From 07d70bc1d96f09664486198847f96873ed33bc0a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 7 Oct 2020 15:35:12 -0500 Subject: [PATCH 001/221] Meeting notification alert bubble --- .../connector/JwtTokenValidationTests.java | 22 ++------ .../com/microsoft/bot/schema/Activity.java | 22 ++++++++ .../bot/schema/teams/NotificationInfo.java | 56 ++++++++++++++++--- 3 files changed, 76 insertions(+), 24 deletions(-) diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java index e0bc4221f..45db7c97a 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java @@ -582,27 +582,17 @@ public void GovernmentChannelValidation_WrongServiceClaimValue_Fails() { } private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(String appId, String pwd, String channelService) throws IOException, ExecutionException, InterruptedException { - ChannelProvider channel = new SimpleChannelProvider(channelService); - String header = channel.isGovernment() ? getGovHeaderToken() : getHeaderToken(); - - JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(header, appId, pwd, channel); + String header = "Bearer " + new MicrosoftAppCredentials(appId, pwd).getToken().join(); + JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(header, appId, pwd, channelService); } - private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(String header, String appId, String pwd, ChannelProvider channel) { + private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Succeeds(String header, String appId, String pwd, String channelService) { CredentialProvider credentials = new SimpleCredentialProvider(appId, pwd); + ChannelProvider channel = new SimpleChannelProvider(channelService); - try { - ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader( - header, - credentials, - channel, - "", - "https://webchat.botframework.com/").join(); + ClaimsIdentity identity = JwtTokenValidation.validateAuthHeader(header, credentials, channel, null, "https://webchat.botframework.com/").join(); - Assert.assertTrue(identity.isAuthenticated()); - } catch (Exception e) { - Assert.fail("Should not have thrown " + e.getClass().getName()); - } + Assert.assertTrue(identity.isAuthenticated()); } private void JwtTokenValidation_ValidateAuthHeader_WithChannelService_Throws(String header, String appId, String pwd, String channelService) throws ExecutionException, InterruptedException { diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index 6d336e698..aff2b263e 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -1820,6 +1820,28 @@ public void teamsNotifyUser() { teamsChannelData.setNotification(new NotificationInfo(true)); } + /** + * Sets the notification of a meeting in the TeamsChannelData. + * @param alertInMeeting True if this is a meeting alert. + * @param externalResourceUrl The external resource Url. + */ + public void teamsNotifyUser(boolean alertInMeeting, String externalResourceUrl) { + TeamsChannelData teamsChannelData; + + try { + teamsChannelData = getChannelData(TeamsChannelData.class); + } catch (JsonProcessingException jpe) { + teamsChannelData = null; + } + + if (teamsChannelData == null) { + teamsChannelData = new TeamsChannelData(); + setChannelData(teamsChannelData); + } + + teamsChannelData.setNotification(new NotificationInfo(true, externalResourceUrl)); + } + /** * Returns this activity as a Message Activity; or null, if this is not that type of activity. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/NotificationInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/NotificationInfo.java index 61c6845ac..95a3db4dc 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/NotificationInfo.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/NotificationInfo.java @@ -15,14 +15,11 @@ public class NotificationInfo { @JsonProperty(value = "alert") private Boolean alert; - /** - * Initialize new NotificationInfo. - * - * @param withAlert initial alert value. - */ - public NotificationInfo(boolean withAlert) { - setAlert(withAlert); - } + @JsonProperty(value = "alertInMeeting") + private Boolean alertInMeeting; + + @JsonProperty(value = "externalResourceUrl") + private String externalResourceUrl; /** * Getter for alert. @@ -42,6 +39,38 @@ public void setAlert(Boolean withAlert) { alert = withAlert; } + /** + * Indicates if this is a meeting alert. + * @return True if this is a meeting alert. + */ + public Boolean getAlertInMeeting() { + return alertInMeeting; + } + + /** + * Indicates if this is a meeting alert. + * @param withAlertInMeeting True if this is a meeting alert. + */ + public void setAlertInMeeting(Boolean withAlertInMeeting) { + alertInMeeting = withAlertInMeeting; + } + + /** + * Gets the resource Url of a meeting alert. + * @return The external resource url. + */ + public String getExternalResourceUrl() { + return externalResourceUrl; + } + + /** + * The resource Url of a meeting alert. + * @param withExternalResourceUrl The external resource Url. + */ + public void setExternalResourceUrl(String withExternalResourceUrl) { + externalResourceUrl = withExternalResourceUrl; + } + /** * A new instance of NotificationInfo. * @@ -51,6 +80,17 @@ public NotificationInfo(Boolean withAlert) { alert = withAlert; } + /** + * A new instance of a meeting alert. + * @param withAlertInMeeting True if this is a meeting alert. + * @param withExternalResourceUrl The external resource Url. + */ + public NotificationInfo(boolean withAlertInMeeting, String withExternalResourceUrl) { + setAlert(true); + setAlertInMeeting(withAlertInMeeting); + setExternalResourceUrl(withExternalResourceUrl); + } + /** * A new instance of NotificationInfo. */ From 5491b91698f267abb60298ac55ffe7cf7310d92a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 08:01:21 +0000 Subject: [PATCH 002/221] Bump junit Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- .../generator-botbuilder-java/generators/app/templates/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/pom.xml index f519f3db7..6f9fbd595 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/pom.xml +++ b/Generator/generator-botbuilder-java/generators/app/templates/pom.xml @@ -25,7 +25,7 @@ junit junit - 4.12 + 4.13.1 test From f584d4e5467f4353ba55d325864bd083cae0be11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 15:22:19 +0000 Subject: [PATCH 003/221] Bump junit from 4.12 to 4.13.1 in /samples/servlet-echo Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/servlet-echo/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index 4803dffbc..62b06fc05 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -46,7 +46,7 @@ junit junit - 4.12 + 4.13.1 test From e06a3ddbc18bdaa6f66536f944743bce76aaa190 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 15:22:53 +0000 Subject: [PATCH 004/221] Bump junit from 4.12 to 4.13.1 Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index ee47b72b6..e4e33cb62 100644 --- a/pom.xml +++ b/pom.xml @@ -216,7 +216,7 @@ junit junit - 4.12 + 4.13.1 test From 44f433f18a342a43bc05c22f3c11c9def02e0bf1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 16:39:12 +0000 Subject: [PATCH 005/221] Bump junit from 4.12 to 4.13.1 in /samples/02.echo-bot Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/02.echo-bot/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index 0826c753f..af633db9a 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -51,7 +51,7 @@ junit junit - 4.12 + 4.13.1 test From c0f91e0c9bb4d12f97c80645bf3e2a84c4cbd51f Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 16:42:47 +0000 Subject: [PATCH 006/221] Bump junit from 4.12 to 4.13.1 in /samples/45.state-management Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/45.state-management/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 4acdd4e62..28efd44f0 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -56,7 +56,7 @@ junit junit - 4.12 + 4.13.1 test From 81ccc802dfaabbd4d62717f877b3d1f2d3da6209 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 16:43:05 +0000 Subject: [PATCH 007/221] Bump junit from 4.12 to 4.13.1 in /samples/03.welcome-user Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/03.welcome-user/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 311b50b0f..603fb5474 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -57,7 +57,7 @@ junit junit - 4.12 + 4.13.1 test From 5f15ff7808fdd5835034ed78edbc6d68f343a621 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 19:53:53 +0000 Subject: [PATCH 008/221] Bump junit from 4.12 to 4.13.1 in /samples/08.suggested-actions Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/08.suggested-actions/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 28e22056a..d377e9373 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -57,7 +57,7 @@ junit junit - 4.12 + 4.13.1 test From bedbfc388adb14332ee965496b5ed534ddb48e50 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Oct 2020 19:54:02 +0000 Subject: [PATCH 009/221] Bump junit from 4.12 to 4.13.1 in /samples/16.proactive-messages Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/16.proactive-messages/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index d72550c3a..3c5b28039 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -57,7 +57,7 @@ junit junit - 4.12 + 4.13.1 test From 5e2bd74ec6422715062653728e4f58f959398e75 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Oct 2020 00:12:14 +0000 Subject: [PATCH 010/221] Bump junit from 4.12 to 4.13.1 in /samples/47.inspection Bumps [junit](https://github.com/junit-team/junit4) from 4.12 to 4.13.1. - [Release notes](https://github.com/junit-team/junit4/releases) - [Changelog](https://github.com/junit-team/junit4/blob/main/doc/ReleaseNotes4.12.md) - [Commits](https://github.com/junit-team/junit4/compare/r4.12...r4.13.1) Signed-off-by: dependabot[bot] --- samples/47.inspection/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index b45ac80a3..bb7984ed6 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -51,7 +51,7 @@ junit junit - 4.12 + 4.13.1 test From 3c69b3ddb82acfe881b5bcc2ce501a2705f23905 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 20 Oct 2020 10:01:13 -0500 Subject: [PATCH 011/221] Removed bin folder from echo-bot --- samples/02.echo-bot/bin/LICENSE | 21 - samples/02.echo-bot/bin/README.md | 90 ---- .../new-rg-parameters.json | 42 -- .../preexisting-rg-parameters.json | 39 -- .../template-with-new-rg.json | 191 -------- .../template-with-preexisting-rg.json | 158 ------- samples/02.echo-bot/bin/pom.xml | 191 -------- .../src/main/resources/application.properties | 2 - .../bin/src/main/webapp/META-INF/MANIFEST.MF | 3 - .../bin/src/main/webapp/WEB-INF/web.xml | 12 - .../bin/src/main/webapp/index.html | 418 ------------------ 11 files changed, 1167 deletions(-) delete mode 100644 samples/02.echo-bot/bin/LICENSE delete mode 100644 samples/02.echo-bot/bin/README.md delete mode 100644 samples/02.echo-bot/bin/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/02.echo-bot/bin/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/02.echo-bot/bin/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/02.echo-bot/bin/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/02.echo-bot/bin/pom.xml delete mode 100644 samples/02.echo-bot/bin/src/main/resources/application.properties delete mode 100644 samples/02.echo-bot/bin/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/02.echo-bot/bin/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/02.echo-bot/bin/src/main/webapp/index.html diff --git a/samples/02.echo-bot/bin/LICENSE b/samples/02.echo-bot/bin/LICENSE deleted file mode 100644 index 09d2ba6d8..000000000 --- a/samples/02.echo-bot/bin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Dave Taniguchi - -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. diff --git a/samples/02.echo-bot/bin/README.md b/samples/02.echo-bot/bin/README.md deleted file mode 100644 index c46553f5c..000000000 --- a/samples/02.echo-bot/bin/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Spring Boot EchoBot - -This demonstrates how to create a Bot using the Bot Framework 4 SDK Preview for Java in Azure. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\springechobot-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or Powershell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` - -#### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` - -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 7. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #7 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/02.echo-bot/bin/deploymentTemplates/new-rg-parameters.json b/samples/02.echo-bot/bin/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/02.echo-bot/bin/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/02.echo-bot/bin/deploymentTemplates/preexisting-rg-parameters.json b/samples/02.echo-bot/bin/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/02.echo-bot/bin/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/02.echo-bot/bin/deploymentTemplates/template-with-new-rg.json b/samples/02.echo-bot/bin/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index dcd6260a5..000000000 --- a/samples/02.echo-bot/bin/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "F0", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "resourcesLocation": "[deployment().location]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new App Service Plan", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using the new App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('appServicePlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} \ No newline at end of file diff --git a/samples/02.echo-bot/bin/deploymentTemplates/template-with-preexisting-rg.json b/samples/02.echo-bot/bin/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index b790d2bdc..000000000 --- a/samples/02.echo-bot/bin/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using an App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", - "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} \ No newline at end of file diff --git a/samples/02.echo-bot/bin/pom.xml b/samples/02.echo-bot/bin/pom.xml deleted file mode 100644 index 9c779a336..000000000 --- a/samples/02.echo-bot/bin/pom.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.connector.sample - spring-echobot - sample - jar - - ${project.groupId}:${project.artifactId} - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.1.7.RELEASE - - - - - UTF-8 - UTF-8 - 1.8 - com.microsoft.bot.sample.echo.Application - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - junit - junit - 4.12 - test - - - com.microsoft.bot.schema - botbuilder-schema - 4.0.0-SNAPSHOT - - - com.microsoft.bot.connector - bot-connector - 4.0.0-SNAPSHOT - - - com.fasterxml.jackson.module - jackson-module-parameter-names - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.2 - - - - - - MyGet - ${repo.url} - - - - - - MyGet - ${repo.url} - - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.echo.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - - - diff --git a/samples/02.echo-bot/bin/src/main/resources/application.properties b/samples/02.echo-bot/bin/src/main/resources/application.properties deleted file mode 100644 index a695b3bf0..000000000 --- a/samples/02.echo-bot/bin/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= diff --git a/samples/02.echo-bot/bin/src/main/webapp/META-INF/MANIFEST.MF b/samples/02.echo-bot/bin/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/02.echo-bot/bin/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/02.echo-bot/bin/src/main/webapp/WEB-INF/web.xml b/samples/02.echo-bot/bin/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/02.echo-bot/bin/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/02.echo-bot/bin/src/main/webapp/index.html b/samples/02.echo-bot/bin/src/main/webapp/index.html deleted file mode 100644 index 40b74c007..000000000 --- a/samples/02.echo-bot/bin/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - From c6577334f90ed7787083a8db2ea194df71e9c713 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 21 Oct 2020 13:41:34 -0500 Subject: [PATCH 012/221] Teams Meeting API --- .../bot/builder/teams/TeamsInfo.java | 60 +++++++++++++++ .../connector/rest/RestTeamsOperations.java | 53 ++++++++++++- .../bot/connector/teams/TeamsOperations.java | 14 ++++ .../com/microsoft/bot/schema/Activity.java | 36 ++++++++- .../schema/teams/MeetingParticipantInfo.java | 46 +++++++++++ .../bot/schema/teams/TeamsChannelData.java | 55 +++++++++++-- .../bot/schema/teams/TeamsMeetingInfo.java | 45 +++++++++++ .../schema/teams/TeamsMeetingParticipant.java | 77 +++++++++++++++++++ 8 files changed, 376 insertions(+), 10 deletions(-) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingInfo.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingParticipant.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java index 091774d2f..65f65c780 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsInfo.java @@ -21,6 +21,7 @@ import com.microsoft.bot.schema.teams.TeamsChannelAccount; import com.microsoft.bot.schema.teams.TeamsChannelData; import com.microsoft.bot.schema.teams.TeamsPagedMembersResult; +import com.microsoft.bot.schema.teams.TeamsMeetingParticipant; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -209,6 +210,65 @@ public static CompletableFuture getPagedMembers( return getPagedMembers(getConnectorClient(turnContext), conversationId, continuationToken); } + /** + * Gets the details for the given meeting participant. This only works in teams meeting scoped conversations. + * @param turnContext The TurnContext that the meeting, participant, and tenant ids are pulled from. + * @return TeamsParticipantChannelAccount + */ + public static CompletableFuture getMeetingParticipant( + TurnContext turnContext + ) { + return getMeetingParticipant(turnContext, null, null, null); + } + + /** + * Gets the details for the given meeting participant. This only works in teams meeting scoped conversations. + * @param turnContext Turn context. + * @param meetingId The meeting id, or null to get from Activities TeamsChannelData + * @param participantId The participant id, or null to get from Activities TeamsChannelData + * @param tenantId The tenant id, or null to get from Activities TeamsChannelData + * @return Team participant channel account. + */ + public static CompletableFuture getMeetingParticipant( + TurnContext turnContext, + String meetingId, + String participantId, + String tenantId + ) { + if (StringUtils.isEmpty(meetingId)) { + meetingId = turnContext.getActivity().teamsGetMeetingInfo() != null + ? turnContext.getActivity().teamsGetMeetingInfo().getId() + : null; + } + if (StringUtils.isEmpty(meetingId)) { + return illegalArgument("TeamsInfo.getMeetingParticipant: method requires a meetingId"); + } + + if (StringUtils.isEmpty(participantId)) { + participantId = turnContext.getActivity().getFrom() != null + ? turnContext.getActivity().getFrom().getAadObjectId() + : null; + } + if (StringUtils.isEmpty(participantId)) { + return illegalArgument("TeamsInfo.getMeetingParticipant: method requires a participantId"); + } + + if (StringUtils.isEmpty(tenantId)) { + tenantId = turnContext.getActivity().teamsGetChannelData() != null + ? turnContext.getActivity().teamsGetChannelData().getTenant().getId() + : null; + } + if (StringUtils.isEmpty(tenantId)) { + return illegalArgument("TeamsInfo.getMeetingParticipant: method requires a tenantId"); + } + + return getTeamsConnectorClient(turnContext).getTeams().fetchParticipant( + meetingId, + participantId, + tenantId + ); + } + private static CompletableFuture> getMembers( ConnectorClient connectorClient, String conversationId diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java index ce799566e..5d79d6f4f 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java @@ -11,9 +11,11 @@ import com.microsoft.bot.rest.ServiceResponse; import com.microsoft.bot.schema.teams.ConversationList; import com.microsoft.bot.schema.teams.TeamDetails; +import com.microsoft.bot.schema.teams.TeamsMeetingParticipant; import okhttp3.ResponseBody; import retrofit2.Response; import retrofit2.Retrofit; +import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.Headers; import retrofit2.http.POST; @@ -22,6 +24,7 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.util.concurrent.CompletableFuture; +import retrofit2.http.Query; /** * msrest impl of TeamsOperations. @@ -117,6 +120,44 @@ private ServiceResponse fetchTeamDetailsDelegate( .build(response); } + /** + * Fetches Teams meeting participant details. + * @param meetingId Teams meeting id + * @param participantId Teams meeting participant id + * @param tenantId Teams meeting tenant id + * @return TeamsParticipantChannelAccount + */ + public CompletableFuture fetchParticipant( + String meetingId, + String participantId, + String tenantId + ) { + return service.fetchParticipant( + meetingId, participantId, tenantId, client.getAcceptLanguage(), client.getUserAgent() + ) + .thenApply(responseBodyResponse -> { + try { + return fetchParticipantDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("fetchParticipant", responseBodyResponse); + } + }); + } + + private ServiceResponse fetchParticipantDelegate( + Response response + ) throws ErrorResponseException, IOException, IllegalArgumentException { + return client.restClient() + .responseBuilderFactory() + .newInstance(client.serializerAdapter()) + .register(HttpURLConnection.HTTP_OK, new TypeToken() { + }.getType()) + .registerError(ErrorResponseException.class) + .build(response); + } + /** * The interface defining all the services for TeamsOperations to be used by * Retrofit to perform actually REST calls. @@ -140,6 +181,16 @@ CompletableFuture> fetchTeamDetails( @Header("accept-language") String acceptLanguage, @Header("User-Agent") String userAgent ); - } + @Headers({ "Content-Type: application/json; charset=utf-8", + "x-ms-logging-context: com.microsoft.bot.schema.Teams fetchParticipant" }) + @GET("v1/meetings/{meetingId}/participants/{participantId}") + CompletableFuture> fetchParticipant( + @Path("meetingId") String meetingId, + @Path("participantId") String participantId, + @Query("tenantId") String tenantId, + @Header("accept-language") String acceptLanguage, + @Header("User-Agent") String userAgent + ); + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsOperations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsOperations.java index 848b241cb..85889c789 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsOperations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsOperations.java @@ -13,6 +13,7 @@ import com.microsoft.bot.schema.teams.ConversationList; import com.microsoft.bot.schema.teams.TeamDetails; +import com.microsoft.bot.schema.teams.TeamsMeetingParticipant; import java.util.concurrent.CompletableFuture; /** @@ -34,4 +35,17 @@ public interface TeamsOperations { * @return The TeamDetails */ CompletableFuture fetchTeamDetails(String teamId); + + /** + * Fetches Teams meeting participant details. + * @param meetingId Teams meeting id + * @param participantId Teams meeting participant id + * @param tenantId Teams meeting tenant id + * @return TeamsMeetingParticipant + */ + CompletableFuture fetchParticipant( + String meetingId, + String participantId, + String tenantId + ); } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index aff2b263e..c90ade421 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -16,6 +16,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.bot.schema.teams.TeamsMeetingInfo; import org.apache.commons.lang3.StringUtils; import java.time.LocalDateTime; @@ -50,9 +51,6 @@ public class Activity { @JsonProperty(value = "localTimestamp") @JsonInclude(JsonInclude.Include.NON_EMPTY) - // @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = - // "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'") - // 2019-10-07T09:49:37-05:00 private OffsetDateTime localTimestamp; @JsonProperty(value = "localTimezone") @@ -1757,6 +1755,22 @@ public String teamsGetChannelId() { return teamsChannelId; } + /** + * Gets the TeamsChannelData. + * @return TeamsChannelData + */ + public TeamsChannelData teamsGetChannelData() { + TeamsChannelData teamsChannelData; + + try { + teamsChannelData = getChannelData(TeamsChannelData.class); + } catch (JsonProcessingException jpe) { + teamsChannelData = null; + } + + return teamsChannelData; + } + /** * Get unique identifier representing a team. * @@ -1842,6 +1856,22 @@ public void teamsNotifyUser(boolean alertInMeeting, String externalResourceUrl) teamsChannelData.setNotification(new NotificationInfo(true, externalResourceUrl)); } + /** + * Gets the TeamsMeetingInfo object from the current activity. + * @return The current activity's team's meeting, or null. + */ + public TeamsMeetingInfo teamsGetMeetingInfo() { + TeamsChannelData teamsChannelData; + + try { + teamsChannelData = getChannelData(TeamsChannelData.class); + } catch (JsonProcessingException jpe) { + teamsChannelData = null; + } + + return teamsChannelData != null ? teamsChannelData.getMeeting() : null; + } + /** * Returns this activity as a Message Activity; or null, if this is not that type of activity. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java new file mode 100644 index 000000000..c3b8597db --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java @@ -0,0 +1,46 @@ +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Teams meeting participant details. + */ +public class MeetingParticipantInfo { + @JsonProperty(value = "role") + private String role; + + @JsonProperty(value = "inMeeting") + private boolean inMeeting; + + /** + * Gets the participant's role in the meeting. + * @return The participant's role in the meeting. + */ + public String getRole() { + return role; + } + + /** + * Sets the participant's role in the meeting. + * @param withRole The participant's role in the meeting. + */ + public void setRole(String withRole) { + role = withRole; + } + + /** + * Gets a value indicating whether the participant is in the meeting or not. + * @return The value indicating if the participant is in the meeting. + */ + public boolean isInMeeting() { + return inMeeting; + } + + /** + * Sets a value indicating whether the participant is in the meeting or not. + * @param withInMeeting The value indicating if the participant is in the meeting. + */ + public void setInMeeting(boolean withInMeeting) { + inMeeting = withInMeeting; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsChannelData.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsChannelData.java index 27448a2a8..166f07b44 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsChannelData.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsChannelData.java @@ -3,7 +3,12 @@ package com.microsoft.bot.schema.teams; +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.util.HashMap; +import java.util.Map; /** * Channel data specific to messages received in Microsoft Teams. @@ -18,24 +23,23 @@ public class TeamsChannelData { @JsonProperty(value = "channel") private ChannelInfo channel; - /// Gets or sets type of event. @JsonProperty(value = "eventType") private String eventType; - /// Gets or sets information about the team in which the message was - /// sent @JsonProperty(value = "team") private TeamInfo team; - /// Gets or sets notification settings for the message @JsonProperty(value = "notification") private NotificationInfo notification; - /// Gets or sets information about the tenant in which the message was - /// sent @JsonProperty(value = "tenant") private TenantInfo tenant; + @JsonProperty(value = "meeting") + private TeamsMeetingInfo meeting; + + private HashMap properties = new HashMap<>(); + /** * Get unique identifier representing a channel. * @@ -163,6 +167,45 @@ public void setTenant(TenantInfo withTenant) { this.tenant = withTenant; } + /** + * Information about the meeting in which the message was sent. + * @return The meeting info + */ + public TeamsMeetingInfo getMeeting() { + return meeting; + } + + /** + * Sets information about the meeting in which the message was sent. + * @param withMeeting The meeting info + */ + public void setMeeting(TeamsMeetingInfo withMeeting) { + meeting = withMeeting; + } + + /** + * Holds the overflow properties that aren't first class properties in the + * object. This allows extensibility while maintaining the object. + * + * @return Map of additional properties. + */ + @JsonAnyGetter + public Map getProperties() { + return this.properties; + } + + /** + * Holds the overflow properties that aren't first class properties in the + * object. This allows extensibility while maintaining the object. + * + * @param key The key of the property to set. + * @param withValue The value for the property. + */ + @JsonAnySetter + public void setProperties(String key, JsonNode withValue) { + this.properties.put(key, withValue); + } + /** * A new instance of TeamChannelData. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingInfo.java new file mode 100644 index 000000000..ec659ab68 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingInfo.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Describes a Teams Meeting. + */ +public class TeamsMeetingInfo { + @JsonProperty(value = "id") + private String id; + + /** + * New Teams meeting. + */ + public TeamsMeetingInfo() { + + } + + /** + * New Teams meeting with ID. + * @param withId Unique identifier representing a teams meeting. + */ + public TeamsMeetingInfo(String withId) { + id = withId; + } + + /** + * Gets the unique identifier representing a meeting. + * @return The meeting id + */ + public String getId() { + return id; + } + + /** + * Sets the unique identifier representing a meeting. + * @param withId The meeting id + */ + public void setId(String withId) { + id = withId; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingParticipant.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingParticipant.java new file mode 100644 index 000000000..63b4b0742 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TeamsMeetingParticipant.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.schema.ConversationAccount; + +/** + * Teams participant channel account detailing user Azure Active Directory and meeting + * participant details. + */ +public class TeamsMeetingParticipant { + @JsonProperty(value = "user") + private TeamsChannelAccount user; + + @JsonProperty(value = "meeting") + private MeetingParticipantInfo meeting; + + @JsonProperty(value = "conversation") + private ConversationAccount conversation; + + /** + * Create TeamsParticipantChannelAccount. + */ + public TeamsMeetingParticipant() { + + } + + /** + * Gets the participant's user information. + * @return The participant's user information. + */ + public TeamsChannelAccount getUser() { + return user; + } + + /** + * Sets the participant's user information. + * @param withUser The participant's user information. + */ + public void setUser(TeamsChannelAccount withUser) { + user = withUser; + } + + /** + * Gets the participant's meeting information. + * @return The participant's role in the meeting. + */ + public MeetingParticipantInfo getMeeting() { + return meeting; + } + + /** + * Sets the participant's meeting information. + * @param withMeeting The participant's role in the meeting. + */ + public void setMeeting(MeetingParticipantInfo withMeeting) { + meeting = withMeeting; + } + + /** + * Gets the Conversation Account for the meeting. + * @return The Conversation Account for the meeting. + */ + public ConversationAccount getConversation() { + return conversation; + } + + /** + * Sets the Conversation Account for the meeting. + * @param withConversation The Conversation Account for the meeting. + */ + public void setConversation(ConversationAccount withConversation) { + conversation = withConversation; + } +} From e58bc6f3862b8963a7ff2a65e5bab0b26a6f713c Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 22 Oct 2020 11:25:06 -0500 Subject: [PATCH 013/221] Teams CacheInfo --- .../microsoft/bot/schema/teams/CacheInfo.java | 50 +++++++++++++++++++ .../MessagingExtensionActionResponse.java | 20 ++++++++ .../teams/MessagingExtensionResponse.java | 23 ++++++++- .../bot/schema/teams/TaskModuleResponse.java | 21 ++++++++ 4 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java new file mode 100644 index 000000000..2c8b5e913 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java @@ -0,0 +1,50 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A cache info object which notifies Teams how long an object should be cached for. + */ +public class CacheInfo { + @JsonProperty(value = "cacheType") + private String cacheType; + + @JsonProperty(value = "cacheDuration") + private int cacheDuration; + + /** + * Gets cache type. + * @return The type of cache for this object. + */ + public String getCacheType() { + return cacheType; + } + + /** + * Sets cache type. + * @param withCacheType The type of cache for this object. + */ + public void setCacheType(String withCacheType) { + cacheType = withCacheType; + } + + /** + * Gets cache duration. + * @return The time in seconds for which the cached object should remain in the cache. + */ + public int getCacheDuration() { + return cacheDuration; + } + + /** + * Sets cache duration. + * @param withCacheDuration The time in seconds for which the cached object should + * remain in the cache. + */ + public void setCacheDuration(int withCacheDuration) { + cacheDuration = withCacheDuration; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionActionResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionActionResponse.java index 221d727b1..c12ef85ed 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionActionResponse.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionActionResponse.java @@ -18,6 +18,10 @@ public class MessagingExtensionActionResponse { @JsonInclude(JsonInclude.Include.NON_EMPTY) private MessagingExtensionResult composeExtension; + @JsonProperty(value = "cacheInfo") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private CacheInfo cacheInfo; + /** * Gets the Adaptive card to appear in the task module. * @@ -53,4 +57,20 @@ public MessagingExtensionResult getComposeExtension() { public void setComposeExtension(MessagingExtensionResult withComposeExtension) { composeExtension = withComposeExtension; } + + /** + * Gets the CacheInfo for this MessagingExtensionActionResponse. + * @return CacheInfo + */ + public CacheInfo getCacheInfo() { + return cacheInfo; + } + + /** + * Sets the CacheInfo for this MessagingExtensionActionResponse. + * @param withCacheInfo CacheInfo + */ + public void setCacheInfo(CacheInfo withCacheInfo) { + cacheInfo = withCacheInfo; + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionResponse.java index df81bd703..3c3d2675a 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionResponse.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MessagingExtensionResponse.java @@ -3,6 +3,7 @@ package com.microsoft.bot.schema.teams; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -12,8 +13,12 @@ public class MessagingExtensionResponse { @JsonProperty(value = "composeExtension") private MessagingExtensionResult composeExtension; + @JsonProperty(value = "cacheInfo") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private CacheInfo cacheInfo; + /** - * Creates a new empty response with the specified result. + * Creates a new empty response. */ public MessagingExtensionResponse() { @@ -45,4 +50,20 @@ public MessagingExtensionResult getComposeExtension() { public void setComposeExtension(MessagingExtensionResult withComposeExtension) { composeExtension = withComposeExtension; } + + /** + * Gets the CacheInfo for this MessagingExtensionResponse. + * @return CacheInfo + */ + public CacheInfo getCacheInfo() { + return cacheInfo; + } + + /** + * Sets the CacheInfo for this MessagingExtensionResponse. + * @param withCacheInfo CacheInfo + */ + public void setCacheInfo(CacheInfo withCacheInfo) { + cacheInfo = withCacheInfo; + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleResponse.java index 48072a989..13ab132cd 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleResponse.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleResponse.java @@ -3,6 +3,7 @@ package com.microsoft.bot.schema.teams; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -12,6 +13,10 @@ public class TaskModuleResponse { @JsonProperty(value = "task") private TaskModuleResponseBase task; + @JsonProperty(value = "cacheInfo") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private CacheInfo cacheInfo; + /** * Gets the response task. * @@ -29,4 +34,20 @@ public TaskModuleResponseBase getTask() { public void setTask(TaskModuleResponseBase withTask) { task = withTask; } + + /** + * Gets the CacheInfo for this MessagingExtensionActionResponse. + * @return CacheInfo + */ + public CacheInfo getCacheInfo() { + return cacheInfo; + } + + /** + * Sets the CacheInfo for this MessagingExtensionActionResponse. + * @param withCacheInfo CacheInfo + */ + public void setCacheInfo(CacheInfo withCacheInfo) { + cacheInfo = withCacheInfo; + } } From 94b736e3950e1af35fd85168e52330b7c54b3b9d Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 26 Oct 2020 15:31:02 -0500 Subject: [PATCH 014/221] Version bump to 4.6.0-preview8 --- libraries/bot-ai-luis-v3/pom.xml | 2 +- libraries/bot-ai-qna/pom.xml | 2 +- libraries/bot-applicationinsights/pom.xml | 2 +- libraries/bot-azure/pom.xml | 2 +- libraries/bot-builder/pom.xml | 2 +- libraries/bot-connector/pom.xml | 2 +- libraries/bot-dialogs/pom.xml | 2 +- libraries/bot-integration-core/pom.xml | 2 +- libraries/bot-integration-spring/pom.xml | 2 +- libraries/bot-schema/pom.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/libraries/bot-ai-luis-v3/pom.xml b/libraries/bot-ai-luis-v3/pom.xml index b5e3da6da..7622682ee 100644 --- a/libraries/bot-ai-luis-v3/pom.xml +++ b/libraries/bot-ai-luis-v3/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-ai-qna/pom.xml b/libraries/bot-ai-qna/pom.xml index b18f1eeca..b2f1898c6 100644 --- a/libraries/bot-ai-qna/pom.xml +++ b/libraries/bot-ai-qna/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-applicationinsights/pom.xml b/libraries/bot-applicationinsights/pom.xml index 4670963d9..46b11893c 100644 --- a/libraries/bot-applicationinsights/pom.xml +++ b/libraries/bot-applicationinsights/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-azure/pom.xml b/libraries/bot-azure/pom.xml index 5be92b71a..d044b1ec6 100644 --- a/libraries/bot-azure/pom.xml +++ b/libraries/bot-azure/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-builder/pom.xml b/libraries/bot-builder/pom.xml index 01c9d2203..98a32ca89 100644 --- a/libraries/bot-builder/pom.xml +++ b/libraries/bot-builder/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index 56dab3981..d764843bf 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index a2ef707a8..1958da61d 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-integration-core/pom.xml b/libraries/bot-integration-core/pom.xml index 19bfc303a..e03286c6d 100644 --- a/libraries/bot-integration-core/pom.xml +++ b/libraries/bot-integration-core/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-integration-spring/pom.xml b/libraries/bot-integration-spring/pom.xml index e67c8abd2..96ec043d1 100644 --- a/libraries/bot-integration-spring/pom.xml +++ b/libraries/bot-integration-spring/pom.xml @@ -5,7 +5,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml diff --git a/libraries/bot-schema/pom.xml b/libraries/bot-schema/pom.xml index df4ea4baf..a4e7a7ccc 100644 --- a/libraries/bot-schema/pom.xml +++ b/libraries/bot-schema/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 ../../pom.xml From 0f8f6d4a3eeeb4494526360ba4335d76ed726e63 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 26 Oct 2020 15:31:35 -0500 Subject: [PATCH 015/221] Version bump to 4.6.0-prewiew8 --- pom.xml | 2 +- samples/02.echo-bot/pom.xml | 2 +- samples/03.welcome-user/pom.xml | 2 +- samples/08.suggested-actions/pom.xml | 2 +- samples/16.proactive-messages/pom.xml | 2 +- samples/45.state-management/pom.xml | 2 +- samples/47.inspection/pom.xml | 2 +- samples/50.teams-messaging-extensions-search/pom.xml | 2 +- samples/51.teams-messaging-extensions-action/pom.xml | 2 +- .../52.teams-messaging-extensions-search-auth-config/pom.xml | 2 +- samples/53.teams-messaging-extensions-action-preview/pom.xml | 2 +- samples/54.teams-task-module/pom.xml | 2 +- samples/55.teams-link-unfurling/pom.xml | 2 +- samples/56.teams-file-upload/pom.xml | 2 +- samples/57.teams-conversation-bot/pom.xml | 2 +- samples/58.teams-start-new-thread-in-channel/pom.xml | 2 +- samples/servlet-echo/pom.xml | 2 +- 17 files changed, 17 insertions(+), 17 deletions(-) diff --git a/pom.xml b/pom.xml index e4e33cb62..75b63485c 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.microsoft.bot bot-java - 4.6.0-preview7 + 4.6.0-preview8 pom Microsoft BotBuilder Java SDK Parent diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index af633db9a..8be1af926 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 603fb5474..a42260a83 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index d377e9373..9e41dfe01 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index 3c5b28039..9c187e338 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 28efd44f0..306206b77 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -78,7 +78,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index bb7984ed6..0e5e9d588 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index 4f005618c..b5c9fe9ae 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -84,7 +84,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index f9897f033..8af7b6e7b 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index a65524bff..ff0760822 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -84,7 +84,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index 3a8689268..8fcc80646 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index a78083366..68a119d90 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -77,7 +77,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index 1bcd80db3..d266c7e7d 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index 672a9c438..f576403c3 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index 977337be0..8162a848c 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -79,7 +79,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index 0291c9e7c..12f410598 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -77,7 +77,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview7 + 4.6.0-preview8 compile diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index 62b06fc05..2bfd8a39c 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -89,7 +89,7 @@ com.microsoft.bot bot-integration-core - 4.6.0-preview7 + 4.6.0-preview8 From 9d389e5f571eb5af1a69ab191ec9c07d8ffb3031 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 26 Oct 2020 16:29:18 -0500 Subject: [PATCH 016/221] README version bump --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80ec6c6ef..f94e24d81 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | |--------|-------------|--------------|-----------------| - |Main | Preview 6 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | + |Main | Preview 8 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | ## Getting Started To get started building bots using the SDK, see the [Azure Bot Service Documentation](https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0). From 6628ebd81d343463b09c8b78969de36526511932 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 26 Oct 2020 16:30:21 -0500 Subject: [PATCH 017/221] README version bump --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 80ec6c6ef..f94e24d81 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | |--------|-------------|--------------|-----------------| - |Main | Preview 6 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | + |Main | Preview 8 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | ## Getting Started To get started building bots using the SDK, see the [Azure Bot Service Documentation](https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0). From 508793115d3bc46690020f54d02a47fb48d9aa92 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 26 Oct 2020 16:32:13 -0500 Subject: [PATCH 018/221] Corrected README version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f94e24d81..43463c550 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | |--------|-------------|--------------|-----------------| - |Main | Preview 8 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | + |Main | Preview 7 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | ## Getting Started To get started building bots using the SDK, see the [Azure Bot Service Documentation](https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0). From 07204117de5558d0bac7e61e5dcb9ef060d53210 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 27 Oct 2020 08:58:16 -0500 Subject: [PATCH 019/221] Teams schema class corrections --- .../java/com/microsoft/bot/schema/teams/CacheInfo.java | 6 +++--- .../bot/schema/teams/MeetingParticipantInfo.java | 8 +++++--- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java index 2c8b5e913..917addc65 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/CacheInfo.java @@ -13,7 +13,7 @@ public class CacheInfo { private String cacheType; @JsonProperty(value = "cacheDuration") - private int cacheDuration; + private Integer cacheDuration; /** * Gets cache type. @@ -35,7 +35,7 @@ public void setCacheType(String withCacheType) { * Gets cache duration. * @return The time in seconds for which the cached object should remain in the cache. */ - public int getCacheDuration() { + public Integer getCacheDuration() { return cacheDuration; } @@ -44,7 +44,7 @@ public int getCacheDuration() { * @param withCacheDuration The time in seconds for which the cached object should * remain in the cache. */ - public void setCacheDuration(int withCacheDuration) { + public void setCacheDuration(Integer withCacheDuration) { cacheDuration = withCacheDuration; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java index c3b8597db..70e5c44b3 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java @@ -1,5 +1,6 @@ package com.microsoft.bot.schema.teams; +import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; /** @@ -10,7 +11,8 @@ public class MeetingParticipantInfo { private String role; @JsonProperty(value = "inMeeting") - private boolean inMeeting; + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Boolean inMeeting; /** * Gets the participant's role in the meeting. @@ -32,7 +34,7 @@ public void setRole(String withRole) { * Gets a value indicating whether the participant is in the meeting or not. * @return The value indicating if the participant is in the meeting. */ - public boolean isInMeeting() { + public Boolean isInMeeting() { return inMeeting; } @@ -40,7 +42,7 @@ public boolean isInMeeting() { * Sets a value indicating whether the participant is in the meeting or not. * @param withInMeeting The value indicating if the participant is in the meeting. */ - public void setInMeeting(boolean withInMeeting) { + public void setInMeeting(Boolean withInMeeting) { inMeeting = withInMeeting; } } From c6a76feda78b4a5c30d82eec7a34030f7d28c4c9 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 28 Oct 2020 09:46:14 -0500 Subject: [PATCH 020/221] Removed unused "as" methods. (#834) --- .../com/microsoft/bot/schema/Activity.java | 126 ------- .../microsoft/bot/schema/ActivityTest.java | 308 ------------------ 2 files changed, 434 deletions(-) diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index c90ade421..560c41d99 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -1871,130 +1871,4 @@ public TeamsMeetingInfo teamsGetMeetingInfo() { return teamsChannelData != null ? teamsChannelData.getMeeting() : null; } - - /** - * Returns this activity as a Message Activity; or null, if this is not that type of activity. - * - * @return This activity as a message activity; or null. - */ - public Activity asMessageActivity() { - return isActivity(ActivityTypes.MESSAGE) ? this : null; - } - - /** - * Returns this activity as a Contact Relation Update Activity; or null, if this is not that type of activity. - * - * @return This activity as a contact relation update activity; or null. - */ - public Activity asContactRelationUpdateActivity() { - return isActivity(ActivityTypes.CONTACT_RELATION_UPDATE) ? this : null; - } - - /** - * Returns this activity as an Installation Update Activity; or null, if this is not that type of activity. - * - * @return This activity as an installation update activity; or null. - */ - public Activity asInstallationUpdateActivity() { - return isActivity(ActivityTypes.INSTALLATION_UPDATE) ? this : null; - } - - /** - * Returns this activity as a Conversation Update Activity; or null, if this is not that type of activity. - * - * @return This activity as a conversation update activity; or null. - */ - public Activity asConversationUpdateActivity() { - return isActivity(ActivityTypes.CONVERSATION_UPDATE) ? this : null; - } - - /** - * Returns this activity as a Typing Activity; or null, if this is not that type of activity. - * - * @return This activity as a typing activity; or null. - */ - public Activity asTypingActivity() { - return isActivity(ActivityTypes.TYPING) ? this : null; - } - - /** - * Returns this activity an End of Conversation Activity; or null, if this is not that type of activity. - * - * @return This activity as an end of conversation activity; or null. - */ - public Activity asEndOfConversationActivity() { - return isActivity(ActivityTypes.END_OF_CONVERSATION) ? this : null; - } - - /** - * Returns this activity an Event Activity; or null, if this is not that type of activity. - * - * @return This activity as an event activity; or null. - */ - public Activity asEventActivity() { - return isActivity(ActivityTypes.EVENT) ? this : null; - } - - /** - * Returns this activity an Invoke Activity; or null, if this is not that type of activity. - * - * @return This activity as an invoke activity; or null. - */ - public Activity asInvokeActivity() { - return isActivity(ActivityTypes.INVOKE) ? this : null; - } - - /** - * Returns this activity a Message Update Activity; or null, if this is not that type of activity. - * - * @return This activity as a message update activity; or null. - */ - public Activity asMessageUpdateActivity() { - return isActivity(ActivityTypes.MESSAGE_UPDATE) ? this : null; - } - - /** - * Returns this activity a Message Delete Activity; or null, if this is not that type of activity. - * - * @return This activity as a message delete activity; or null. - */ - public Activity asMessageDeleteActivity() { - return isActivity(ActivityTypes.MESSAGE_DELETE) ? this : null; - } - - /** - * Returns this activity a Message Reaction Activity; or null, if this is not that type of activity. - * - * @return This activity as a message reaction activity; or null. - */ - public Activity asMessageReactionActivity() { - return isActivity(ActivityTypes.MESSAGE_REACTION) ? this : null; - } - - /** - * Returns this activity a Suggestion Activity; or null, if this is not that type of activity. - * - * @return This activity as a suggestion activity; or null. - */ - public Activity asSuggestionActivity() { - return isActivity(ActivityTypes.SUGGESTION) ? this : null; - } - - /** - * Returns this activity a Trace Activity; or null, if this is not that type of activity. - * - * @return This activity as a trace activity; or null. - */ - public Activity asTraceActivity() { - return isActivity(ActivityTypes.TRACE) ? this : null; - } - - /** - * Returns this activity a Handoff Activity; or null, if this is not that type of activity. - * - * @return This activity as a handoff activity; or null. - */ - public Activity asHandoffActivity() { - return isActivity(ActivityTypes.HANDOFF) ? this : null; - } } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java index de444d73e..61bf75290 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java @@ -403,314 +403,6 @@ public void CreateReplyWithoutArguments() { Assert.assertEquals(reply.getLocale(), activity.getLocale()); } - @Test - public void AsMessageActivity() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asMessageActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.MESSAGE); - } - - @Test - public void AsMessageActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("trace"); - - Activity result = activity.asMessageActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsContactRelationUpdateActivity() { - Activity activity = createActivity(); - - activity.setType("contactRelationUpdate"); - - Activity result = activity.asContactRelationUpdateActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.CONTACT_RELATION_UPDATE); - } - - @Test - public void AsContactRelationUpdateActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asContactRelationUpdateActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsInstallationUpdateActivity() { - Activity activity = createActivity(); - - activity.setType("installationUpdate"); - - Activity result = activity.asInstallationUpdateActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.INSTALLATION_UPDATE); - } - - @Test - public void AsInstallationUpdateActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asInstallationUpdateActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsConversationUpdateActivity() { - Activity activity = createActivity(); - - activity.setType("conversationUpdate"); - - Activity result = activity.asConversationUpdateActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.CONVERSATION_UPDATE); - } - - @Test - public void AsConversationUpdateActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asConversationUpdateActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsTypingActivity() { - Activity activity = createActivity(); - - activity.setType("typing"); - - Activity result = activity.asTypingActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.TYPING); - } - - @Test - public void AsTypingActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asTypingActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsEndOfConversationActivity() { - Activity activity = createActivity(); - - activity.setType("endOfConversation"); - - Activity result = activity.asEndOfConversationActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.END_OF_CONVERSATION); - } - - @Test - public void AsEndOfConversationActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asEndOfConversationActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsEventActivity() { - Activity activity = createActivity(); - - activity.setType("event"); - - Activity result = activity.asEventActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.EVENT); - } - - @Test - public void AsEventActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asEventActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsInvokeActivity() { - Activity activity = createActivity(); - - activity.setType("invoke"); - - Activity result = activity.asInvokeActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.INVOKE); - } - - @Test - public void AsInvokeActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asInvokeActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsMessageUpdateActivity() { - Activity activity = createActivity(); - - activity.setType("messageUpdate"); - - Activity result = activity.asMessageUpdateActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.MESSAGE_UPDATE); - } - - @Test - public void AsMessageUpdateActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asMessageUpdateActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsMessageDeleteActivity() { - Activity activity = createActivity(); - - activity.setType("messageDelete"); - - Activity result = activity.asMessageDeleteActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.MESSAGE_DELETE); - } - - @Test - public void AsMessageDeleteActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asMessageDeleteActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsMessageReactionActivity() { - Activity activity = createActivity(); - - activity.setType("messageReaction"); - - Activity result = activity.asMessageReactionActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.MESSAGE_REACTION); - } - - @Test - public void AsMessageReactionActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asMessageReactionActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsSuggestionActivity() { - Activity activity = createActivity(); - - activity.setType("suggestion"); - - Activity result = activity.asSuggestionActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.SUGGESTION); - } - - @Test - public void AsSuggestionActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asSuggestionActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsTraceActivity() { - Activity activity = createActivity(); - - activity.setType("trace"); - - Activity result = activity.asTraceActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.TRACE); - } - - @Test - public void AsTraceActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asTraceActivity(); - - Assert.assertEquals(result, null); - } - - @Test - public void AsHandoffActivity() { - Activity activity = createActivity(); - - activity.setType("handoff"); - - Activity result = activity.asHandoffActivity(); - - Assert.assertEquals(result.getType(), ActivityTypes.HANDOFF); - } - - @Test - public void AsHandoffActivityIsNull() { - Activity activity = createActivity(); - - activity.setType("message"); - - Activity result = activity.asHandoffActivity(); - - Assert.assertEquals(result, null); - } - @Test public void HasContentIsFalseWhenActivityTextHasNoContent() { Activity activity = createActivity(); From 794af6f6cf2cb4453e6a83621204e94567145e2a Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 28 Oct 2020 09:46:58 -0500 Subject: [PATCH 021/221] Added constant for "empty speak tag" (#835) * Added constant for Empty Speak Tag * Fix Checkstyle issues --- .../microsoft/bot/schema/SpeechConstants.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SpeechConstants.java diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SpeechConstants.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SpeechConstants.java new file mode 100644 index 000000000..3b267af3f --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SpeechConstants.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +/** + * Defines constants that can be used in the processing of speech interactions. + */ +public final class SpeechConstants { + + private SpeechConstants() { + + } + + /** + * The xml tag structure to indicate an empty speak tag, to be used in the 'speak' property of an Activity. + * When this is set it indicates to the channel that speech should not be generated. + */ + public static final String EMPTY_SPEAK_TAG = + ""; +} From aa307c54ae9219fd439792a201a31d3bd271d9ac Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 28 Oct 2020 11:20:25 -0500 Subject: [PATCH 022/221] Added typing activity to activityhandler (#837) --- .../microsoft/bot/builder/ActivityHandler.java | 14 ++++++++++++++ .../bot/builder/ActivityHandlerTests.java | 18 ++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index 3eb858ebe..6a451e17d 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -80,6 +80,9 @@ public CompletableFuture onTurn(TurnContext turnContext) { case ActivityTypes.INSTALLATION_UPDATE: return onInstallationUpdate(turnContext); + case ActivityTypes.TYPING: + return onTypingActivity(turnContext); + case ActivityTypes.INVOKE: return onInvokeActivity(turnContext).thenCompose(invokeResponse -> { // If OnInvokeActivityAsync has already sent an InvokeResponse, do not send @@ -501,6 +504,17 @@ protected CompletableFuture onInstallationUpdate(TurnContext turnContext) return CompletableFuture.completedFuture(null); } + /** + * Override this in a derived class to provide logic specific to + * ActivityTypes.Typing activities. + * + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onTypingActivity(TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + /** * Invoked when an activity other than a message, conversation update, or event * is received when the base behavior of {@link #onTurn(TurnContext)} is used. diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 06aef2590..6cf214870 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -33,6 +33,18 @@ public void TestOnInstallationUpdate() { Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); } + @Test + public void TestOnTypingActivity() { + Activity activity = new Activity(ActivityTypes.TYPING); + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(1, bot.getRecord().size()); + Assert.assertEquals("onTypingActivity", bot.getRecord().get(0)); + } + @Test public void TestMemberAdded1() { Activity activity = new Activity() { @@ -449,6 +461,12 @@ protected CompletableFuture onInstallationUpdate(TurnContext turnContext) { return super.onInstallationUpdate(turnContext); } + @Override + protected CompletableFuture onTypingActivity(TurnContext turnContext) { + record.add("onTypingActivity"); + return super.onTypingActivity(turnContext); + } + @Override protected CompletableFuture onUnrecognizedActivityType(TurnContext turnContext) { record.add("onUnrecognizedActivityType"); From 59a7ae7deb96179ac0f75684ec0e932e310b7869 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 30 Oct 2020 08:15:08 -0500 Subject: [PATCH 023/221] Updated pom.xml to use parameters (#838) --- samples/02.echo-bot/README.md | 13 ++++--------- samples/02.echo-bot/pom.xml | 4 ++-- samples/03.welcome-user/README.md | 13 ++++--------- samples/03.welcome-user/pom.xml | 4 ++-- samples/08.suggested-actions/README.md | 13 ++++--------- samples/08.suggested-actions/pom.xml | 4 ++-- samples/16.proactive-messages/README.md | 13 ++++--------- samples/16.proactive-messages/pom.xml | 4 ++-- samples/45.state-management/README.md | 13 ++++--------- samples/45.state-management/pom.xml | 4 ++-- samples/47.inspection/README.md | 13 ++++--------- samples/47.inspection/pom.xml | 4 ++-- samples/servlet-echo/README.md | 13 ++++--------- samples/servlet-echo/pom.xml | 4 ++-- 14 files changed, 42 insertions(+), 77 deletions(-) diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index 476bdc790..a98e6592f 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -59,23 +59,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index 8be1af926..f9a1939bf 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -143,8 +143,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index e6aef2368..69a2012e3 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -59,23 +59,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index a42260a83..0fb01da59 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -143,8 +143,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md index 229ef66fe..6ed0433d6 100644 --- a/samples/08.suggested-actions/README.md +++ b/samples/08.suggested-actions/README.md @@ -59,23 +59,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 9e41dfe01..d1360ada3 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -143,8 +143,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/16.proactive-messages/README.md b/samples/16.proactive-messages/README.md index 25c000e00..cccc250a4 100644 --- a/samples/16.proactive-messages/README.md +++ b/samples/16.proactive-messages/README.md @@ -68,23 +68,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index 9c187e338..c598b6a91 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -143,8 +143,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/45.state-management/README.md b/samples/45.state-management/README.md index 3e8ef2fa0..bbf7be4be 100644 --- a/samples/45.state-management/README.md +++ b/samples/45.state-management/README.md @@ -62,23 +62,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 306206b77..32c20d122 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -142,8 +142,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/47.inspection/README.md b/samples/47.inspection/README.md index 7b99e24d0..12bb4d250 100644 --- a/samples/47.inspection/README.md +++ b/samples/47.inspection/README.md @@ -74,23 +74,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the botsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Further reading diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index 0e5e9d588..cc29f2371 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -143,8 +143,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS diff --git a/samples/servlet-echo/README.md b/samples/servlet-echo/README.md index 6eea0c93b..0b08a5371 100644 --- a/samples/servlet-echo/README.md +++ b/samples/servlet-echo/README.md @@ -41,23 +41,18 @@ Replace the values for ``, ``, ``, and `` #### To an existing Resource Group `az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password +### 5. Update app id and password In src/main/resources/application.properties update - `MicrosoftAppPassword` with the appsecret value - `MicrosoftAppId` with the appid from the first step -### 7. Deploy the code +### 6. Deploy the code - Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. -After the bot is deployed, you only need to execute #7 if you make changes to the bot. +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ## Reference diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index 2bfd8a39c..0d95ad232 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -141,8 +141,8 @@ 1.7.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS From edf96ef7d16a5e12caafd6cf39e3291cb204b5e6 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 30 Oct 2020 14:19:30 -0500 Subject: [PATCH 024/221] Updated samples to use port 3978 (#839) --- samples/02.echo-bot/README.md | 2 +- samples/02.echo-bot/src/main/resources/application.properties | 1 + samples/02.echo-bot/src/main/webapp/index.html | 2 +- samples/03.welcome-user/README.md | 2 +- .../03.welcome-user/src/main/resources/application.properties | 1 + samples/03.welcome-user/src/main/webapp/index.html | 2 +- samples/08.suggested-actions/README.md | 2 +- samples/08.suggested-actions/bin/README.md | 2 +- samples/08.suggested-actions/bin/src/main/webapp/index.html | 2 +- .../src/main/resources/application.properties | 1 + samples/08.suggested-actions/src/main/webapp/index.html | 2 +- samples/16.proactive-messages/README.md | 2 +- samples/16.proactive-messages/bin/README.md | 2 +- samples/16.proactive-messages/bin/src/main/webapp/index.html | 2 +- .../java/com/microsoft/bot/sample/proactive/ProactiveBot.java | 2 +- .../src/main/resources/application.properties | 1 + samples/16.proactive-messages/src/main/webapp/index.html | 2 +- samples/45.state-management/README.md | 2 +- .../src/main/resources/application.properties | 1 + samples/45.state-management/src/main/webapp/index.html | 2 +- samples/47.inspection/README.md | 4 ++-- .../47.inspection/src/main/resources/application.properties | 1 + samples/47.inspection/src/main/webapp/index.html | 2 +- samples/50.teams-messaging-extensions-search/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + .../src/main/webapp/index.html | 2 +- samples/51.teams-messaging-extensions-action/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + .../src/main/webapp/index.html | 2 +- .../README.md | 4 ++-- .../src/main/resources/application.properties | 1 + .../src/main/webapp/index.html | 2 +- .../53.teams-messaging-extensions-action-preview/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + .../src/main/webapp/index.html | 2 +- samples/54.teams-task-module/README.md | 4 ++-- .../src/main/resources/application.properties | 3 ++- samples/55.teams-link-unfurling/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + samples/55.teams-link-unfurling/src/main/webapp/index.html | 2 +- samples/56.teams-file-upload/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + samples/56.teams-file-upload/src/main/webapp/index.html | 2 +- samples/57.teams-conversation-bot/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + samples/57.teams-conversation-bot/src/main/webapp/index.html | 2 +- samples/58.teams-start-new-thread-in-channel/README.md | 4 ++-- .../src/main/resources/application.properties | 1 + .../src/main/webapp/index.html | 2 +- 49 files changed, 60 insertions(+), 45 deletions(-) diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index a98e6592f..a89774b04 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -25,7 +25,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/02.echo-bot/src/main/resources/application.properties b/samples/02.echo-bot/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/02.echo-bot/src/main/resources/application.properties +++ b/samples/02.echo-bot/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/02.echo-bot/src/main/webapp/index.html b/samples/02.echo-bot/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/02.echo-bot/src/main/webapp/index.html +++ b/samples/02.echo-bot/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index 69a2012e3..e65396c03 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -25,7 +25,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/03.welcome-user/src/main/resources/application.properties b/samples/03.welcome-user/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/03.welcome-user/src/main/resources/application.properties +++ b/samples/03.welcome-user/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/03.welcome-user/src/main/webapp/index.html b/samples/03.welcome-user/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/03.welcome-user/src/main/webapp/index.html +++ b/samples/03.welcome-user/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md index 6ed0433d6..40dcd231e 100644 --- a/samples/08.suggested-actions/README.md +++ b/samples/08.suggested-actions/README.md @@ -25,7 +25,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/08.suggested-actions/bin/README.md b/samples/08.suggested-actions/bin/README.md index c46553f5c..131a86b65 100644 --- a/samples/08.suggested-actions/bin/README.md +++ b/samples/08.suggested-actions/bin/README.md @@ -25,7 +25,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/08.suggested-actions/bin/src/main/webapp/index.html b/samples/08.suggested-actions/bin/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/08.suggested-actions/bin/src/main/webapp/index.html +++ b/samples/08.suggested-actions/bin/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/08.suggested-actions/src/main/resources/application.properties b/samples/08.suggested-actions/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/08.suggested-actions/src/main/resources/application.properties +++ b/samples/08.suggested-actions/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/08.suggested-actions/src/main/webapp/index.html b/samples/08.suggested-actions/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/08.suggested-actions/src/main/webapp/index.html +++ b/samples/08.suggested-actions/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/16.proactive-messages/README.md b/samples/16.proactive-messages/README.md index cccc250a4..dc35a0cee 100644 --- a/samples/16.proactive-messages/README.md +++ b/samples/16.proactive-messages/README.md @@ -34,7 +34,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/16.proactive-messages/bin/README.md b/samples/16.proactive-messages/bin/README.md index c46553f5c..131a86b65 100644 --- a/samples/16.proactive-messages/bin/README.md +++ b/samples/16.proactive-messages/bin/README.md @@ -25,7 +25,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/16.proactive-messages/bin/src/main/webapp/index.html b/samples/16.proactive-messages/bin/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/16.proactive-messages/bin/src/main/webapp/index.html +++ b/samples/16.proactive-messages/bin/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java index 185668431..033f79d35 100644 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java +++ b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java @@ -30,7 +30,7 @@ */ @Component public class ProactiveBot extends ActivityHandler { - @Value("${server.port:8080}") + @Value("${server.port:3978}") private int port; private static final String WELCOMEMESSAGE = diff --git a/samples/16.proactive-messages/src/main/resources/application.properties b/samples/16.proactive-messages/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/16.proactive-messages/src/main/resources/application.properties +++ b/samples/16.proactive-messages/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/16.proactive-messages/src/main/webapp/index.html b/samples/16.proactive-messages/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/16.proactive-messages/src/main/webapp/index.html +++ b/samples/16.proactive-messages/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/45.state-management/README.md b/samples/45.state-management/README.md index bbf7be4be..d7fda09ec 100644 --- a/samples/45.state-management/README.md +++ b/samples/45.state-management/README.md @@ -28,7 +28,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ## Deploy the bot to Azure diff --git a/samples/45.state-management/src/main/resources/application.properties b/samples/45.state-management/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/45.state-management/src/main/resources/application.properties +++ b/samples/45.state-management/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/45.state-management/src/main/webapp/index.html b/samples/45.state-management/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/45.state-management/src/main/webapp/index.html +++ b/samples/45.state-management/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/47.inspection/README.md b/samples/47.inspection/README.md index 12bb4d250..c789de873 100644 --- a/samples/47.inspection/README.md +++ b/samples/47.inspection/README.md @@ -31,12 +31,12 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p - Launch Bot Framework Emulator - File -> Open Bot - - Enter a Bot URL of `http://localhost:8080/api/messages` + - Enter a Bot URL of `http://localhost:3978/api/messages` ### Special Instructions for Running Inspection - In the emulator, select Debug -> Start Debugging. -- Enter the endpoint url (http://localhost:8080)/api/messages, and select Connect. +- Enter the endpoint url (http://localhost:3978)/api/messages, and select Connect. - The result is a trace activity which contains a statement that looks like /INSPECT attach < identifier > - Right click and copy that response. - In the original Live Chat session paste the value. diff --git a/samples/47.inspection/src/main/resources/application.properties b/samples/47.inspection/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/47.inspection/src/main/resources/application.properties +++ b/samples/47.inspection/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/47.inspection/src/main/webapp/index.html b/samples/47.inspection/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/47.inspection/src/main/webapp/index.html +++ b/samples/47.inspection/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/50.teams-messaging-extensions-search/README.md b/samples/50.teams-messaging-extensions-search/README.md index 19f166400..8a9bb5d1e 100644 --- a/samples/50.teams-messaging-extensions-search/README.md +++ b/samples/50.teams-messaging-extensions-search/README.md @@ -23,10 +23,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/50.teams-messaging-extensions-search/src/main/resources/application.properties b/samples/50.teams-messaging-extensions-search/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/50.teams-messaging-extensions-search/src/main/resources/application.properties +++ b/samples/50.teams-messaging-extensions-search/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/50.teams-messaging-extensions-search/src/main/webapp/index.html b/samples/50.teams-messaging-extensions-search/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/50.teams-messaging-extensions-search/src/main/webapp/index.html +++ b/samples/50.teams-messaging-extensions-search/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/51.teams-messaging-extensions-action/README.md b/samples/51.teams-messaging-extensions-action/README.md index 29cf1f859..c7e32a4b0 100644 --- a/samples/51.teams-messaging-extensions-action/README.md +++ b/samples/51.teams-messaging-extensions-action/README.md @@ -22,10 +22,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/51.teams-messaging-extensions-action/src/main/resources/application.properties b/samples/51.teams-messaging-extensions-action/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/resources/application.properties +++ b/samples/51.teams-messaging-extensions-action/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/51.teams-messaging-extensions-action/src/main/webapp/index.html b/samples/51.teams-messaging-extensions-action/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/webapp/index.html +++ b/samples/51.teams-messaging-extensions-action/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/52.teams-messaging-extensions-search-auth-config/README.md b/samples/52.teams-messaging-extensions-search-auth-config/README.md index d68d599e5..a0636c549 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/README.md +++ b/samples/52.teams-messaging-extensions-search-auth-config/README.md @@ -22,10 +22,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/application.properties b/samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/application.properties index ba3da1e1f..3c26e3ecc 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/application.properties +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/application.properties @@ -2,3 +2,4 @@ MicrosoftAppId= MicrosoftAppPassword= ConnectionName= SiteUrl= +server.port=3978 diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/index.html b/samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/index.html +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/53.teams-messaging-extensions-action-preview/README.md b/samples/53.teams-messaging-extensions-action-preview/README.md index 122dd4c7f..f1fb30e49 100644 --- a/samples/53.teams-messaging-extensions-action-preview/README.md +++ b/samples/53.teams-messaging-extensions-action-preview/README.md @@ -21,10 +21,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/resources/application.properties b/samples/53.teams-messaging-extensions-action-preview/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/resources/application.properties +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/webapp/index.html b/samples/53.teams-messaging-extensions-action-preview/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/webapp/index.html +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/54.teams-task-module/README.md b/samples/54.teams-task-module/README.md index 4612fca93..0c263f409 100644 --- a/samples/54.teams-task-module/README.md +++ b/samples/54.teams-task-module/README.md @@ -20,10 +20,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/54.teams-task-module/src/main/resources/application.properties b/samples/54.teams-task-module/src/main/resources/application.properties index c314a733f..f175173d2 100644 --- a/samples/54.teams-task-module/src/main/resources/application.properties +++ b/samples/54.teams-task-module/src/main/resources/application.properties @@ -1,3 +1,4 @@ MicrosoftAppId= MicrosoftAppPassword= -BaseUrl=https://YourDeployedBotUrl.com \ No newline at end of file +BaseUrl=https://YourDeployedBotUrl.com +server.port=3978 diff --git a/samples/55.teams-link-unfurling/README.md b/samples/55.teams-link-unfurling/README.md index 7a2446381..0e5c00c9f 100644 --- a/samples/55.teams-link-unfurling/README.md +++ b/samples/55.teams-link-unfurling/README.md @@ -21,10 +21,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/55.teams-link-unfurling/src/main/resources/application.properties b/samples/55.teams-link-unfurling/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/55.teams-link-unfurling/src/main/resources/application.properties +++ b/samples/55.teams-link-unfurling/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/55.teams-link-unfurling/src/main/webapp/index.html b/samples/55.teams-link-unfurling/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/55.teams-link-unfurling/src/main/webapp/index.html +++ b/samples/55.teams-link-unfurling/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/56.teams-file-upload/README.md b/samples/56.teams-file-upload/README.md index 04d4a14e1..1ad6d5cea 100644 --- a/samples/56.teams-file-upload/README.md +++ b/samples/56.teams-file-upload/README.md @@ -22,10 +22,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/56.teams-file-upload/src/main/resources/application.properties b/samples/56.teams-file-upload/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/56.teams-file-upload/src/main/resources/application.properties +++ b/samples/56.teams-file-upload/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/56.teams-file-upload/src/main/webapp/index.html b/samples/56.teams-file-upload/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/56.teams-file-upload/src/main/webapp/index.html +++ b/samples/56.teams-file-upload/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/57.teams-conversation-bot/README.md b/samples/57.teams-conversation-bot/README.md index 5c35b5652..928127c5e 100644 --- a/samples/57.teams-conversation-bot/README.md +++ b/samples/57.teams-conversation-bot/README.md @@ -22,10 +22,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/57.teams-conversation-bot/src/main/resources/application.properties b/samples/57.teams-conversation-bot/src/main/resources/application.properties index a695b3bf0..d7d0ee864 100644 --- a/samples/57.teams-conversation-bot/src/main/resources/application.properties +++ b/samples/57.teams-conversation-bot/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId= MicrosoftAppPassword= +server.port=3978 diff --git a/samples/57.teams-conversation-bot/src/main/webapp/index.html b/samples/57.teams-conversation-bot/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/57.teams-conversation-bot/src/main/webapp/index.html +++ b/samples/57.teams-conversation-bot/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure diff --git a/samples/58.teams-start-new-thread-in-channel/README.md b/samples/58.teams-start-new-thread-in-channel/README.md index 04bc249c8..4e694305d 100644 --- a/samples/58.teams-start-new-thread-in-channel/README.md +++ b/samples/58.teams-start-new-thread-in-channel/README.md @@ -21,10 +21,10 @@ the Teams service needs to call into the bot. git clone https://github.com/Microsoft/botbuilder-java.git ``` -1) Run ngrok - point to port 8080 +1) Run ngrok - point to port 3978 ```bash - ngrok http -host-header=rewrite 8080 + ngrok http -host-header=rewrite 3978 ``` 1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure diff --git a/samples/58.teams-start-new-thread-in-channel/src/main/resources/application.properties b/samples/58.teams-start-new-thread-in-channel/src/main/resources/application.properties index 161bdddb8..d42a67c8d 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/main/resources/application.properties +++ b/samples/58.teams-start-new-thread-in-channel/src/main/resources/application.properties @@ -1,2 +1,3 @@ MicrosoftAppId = MicrosoftAppPassword = +server.port=3978 diff --git a/samples/58.teams-start-new-thread-in-channel/src/main/webapp/index.html b/samples/58.teams-start-new-thread-in-channel/src/main/webapp/index.html index 40b74c007..d5ba5158e 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/main/webapp/index.html +++ b/samples/58.teams-start-new-thread-in-channel/src/main/webapp/index.html @@ -399,7 +399,7 @@
Your bot is ready!
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:8080/api/messages.
+ by connecting to http://localhost:3978/api/messages.
Visit Azure From 24c2769294f41679874dcd3c370cb9779cee662c Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 13 Nov 2020 11:06:46 -0600 Subject: [PATCH 025/221] Implemented HealthCheck feature (#840) * Implemented HealthCheck feature * Removed test code that was commented out --- .../bot/builder/ActivityHandler.java | 18 +++ .../microsoft/bot/builder/HealthCheck.java | 51 +++++++ .../bot/builder/ActivityHandlerTests.java | 106 +++++++++++++ .../bot/builder/MockAppCredentials.java | 36 +++++ .../bot/builder/MockConnectorClient.java | 86 +++++++++++ .../bot/schema/HealthCheckResponse.java | 40 +++++ .../microsoft/bot/schema/HealthResults.java | 139 ++++++++++++++++++ 7 files changed, 476 insertions(+) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index 6a451e17d..7946e8f7b 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -10,9 +10,11 @@ import org.apache.commons.lang3.StringUtils; +import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.HealthCheckResponse; import com.microsoft.bot.schema.MessageReaction; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.SignInConstants; @@ -405,6 +407,10 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon } return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null); }); + } else if (StringUtils.equals(turnContext.getActivity().getName(), "healthCheck")) { + CompletableFuture result = new CompletableFuture<>(); + result.complete(new InvokeResponse(HttpURLConnection.HTTP_OK, onHealthCheck(turnContext))); + return result; } CompletableFuture result = new CompletableFuture<>(); @@ -436,6 +442,18 @@ protected CompletableFuture onSignInInvoke(TurnContext turnContext) { return result; } + /** + * Invoked when a 'healthCheck' event is + * received when the base behavior of onInvokeActivity is used. + * + * @param turnContext The current TurnContext. + * @return A task that represents a HealthCheckResponse. + */ + protected CompletableFuture onHealthCheck(TurnContext turnContext) { + ConnectorClient client = turnContext.getTurnState().get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY); + return CompletableFuture.completedFuture(HealthCheck.createHealthCheckResponse(client)); + } + /** * Creates a success InvokeResponse with the specified body. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java new file mode 100644 index 000000000..3fe4ef890 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.ExecutionException; + +import com.microsoft.bot.connector.ConnectorClient; +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.schema.HealthCheckResponse; +import com.microsoft.bot.schema.HealthResults; + +/** + * A class to process a HealthCheck request. + */ +public final class HealthCheck { + + private HealthCheck() { + // not called + } + + /** + * @param connector the ConnectorClient instance for this request + * @return HealthCheckResponse + */ + public static HealthCheckResponse createHealthCheckResponse(ConnectorClient connector) { + HealthResults healthResults = new HealthResults(); + healthResults.setSuccess(true); + + if (connector != null) { + healthResults.setUserAgent(connector.getUserAgent()); + AppCredentials credentials = (AppCredentials) connector.credentials(); + try { + healthResults.setAuthorization(credentials.getToken().get()); + } catch (InterruptedException | ExecutionException ignored) { + // An exception here may happen when you have a valid appId but invalid or blank secret. + // No callbacks will be possible, although the bot maybe healthy in other respects. + } + } + + if (healthResults.getAuthorization() != null) { + healthResults.setMessages(new String[]{"Health check succeeded."}); + } else { + healthResults.setMessages(new String[]{"Health check succeeded.", "Callbacks are not authorized."}); + } + + HealthCheckResponse response = new HealthCheckResponse(); + response.setHealthResults(healthResults); + return response; + } +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 6cf214870..6b6d7590f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -7,6 +7,8 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.atomic.AtomicReference; public class ActivityHandlerTests { @Test @@ -346,6 +348,98 @@ public void TestUnrecognizedActivityType() { Assert.assertEquals("onUnrecognizedActivityType", bot.getRecord().get(0)); } + @Test + public void TestHealthCheckAsyncOverride() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INVOKE); + setName("healthCheck"); + } + }; + + TurnContext turnContext = new TurnContextImpl(new TestInvokeAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInvokeActivity", bot.getRecord().get(0)); + Assert.assertEquals("onHealthCheck", bot.getRecord().get(1)); + } + + @Test + public void TestHealthCheckAsync() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INVOKE); + setName("healthCheck"); + } + }; + + AtomicReference> activitiesToSend = new AtomicReference<>(); + TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity); + + ActivityHandler bot = new ActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertNotNull(activitiesToSend.get()); + Assert.assertEquals(1, activitiesToSend.get().size()); + Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse); + Assert.assertEquals(200, ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus()); + CompletableFuture future = ((CompletableFuture) ((InvokeResponse) activitiesToSend.get().get(0).getValue()) + .getBody()); + HealthCheckResponse result = new HealthCheckResponse(); + result = (HealthCheckResponse) future.join(); + Assert.assertTrue(result.getHealthResults().getSuccess()); + String[] messages = result.getHealthResults().getMessages(); + Assert.assertEquals(messages[0], "Health check succeeded."); + } + + @Test + public void TestHealthCheckWithConnectorAsync() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INVOKE); + setName("healthCheck"); + } + }; + + AtomicReference> activitiesToSend = new AtomicReference<>(); + TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity); + MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome")); + turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, mockConnector); + ActivityHandler bot = new ActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertNotNull(activitiesToSend.get()); + Assert.assertEquals(1, activitiesToSend.get().size()); + Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse); + Assert.assertEquals( + 200, + ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus() + ); + CompletableFuture future = + ((CompletableFuture) + ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getBody()); + HealthCheckResponse result = new HealthCheckResponse(); + result = (HealthCheckResponse) future.join(); + Assert.assertTrue(result.getHealthResults().getSuccess()); + Assert.assertEquals(result.getHealthResults().getAuthorization(), "awesome"); + Assert.assertEquals(result.getHealthResults().getUserAgent(), "Windows/3.1"); + String[] messages = result.getHealthResults().getMessages(); + Assert.assertEquals(messages[0], "Health check succeeded."); + } + + private static class TestInvokeAdapter extends NotImplementedAdapter { + @Override + public CompletableFuture sendActivities( + TurnContext context, + List activities + ) { + return CompletableFuture.completedFuture(new ResourceResponse[0]); + } + } + private static class NotImplementedAdapter extends BotAdapter { @Override public CompletableFuture sendActivities( @@ -455,6 +549,18 @@ protected CompletableFuture onEvent(TurnContext turnContext) { return super.onEvent(turnContext); } + @Override + protected CompletableFuture onInvokeActivity(TurnContext turnContext) { + record.add("onInvokeActivity"); + return super.onInvokeActivity(turnContext); + } + + @Override + protected CompletableFuture onHealthCheck(TurnContext turnContext) { + record.add("onHealthCheck"); + return super.onHealthCheck(turnContext); + } + @Override protected CompletableFuture onInstallationUpdate(TurnContext turnContext) { record.add("onInstallationUpdate"); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java new file mode 100644 index 000000000..fe949002e --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java @@ -0,0 +1,36 @@ +package com.microsoft.bot.builder; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.connector.authentication.Authenticator; + +public class MockAppCredentials extends AppCredentials { + + private String token; + + public MockAppCredentials(String token) { + super(null); + this.token = token; + } + + @Override + public CompletableFuture getToken() { + CompletableFuture result; + + result = new CompletableFuture(); + result.complete(this.token); + return result; + } + + /** + * Returns an appropriate Authenticator that is provided by a subclass. + * + * @return An Authenticator object. + * @throws MalformedURLException If the endpoint isn't valid. + */ + protected Authenticator buildAuthenticator(){ + return null; + }; + +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java new file mode 100644 index 000000000..cda3dd92f --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java @@ -0,0 +1,86 @@ +package com.microsoft.bot.builder; + +import com.microsoft.bot.connector.Attachments; +import com.microsoft.bot.connector.ConnectorClient; +import com.microsoft.bot.connector.Conversations; +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.rest.RestClient; +import com.microsoft.bot.rest.credentials.ServiceClientCredentials; + +public class MockConnectorClient implements ConnectorClient { + + private AppCredentials credentials; + private String userAgent; + + public MockConnectorClient(String userAgent, AppCredentials credentials) { + this.userAgent = userAgent; + this.credentials = credentials; + } + + private MemoryConversations conversations = new MemoryConversations(); + + @Override + public RestClient getRestClient() { + return null; + } + + @Override + public String baseUrl() { + return null; + } + + @Override + public ServiceClientCredentials credentials() { + return credentials; + } + + @Override + public String getUserAgent() { + return userAgent; + } + + @Override + public String getAcceptLanguage() { + return null; + } + + @Override + public void setAcceptLanguage(String acceptLanguage) { + + } + + @Override + public int getLongRunningOperationRetryTimeout() { + return 0; + } + + @Override + public void setLongRunningOperationRetryTimeout(int timeout) { + + } + + @Override + public boolean getGenerateClientRequestId() { + return false; + } + + @Override + public void setGenerateClientRequestId(boolean generateClientRequestId) { + + } + + @Override + public Attachments getAttachments() { + return null; + } + + @Override + public Conversations getConversations() { + return conversations; + } + + @Override + public void close() throws Exception { + + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java new file mode 100644 index 000000000..caf9d49af --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that is returned as the result of a health check on the bot. + * The health check is sent to the bot as an {@link Activity} of type "invoke" and this class along + * with {@link HealthResults} defines the structure of the body of the response. + * The name of the invoke Activity is "healthCheck". + */ +public class HealthCheckResponse { + /** + * The health check results. + */ + @JsonProperty(value = "healthResults") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private HealthResults healthResults; + + /** + * Gets the healthResults value. + * + * @return The healthResults value. + */ + public HealthResults getHealthResults() { + return this.healthResults; + } + + /** + * Sets the healthResults value. + * + * @param withHealthResults The healthResults value to set. + */ + public void setHealthResults(HealthResults withHealthResults) { + this.healthResults = withHealthResults; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java new file mode 100644 index 000000000..2e727cc00 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java @@ -0,0 +1,139 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that is returned as the result of a health check on the bot. + * The health check is sent to the bot as an InvokeActivity and this class along with {@link HealthCheckResponse} + * defines the structure of the body of the response. + */ +public class HealthResults { + /** + * A value indicating whether the health check has succeeded and the bot is healthy. + */ + @JsonProperty(value = "success") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Boolean success; + + /** + * A value that is exactly the same as the Authorization header that would have been added to an HTTP POST back. + */ + @JsonProperty(value = "authorization") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String authorization; + + /** + * A value that is exactly the same as the User-Agent header that would have been added to an HTTP POST back. + */ + @JsonProperty(value = "user-agent") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String userAgent; + + /** + * Informational messages that can be optionally included in the health check response. + */ + @JsonProperty(value = "messages") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String[] messages; + + /** + * Diagnostic data that can be optionally included in the health check response. + */ + @JsonProperty(value = "diagnostics") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Object diagnostics; + + /** + * Gets the success value. + * + * @return The success value. + */ + public Boolean getSuccess() { + return this.success; + } + + /** + * Sets the success value. + * + * @param withSuccess The success value to set. + */ + public void setSuccess(Boolean withSuccess) { + this.success = withSuccess; + } + + /** + * Get the authorization value. + * + * @return the authorization value + */ + public String getAuthorization() { + return this.authorization; + } + + /** + * Set the authorization value. + * + * @param withAuthorization the authization value to set + */ + public void setAuthorization(String withAuthorization) { + this.authorization = withAuthorization; + } + + /** + * Get the userAgent value. + * + * @return the userAgent value + */ + public String getUserAgent() { + return this.userAgent; + } + + /** + * Set the userAgent value. + * + * @param withUserAgent the userAgent value to set + */ + public void setUserAgent(String withUserAgent) { + this.userAgent = withUserAgent; + } + + /** + * Get the messages value. + * + * @return the messages value + */ + public String[] getMessages() { + return this.messages; + } + + /** + * Set the messages value. + * + * @param withMessages the messages value to set + */ + public void setMessages(String[] withMessages) { + this.messages = withMessages; + } + + /** + * Get the diagnostics value. + * + * @return the diagnostics value + */ + public Object getDiagnostics() { + return this.diagnostics; + } + + /** + * Set the diagnostics value. + * + * @param withDiagnostics the diagnostics value to set + */ + public void setDiagnostics(Object withDiagnostics) { + this.diagnostics = withDiagnostics; + } +} From 42814b0e944cffbf0523be9d7d37d3147320b576 Mon Sep 17 00:00:00 2001 From: vini-desouza <59612929+vini-desouza@users.noreply.github.com> Date: Mon, 30 Nov 2020 21:39:06 +0000 Subject: [PATCH 026/221] bugfix - Change the fetchChannelList from POST to GET (#844) --- .../com/microsoft/bot/connector/rest/RestTeamsOperations.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java index 5d79d6f4f..9f5210fd9 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java @@ -166,7 +166,7 @@ private ServiceResponse fetchParticipantDelegate( interface TeamsService { @Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.Teams fetchChannelList" }) - @POST("v3/teams/{teamId}/conversations") + @GET("v3/teams/{teamId}/conversations") CompletableFuture> fetchChannelList( @Path("teamId") String teamId, @Header("accept-language") String acceptLanguage, From 466dc6d9ccf813e23cafc445a31dafe54a95f87f Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 2 Dec 2020 10:43:37 -0600 Subject: [PATCH 027/221] Updated to latest Spring (#845) --- libraries/bot-integration-spring/pom.xml | 10 +++++----- samples/02.echo-bot/pom.xml | 4 ++-- samples/03.welcome-user/pom.xml | 4 ++-- samples/08.suggested-actions/bin/pom.xml | 2 +- samples/08.suggested-actions/pom.xml | 4 ++-- samples/16.proactive-messages/bin/pom.xml | 2 +- samples/16.proactive-messages/pom.xml | 4 ++-- samples/45.state-management/pom.xml | 4 ++-- samples/47.inspection/pom.xml | 4 ++-- samples/50.teams-messaging-extensions-search/pom.xml | 4 ++-- samples/51.teams-messaging-extensions-action/pom.xml | 4 ++-- .../pom.xml | 4 ++-- .../pom.xml | 4 ++-- samples/54.teams-task-module/pom.xml | 4 ++-- samples/55.teams-link-unfurling/pom.xml | 4 ++-- samples/56.teams-file-upload/pom.xml | 4 ++-- samples/57.teams-conversation-bot/pom.xml | 4 ++-- samples/58.teams-start-new-thread-in-channel/pom.xml | 4 ++-- 18 files changed, 37 insertions(+), 37 deletions(-) diff --git a/libraries/bot-integration-spring/pom.xml b/libraries/bot-integration-spring/pom.xml index 96ec043d1..fa11f01cc 100644 --- a/libraries/bot-integration-spring/pom.xml +++ b/libraries/bot-integration-spring/pom.xml @@ -56,28 +56,28 @@ org.springframework.boot spring-boot-starter-web - 2.1.8.RELEASE + 2.4.0 org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test org.springframework spring-core - 5.1.9.RELEASE + 5.3.1 org.springframework spring-beans - 5.1.9.RELEASE + 5.3.1 org.springframework.boot spring-boot - 2.1.8.RELEASE + 2.4.0 compile diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index f9a1939bf..2cf05cccf 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 0fb01da59..7f6fbb4a0 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -51,7 +51,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/08.suggested-actions/bin/pom.xml b/samples/08.suggested-actions/bin/pom.xml index 9c779a336..6900cba5e 100644 --- a/samples/08.suggested-actions/bin/pom.xml +++ b/samples/08.suggested-actions/bin/pom.xml @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index d1360ada3..0f73b9ba3 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -51,7 +51,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/16.proactive-messages/bin/pom.xml b/samples/16.proactive-messages/bin/pom.xml index 9c779a336..6900cba5e 100644 --- a/samples/16.proactive-messages/bin/pom.xml +++ b/samples/16.proactive-messages/bin/pom.xml @@ -15,7 +15,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index c598b6a91..c314a2710 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -51,7 +51,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 32c20d122..e1f3b0050 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -50,7 +50,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index cc29f2371..043ce9008 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index b5c9fe9ae..6a02d2758 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index 8af7b6e7b..f392538a6 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index ff0760822..005b63303 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -62,7 +62,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index 8fcc80646..c83dcd358 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index 68a119d90..f58521655 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index d266c7e7d..e2ed87974 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index f576403c3..07d2a7436 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index 8162a848c..4364d13e7 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index 12f410598..d36164f28 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -17,7 +17,7 @@ org.springframework.boot spring-boot-starter-parent - 2.1.7.RELEASE + 2.4.0 @@ -57,7 +57,7 @@ org.springframework.boot spring-boot-starter-test - 2.1.8.RELEASE + 2.4.0 test From baeb2259706aa129d459c8dbad06c07e6f90cbc3 Mon Sep 17 00:00:00 2001 From: Andrew Clear <1139814+clearab@users.noreply.github.com> Date: Wed, 2 Dec 2020 10:04:56 -0800 Subject: [PATCH 028/221] Update manifest.json (#846) "message" is not valid for search command --- .../teamsAppManifest/manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/manifest.json b/samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/manifest.json index 610027187..c8e763d24 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/manifest.json +++ b/samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/manifest.json @@ -37,8 +37,7 @@ "fetchTask": false, "context": [ "commandBox", - "compose", - "message" + "compose" ], "parameters": [ { From 3a2d872142ee4b9631be5c04cf83a87fcfee9b24 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 2 Dec 2020 12:48:37 -0600 Subject: [PATCH 029/221] Updated to latest msal4j (#847) --- .../bot/connector/authentication/CachingOpenIdMetadata.java | 4 ++-- .../connector/authentication/CredentialsAuthenticator.java | 2 +- pom.xml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java index 3b2587001..336c5fa71 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java @@ -73,10 +73,10 @@ private void refreshCache() { try { URL openIdUrl = new URL(this.url); - HashMap openIdConf = + HashMap openIdConf = this.mapper.readValue(openIdUrl, new TypeReference>() { }); - URL keysUrl = new URL(openIdConf.get("jwks_uri")); + URL keysUrl = new URL(openIdConf.get("jwks_uri").toString()); lastUpdated = System.currentTimeMillis(); UrlJwkProvider provider = new UrlJwkProvider(keysUrl); keyCache = provider.getAll().stream().collect(Collectors.toMap(Jwk::getId, jwk -> jwk)); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java index 504c117df..48edc6d4b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java @@ -28,7 +28,7 @@ public class CredentialsAuthenticator implements Authenticator { throws MalformedURLException { app = ConfidentialClientApplication - .builder(appId, ClientCredentialFactory.create(appPassword)) + .builder(appId, ClientCredentialFactory.createFromSecret(appPassword)) .authority(configuration.getAuthority()) .build(); diff --git a/pom.xml b/pom.xml index 75b63485c..eafca7cbe 100644 --- a/pom.xml +++ b/pom.xml @@ -229,7 +229,7 @@ com.microsoft.azure msal4j - 0.5.0-preview + 1.8.0 From 4f24d95458b9558d499b90babe1cca3a2844f59b Mon Sep 17 00:00:00 2001 From: BruceHaley Date: Thu, 3 Dec 2020 12:23:43 -0800 Subject: [PATCH 030/221] Update devops profile in pom.xml (#848) --- pom.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/pom.xml b/pom.xml index eafca7cbe..79de32ce1 100644 --- a/pom.xml +++ b/pom.xml @@ -147,6 +147,19 @@ org.codehaus.mojo cobertura-maven-plugin + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + From ea18ab6529487a30ce556524a22efbceb498009a Mon Sep 17 00:00:00 2001 From: BruceHaley Date: Fri, 4 Dec 2020 08:17:47 -0800 Subject: [PATCH 031/221] Update pom.xml for ConversationalAI feed deploys (#849) * Update pom.xml for ConversationalAI feed deploys * Fix distributionManagement entries * Insert ${repo.id} * Fix defaults for repo.id and repo.url * Try adding releases and snapshots items --- .gitignore | 8 ++++++++ pom.xml | 13 +++++++++++-- 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index ce2912fb5..660bbe29e 100644 --- a/.gitignore +++ b/.gitignore @@ -58,3 +58,11 @@ dependency-reduced-pom.xml .vscode/settings.json pom.xml.versionsBackup libraries/swagger/generated +/javabotframework_pub.gpg +/javabotframework_sec.gpg +/private.pgp +/privatekey.txt +/public.pgp +/.vs/slnx.sqlite +/.vs/botbuilder-java/v16/.suo +/.vs/ProjectSettings.json diff --git a/pom.xml b/pom.xml index 79de32ce1..c36db0cb2 100644 --- a/pom.xml +++ b/pom.xml @@ -20,6 +20,7 @@ 1.8 3.1.0 3.12.0 + MyGet https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -361,8 +362,14 @@ - MyGet + ${repo.id} ${repo.url} + + true + + + true + @@ -370,7 +377,9 @@ - MyGet + + + ${repo.id} ${repo.url} From ecbacc8b6414e40d0d56ffc298e27fa77b9758af Mon Sep 17 00:00:00 2001 From: BruceHaley Date: Fri, 4 Dec 2020 10:56:25 -0800 Subject: [PATCH 032/221] Add ConversationalAI to settings.xml (#850) --- settings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/settings.xml b/settings.xml index 6feaf8f5d..8b26454e0 100644 --- a/settings.xml +++ b/settings.xml @@ -137,6 +137,12 @@ under the License. ${repo.username} ${repo.password} + + ConversationalAI + + ${repo.username} + ${repo.password} + + ${repo.username} + ${repo.password} + - https://oss.sonatype.org/ - - - - build @@ -107,14 +88,16 @@ true + + + src/main/resources + false + + maven-compiler-plugin 3.8.1 - - 1.8 - 1.8 - maven-war-plugin @@ -168,51 +151,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 - - @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 7f6fbb4a0..f9ebd8cd0 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.welcomeuser.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.welcomeuser.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.welcomeuser.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 0f73b9ba3..26774790a 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.suggestedactions.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.suggestedactions.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.suggestedactions.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index c314a2710..2c38c8c8b 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.proactive.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.proactive.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.proactive.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index e1f3b0050..6b6809f6a 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -38,12 +38,9 @@ - UTF-8 - UTF-8 1.8 1.8 com.microsoft.bot.sample.statemanagement.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -83,22 +80,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -106,113 +87,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.statemanagement.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-statemanagement-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.statemanagement.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -226,8 +164,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -288,27 +229,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index 043ce9008..6f0912c2b 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.inspection.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.inspection.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-inspection-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.inspection.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index 6a02d2758..a2ea70374 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamssearch.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -99,22 +96,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -122,113 +103,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamssearch.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamssearch-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamssearch.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -242,8 +180,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -304,27 +245,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index f392538a6..1e176b13f 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsaction.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamsaction.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamsaction-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsaction.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index 005b63303..d477d6420 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamssearchauth.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -95,136 +92,77 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - - + build true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamssearchauth.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamssearchauth-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamssearchauth.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -238,8 +176,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -300,27 +241,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index c83dcd358..ac9a5c38b 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsactionpreview.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamsactionpreview.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamsactionpreview-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsactionpreview.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index f58521655..b8b781653 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamstaskmodule.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -92,22 +89,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -115,113 +96,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamstaskmodule.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamstaskmodule-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamstaskmodule.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -235,8 +173,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -297,27 +238,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index e2ed87974..43750d1b2 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsunfurl.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamsunfurl.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamsunfurl-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsunfurl.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index 07d2a7436..b9c97ec68 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsfileupload.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamsfileupload.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamsfileupload-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsfileupload.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index 4364d13e7..dff36819f 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsconversation.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -84,22 +81,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -107,113 +88,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamsconversation.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamsconversation-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsconversation.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -227,8 +165,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -289,27 +230,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index d36164f28..3a79037ed 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -38,13 +38,10 @@ - UTF-8 - UTF-8 1.8 1.8 1.8 com.microsoft.bot.sample.teamsstartnewthread.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -87,22 +84,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -110,113 +91,70 @@ true - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.teamstaskmodule.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-teamstaskmodule-sample - xml - 256m - - true - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 - - + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamstaskmodule.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + {groupname} + {botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + @@ -230,8 +168,11 @@ maven-compiler-plugin - org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 3.2.3 + + src/main/webapp + @@ -292,27 +233,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index 0d95ad232..ae1d05dbc 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -30,11 +30,9 @@ - UTF-8 false 1.8 1.8 - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ @@ -93,22 +91,6 @@ - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - build @@ -116,94 +98,59 @@ true - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.8 - 1.8 - - - - org.apache.maven.plugins - maven-war-plugin - 2.1 - - false - src/main/webapp - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - windows - 1.8 - tomcat 9.0 - - - - - ${project.basedir}/target - - *.war - - - - - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/servlet-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - + + + src/main/resources + false + + + + org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - + maven-compiler-plugin + 3.7.0 + + org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 - - - + maven-war-plugin + 2.1 + + false + src/main/webapp + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + windows + 1.8 + tomcat 9.0 + + + + + ${project.basedir}/target + + *.war + + + + + + + @@ -218,7 +165,12 @@ org.apache.maven.plugins - maven-jar-plugin + maven-war-plugin + 2.1 + + false + src/main/webapp + @@ -279,27 +231,4 @@ - - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - org.apache.maven.plugins - maven-checkstyle-plugin - 3.1.0 - - - - checkstyle - - - - - - - From b2900e426cbb1047e9c0c996cd511dd5973dca0c Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 14 Dec 2020 08:03:30 -0600 Subject: [PATCH 036/221] Echo Generator (#856) * Echo Generator * Generator project cleanup * Added Empty template for generator. Updated README * Generator review changes * Renamed Generator template "tree" folder to "project" * Generator review changes, fixed bug with spaces in artifactId set in POM * Corrected units tests not running in generator templates --- .../generator-botbuilder-java/.editorconfig | 11 - .../generator-botbuilder-java/.eslintignore | 2 - .../generator-botbuilder-java/.gitattributes | 1 - .../generator-botbuilder-java/.travis.yml | 7 - .../generator-botbuilder-java/.yo-rc.json | 9 - .../{LICENSE => LICENSE.md} | 12 +- Generator/generator-botbuilder-java/README.md | 126 +- .../__tests__/app.js | 16 - .../generators/app/index.js | 286 +- .../generators/app/templates/README.md | 37 - .../generators/app/templates/app.java | 95 - .../app/templates/echo/project/README.md | 85 + .../new-rg-parameters.json | 43 + .../preexisting-rg-parameters.json | 40 + .../template-with-new-rg.json | 204 + .../template-with-preexisting-rg.json | 173 + .../app/templates/echo/project/pom.xml | 253 + .../src/main/resources/application.properties | 3 + .../project/src/main/resources/log4j2.json | 21 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../project/src/main/webapp/WEB-INF/web.xml | 12 + .../echo/project/src/main/webapp/index.html | 418 + .../echo/src/main/java/Application.java | 48 + .../templates/echo/src/main/java/EchoBot.java | 48 + .../echo/src/test/java/ApplicationTest.java | 19 + .../app/templates/empty/project/README.md | 85 + .../new-rg-parameters.json | 43 + .../preexisting-rg-parameters.json | 40 + .../template-with-new-rg.json | 204 + .../template-with-preexisting-rg.json | 173 + .../app/templates/empty/project/pom.xml | 253 + .../src/main/resources/application.properties | 3 + .../project/src/main/resources/log4j2.json | 21 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../project/src/main/webapp/WEB-INF/web.xml | 12 + .../empty/project/src/main/webapp/index.html | 418 + .../empty/src/main/java/Application.java | 48 + .../empty/src/main/java/EmptyBot.java | 28 + .../empty/src/test/java/ApplicationTest.java | 19 + .../generators/app/templates/pom.xml | 89 - .../package-lock.json | 9547 ++++------------- .../generator-botbuilder-java/package.json | 112 +- 42 files changed, 5176 insertions(+), 7894 deletions(-) delete mode 100644 Generator/generator-botbuilder-java/.editorconfig delete mode 100644 Generator/generator-botbuilder-java/.eslintignore delete mode 100644 Generator/generator-botbuilder-java/.gitattributes delete mode 100644 Generator/generator-botbuilder-java/.travis.yml delete mode 100644 Generator/generator-botbuilder-java/.yo-rc.json rename Generator/generator-botbuilder-java/{LICENSE => LICENSE.md} (87%) delete mode 100644 Generator/generator-botbuilder-java/__tests__/app.js delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/README.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/app.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/new-rg-parameters.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTest.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/new-rg-parameters.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTest.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/pom.xml diff --git a/Generator/generator-botbuilder-java/.editorconfig b/Generator/generator-botbuilder-java/.editorconfig deleted file mode 100644 index beffa3084..000000000 --- a/Generator/generator-botbuilder-java/.editorconfig +++ /dev/null @@ -1,11 +0,0 @@ -root = true - -[*] -indent_style = space -indent_size = 2 -charset = utf-8 -trim_trailing_whitespace = true -insert_final_newline = true - -[*.md] -trim_trailing_whitespace = false diff --git a/Generator/generator-botbuilder-java/.eslintignore b/Generator/generator-botbuilder-java/.eslintignore deleted file mode 100644 index 515dfdf4f..000000000 --- a/Generator/generator-botbuilder-java/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage -**/templates diff --git a/Generator/generator-botbuilder-java/.gitattributes b/Generator/generator-botbuilder-java/.gitattributes deleted file mode 100644 index 176a458f9..000000000 --- a/Generator/generator-botbuilder-java/.gitattributes +++ /dev/null @@ -1 +0,0 @@ -* text=auto diff --git a/Generator/generator-botbuilder-java/.travis.yml b/Generator/generator-botbuilder-java/.travis.yml deleted file mode 100644 index 335ea2d0a..000000000 --- a/Generator/generator-botbuilder-java/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js -node_js: - - v10 - - v8 - - v6 - - v4 -after_script: cat ./coverage/lcov.info | coveralls diff --git a/Generator/generator-botbuilder-java/.yo-rc.json b/Generator/generator-botbuilder-java/.yo-rc.json deleted file mode 100644 index 0904532be..000000000 --- a/Generator/generator-botbuilder-java/.yo-rc.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "generator-node": { - "promptValues": { - "authorName": "Microsoft", - "authorEmail": "", - "authorUrl": "" - } - } -} \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/LICENSE b/Generator/generator-botbuilder-java/LICENSE.md similarity index 87% rename from Generator/generator-botbuilder-java/LICENSE rename to Generator/generator-botbuilder-java/LICENSE.md index 08ea44557..506ab97e5 100644 --- a/Generator/generator-botbuilder-java/LICENSE +++ b/Generator/generator-botbuilder-java/LICENSE.md @@ -1,6 +1,6 @@ -The MIT License (MIT) +MIT License -Copyright (c) 2018 Microsoft +Copyright (c) 2018 Microsoft Corporation Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -9,13 +9,13 @@ 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 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. +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/Generator/generator-botbuilder-java/README.md b/Generator/generator-botbuilder-java/README.md index 4796cad67..9ab728d91 100644 --- a/Generator/generator-botbuilder-java/README.md +++ b/Generator/generator-botbuilder-java/README.md @@ -1,38 +1,120 @@ -# generator-botbuilder-java [![NPM version][npm-image]][npm-url] [![Build Status][travis-image]][travis-url] [![Dependency Status][daviddm-image]][daviddm-url] [![Coverage percentage][coveralls-image]][coveralls-url] -> Template to create conversational bots in Java using Microsoft Bot Framework. +# generator-botbuilder-java + +Yeoman generator for [Bot Framework v4](https://dev.botframework.com). Will let you quickly set up a conversational AI bot +using core AI capabilities. + +## About + +`generator-botbuilder-java` will help you build new conversational AI bots using the [Bot Framework v4](https://dev.botframework.com). + +## Templates + +The generator supports three different template options. The table below can help guide which template is right for you. + +| Template | Description | +| ---------- | --------- | +| Echo Bot | A good template if you want a little more than "Hello World!", but not much more. This template handles the very basics of sending messages to a bot, and having the bot process the messages by repeating them back to the user. This template produces a bot that simply "echoes" back to the user anything the user says to the bot. | +| Empty Bot | A good template if you are familiar with Bot Framework v4, and simply want a basic skeleton project. Also a good option if you want to take sample code from the documentation and paste it into a minimal bot in order to learn. | + +### How to Choose a Template + +| Template | When This Template is a Good Choice | +| -------- | -------- | +| Echo Bot | You are new to Bot Framework v4 and want a working bot with minimal features. | +| Empty Bot | You are a seasoned Bot Framework v4 developer. You've built bots before, and want the minimum skeleton of a bot. | + +### Template Overview + +#### Echo Bot Template + +The Echo Bot template is slightly more than the a classic "Hello World!" example, but not by much. This template shows the basic structure of a bot, how a bot recieves messages from a user, and how a bot sends messages to a user. The bot will "echo" back to the user, what the user says to the bot. It is a good choice for first time, new to Bot Framework v4 developers. + +#### Empty Bot Template + +The Empty Bot template is the minimal skeleton code for a bot. It provides a stub `onTurn` handler but does not perform any actions. If you are experienced writing bots with Bot Framework v4 and want the minimum scaffolding, the Empty template is for you. ## Installation -First, install [Yeoman](http://yeoman.io) and generator-botbuilder-java using [npm](https://www.npmjs.com/) (we assume you have pre-installed [node.js](https://nodejs.org/)). +1. Install [Yeoman](http://yeoman.io) using [npm](https://www.npmjs.com) (we assume you have pre-installed [node.js](https://nodejs.org/)). + + ```bash + # Make sure both are installed globally + npm install -g yo + ``` + +2. Install generator-botbuilder-java by typing the following in your console: + + ```bash + # Make sure both are installed globally + npm install -g generator-botbuilder-java + ``` + +3. Verify that Yeoman and generator-botbuilder-java have been installed correctly by typing the following into your console: + + ```bash + yo botbuilder-java --help + ``` + +## Usage + +### Creating a New Bot Project + +When the generator is launched, it will prompt for the information required to create a new bot. ```bash -npm install -g yo -npm install -g generator-botbuilder-java +# Run the generator in interactive mode +yo botbuilder-java ``` -Then generate your new project: +### Generator Command Line Options + +The generator supports a number of command line options that can be used to change the generator's default options or to pre-seed a prompt. + +| Command line Option | Description | +| ------------------- | ----------- | +| --help, -h | List help text for all supported command-line options | +| --botName, -N | The name given to the bot project | +| --packageName, -P | The Java package name to use for the bot | +| --template, -T | The template used to generate the project. Options are `empty`, or `echo`. See [https://aka.ms/botbuilder-generator](https://aka.ms/botbuilder-generator) for additional information regarding the different template option and their functional differences. | +| --noprompt | The generator will not prompt for confirmation before creating a new bot. Any requirement options not passed on the command line will use a reasonable default value. This option is intended to enable automated bot generation for testing purposes. | + +#### Example Using Command Line Options + +This example shows how to pass command line options to the generator, setting the default language to TypeScript and the default template to Core. ```bash -yo botbuilder-java +# Run the generator defaulting the pacakge name and the template +yo botbuilder-java --P "com.mycompany.bot" --T "echo" ``` -## Getting To Know Yeoman +### Generating a Bot Using --noprompt - * Yeoman has a heart of gold. - * Yeoman is a person with feelings and opinions, but is very easy to work with. - * Yeoman can be too opinionated at times but is easily convinced not to be. - * Feel free to [learn more about Yeoman](http://yeoman.io/). +The generator can be run in `--noprompt` mode, which can be used for automated bot creation. When run in `--noprompt` mode, the generator can be configured using command line options as documented above. If a command line option is ommitted a reasonable default will be used. In addition, passing the `--noprompt` option will cause the generator to create a new bot project without prompting for confirmation before generating the bot. -## License +#### Default Options -MIT © [Microsoft]() +| Command line Option | Default Value | +| ------------------- | ----------- | +| --botname, -N | `echo` | +| --packageName, -p | `echo` | +| --template, -T | `echo` | + +#### Examples Using --noprompt + +This example shows how to run the generator in --noprompt mode, setting all required options on the command line. + +```bash +# Run the generator, setting all command line options +yo botbuilder-java --noprompt -N "MyEchoBot" -P "com.mycompany.bot.echo" -T "echo" +``` + +This example shows how to run the generator in --noprompt mode, using all the default command line options. The generator will create a bot project using all the default values specified in the **Default Options** table above. + +```bash +# Run the generator using all default options +yo botbuilder-java --noprompt +``` +## Logging Issues and Providing Feedback -[npm-image]: https://badge.fury.io/js/generator-botbuilder-java.svg -[npm-url]: https://npmjs.org/package/generator-botbuilder-java -[travis-image]: https://travis-ci.org/Microsoft/generator-botbuilder-java.svg?branch=master -[travis-url]: https://travis-ci.org/Microsoft/generator-botbuilder-java -[daviddm-image]: https://david-dm.org/Microsoft/generator-botbuilder-java.svg?theme=shields.io -[daviddm-url]: https://david-dm.org/Microsoft/generator-botbuilder-java -[coveralls-image]: https://coveralls.io/repos/Microsoft/generator-botbuilder-java/badge.svg -[coveralls-url]: https://coveralls.io/r/Microsoft/generator-botbuilder-java +Issues and feedback about the botbuilder generator can be submitted through the project's [GitHub Issues](https://github.com/Microsoft/botbuilder-samples/issues) page. diff --git a/Generator/generator-botbuilder-java/__tests__/app.js b/Generator/generator-botbuilder-java/__tests__/app.js deleted file mode 100644 index 72f8b04db..000000000 --- a/Generator/generator-botbuilder-java/__tests__/app.js +++ /dev/null @@ -1,16 +0,0 @@ -'use strict'; -const path = require('path'); -const assert = require('yeoman-assert'); -const helpers = require('yeoman-test'); - -describe('generator-botbuilder-java:app', () => { - beforeAll(() => { - return helpers - .run(path.join(__dirname, '../generators/app')) - .withPrompts({ someAnswer: true }); - }); - - it('creates files', () => { - assert.file(['dummyfile.txt']); - }); -}); diff --git a/Generator/generator-botbuilder-java/generators/app/index.js b/Generator/generator-botbuilder-java/generators/app/index.js index 59930463f..725a0f68b 100644 --- a/Generator/generator-botbuilder-java/generators/app/index.js +++ b/Generator/generator-botbuilder-java/generators/app/index.js @@ -1,49 +1,253 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + 'use strict'; +const pkg = require('../../package.json'); const Generator = require('yeoman-generator'); -const chalk = require('chalk'); -const yosay = require('yosay'); const path = require('path'); const _ = require('lodash'); -const extend = require('deep-extend'); +const chalk = require('chalk'); const mkdirp = require('mkdirp'); +const camelCase = require('camelcase'); + +const BOT_TEMPLATE_NAME_EMPTY = 'Empty Bot'; +const BOT_TEMPLATE_NAME_SIMPLE = 'Echo Bot'; +const BOT_TEMPLATE_NAME_CORE = 'Core Bot'; + +const BOT_TEMPLATE_NOPROMPT_EMPTY = 'empty'; +const BOT_TEMPLATE_NOPROMPT_SIMPLE = 'echo'; +const BOT_TEMPLATE_NOPROMPT_CORE = 'core'; module.exports = class extends Generator { - prompting() { - // Have Yeoman greet the user. - this.log( - yosay(`Welcome to the badass ${chalk.red('generator-botbuilder-java')} generator!`) - ); - - const prompts = [ - { name: 'botName', message: `What 's the name of your bot?`, default: 'sample' }, - { name: 'description', message: 'What will your bot do?', default: 'sample' }, - { name: 'dialog', type: 'list', message: 'What default dialog do you want?', choices: ['Echo'] }, - ]; - - return this.prompt(prompts).then(props => { - // To access props later use this.props.someAnswer; - this.props = props; - }); - } - - writing() { - const directoryName = _.kebabCase(this.props.botName); - const defaultDialog = this.props.dialog.split(' ')[0].toLowerCase(); - - if (path.basename(this.destinationPath()) !== directoryName) { - this.log(`Your bot should be in a directory named ${directoryName}\nI'll automatically create this folder.`); - mkdirp(directoryName); - this.destinationRoot(this.destinationPath(directoryName)); - } - - this.fs.copyTpl(this.templatePath('pom.xml'), this.destinationPath('pom.xml'), { botName: directoryName }); - this.fs.copy(this.templatePath(`app.java`), this.destinationPath(`app.java`)); - this.fs.copyTpl(this.templatePath('README.md'), this.destinationPath('README.md'), { - botName: this.props.botName, description: this.props.description - }); - } - - install() { - this.installDependencies({bower: false}); - } + constructor(args, opts) { + super(args, opts); + + // allocate an object that we can use to store our user prompt values from our askFor* functions + this.templateConfig = {}; + + // configure the commandline options + this._configureCommandlineOptions(); + } + + initializing() { + // give the user some data before we start asking them questions + this.log(`\nWelcome to the Microsoft Java Bot Builder generator v${pkg.version}. `); + } + + prompting() { + // if we're told to not prompt, then pick what we need and return + if(this.options.noprompt) { + // this function will throw if it encounters errors/invalid options + return this._verifyNoPromptOptions(); + } + + const userPrompts = this._getPrompts(); + async function executePrompts([prompt, ...rest]) { + if (prompt) { + await prompt(); + return executePrompts(rest); + } + } + + return executePrompts(userPrompts); + } + + writing() { + // if the user confirmed their settings, then lets go ahead + // an install module dependencies + if(this.templateConfig.finalConfirmation === true) { + const botName = this.templateConfig.botName; + const packageName = this.templateConfig.packageName.toLowerCase(); + const packageTree = packageName.replace(/\./g, '/'); + const artifact = camelCase(this.templateConfig.botName); + const directoryName = camelCase(this.templateConfig.botName); + const template = this.templateConfig.template.toLowerCase(); + + if (path.basename(this.destinationPath()) !== directoryName) { + mkdirp.sync(directoryName); + this.destinationRoot(this.destinationPath(directoryName)); + } + + // Copy the project tree + this.fs.copyTpl( + this.templatePath(path.join(template, 'project', '**')), + this.destinationPath(), + { + botName, + packageName, + artifact + } + ); + + // Copy main source + this.fs.copyTpl( + this.templatePath(path.join(template, 'src/main/java/**')), + this.destinationPath(path.join('src/main/java', packageTree)), + { + packageName + } + ); + + // Copy test source + this.fs.copyTpl( + this.templatePath(path.join(template, 'src/test/java/**')), + this.destinationPath(path.join('src/test/java', packageTree)), + { + packageName + } + ); + } + } + + end() { + if(this.templateConfig.finalConfirmation === true) { + this.log(chalk.green('------------------------ ')); + this.log(chalk.green(' Your new bot is ready! ')); + this.log(chalk.green('------------------------ ')); + this.log(`Your bot should be in a directory named "${camelCase(this.templateConfig.botName)}"`); + this.log('Open the ' + chalk.green.bold('README.md') + ' to learn how to run your bot. '); + this.log('Thank you for using the Microsoft Bot Framework. '); + this.log('\n< ** > The Bot Framework Team'); + } else { + this.log(chalk.red.bold('-------------------------------- ')); + this.log(chalk.red.bold(' New bot creation was canceled. ')); + this.log(chalk.red.bold('-------------------------------- ')); + this.log('Thank you for using the Microsoft Bot Framework. '); + this.log('\n< ** > The Bot Framework Team'); + } + } + + _configureCommandlineOptions() { + this.option('botName', { + desc: 'The name you want to give to your bot', + type: String, + default: 'echo', + alias: 'N' + }); + + this.option('packageName', { + desc: `What's the fully qualified package name of your bot?`, + type: String, + default: 'com.mycompany.echo', + alias: 'P' + }); + + const templateDesc = `The initial bot capabilities. (${BOT_TEMPLATE_NAME_EMPTY} | ${BOT_TEMPLATE_NAME_SIMPLE} | ${BOT_TEMPLATE_NAME_CORE})`; + this.option('template', { + desc: templateDesc, + type: String, + default: BOT_TEMPLATE_NAME_SIMPLE, + alias: 'T' + }); + + this.argument('noprompt', { + desc: 'Do not prompt for any information or confirmation', + type: Boolean, + required: false, + default: false + }); + } + + _getPrompts() { + return [ + // ask the user to name their bot + async () => { + return this.prompt({ + type: 'input', + name: 'botName', + message: `What's the name of your bot?`, + default: (this.options.botName ? this.options.botName : 'echo') + }).then(answer => { + // store the botname description answer + this.templateConfig.botName = answer.botName; + }); + }, + + // ask for package name + async () => { + return this.prompt({ + type: 'input', + name: 'packageName', + message: `What's the fully qualified package name of your bot?`, + default: (this.options.packageName ? this.options.packageName : 'com.mycompany.echo') + }).then(answer => { + // store the package name description answer + this.templateConfig.packageName = answer.packageName; + }); + }, + + + // ask the user which bot template we should use + async () => { + return this.prompt({ + type: 'list', + name: 'template', + message: 'Which template would you like to start with?', + choices: [ + { + name: BOT_TEMPLATE_NAME_SIMPLE, + value: BOT_TEMPLATE_NOPROMPT_SIMPLE + }, + { + name: BOT_TEMPLATE_NAME_EMPTY, + value: BOT_TEMPLATE_NOPROMPT_EMPTY + } + + /*, + { + name: BOT_TEMPLATE_NAME_CORE, + value: BOT_TEMPLATE_NOPROMPT_CORE + } + */ + ], + default: (this.options.template ? _.toLower(this.options.template) : BOT_TEMPLATE_NOPROMPT_SIMPLE) + }).then(answer => { + // store the template prompt answer + this.templateConfig.template = answer.template; + }); + }, + + // ask the user for final confirmation before we generate their bot + async () => { + return this.prompt({ + type: 'confirm', + name: 'finalConfirmation', + message: 'Looking good. Shall I go ahead and create your new bot?', + default: true + }).then(answer => { + // store the finalConfirmation prompt answer + this.templateConfig.finalConfirmation = answer.finalConfirmation; + }); + } + ]; + } + + // if we're run with the --noprompt option, verify that all required options were supplied. + // throw for missing options, or a resolved Promise + _verifyNoPromptOptions() { + this.templateConfig = _.pick(this.options, ['botName', 'packageName', 'template']) + + // validate we have what we need, or we'll need to throw + if(!this.templateConfig.botName) { + throw new Error('Must specify a name for your bot when using --noprompt argument. Use --botName or -N'); + } + if(!this.templateConfig.packageName) { + throw new Error('Must specify a package name for your bot when using --noprompt argument. Use --packageName or -P'); + } + + // make sure we have a supported template + const template = (this.templateConfig.template ? _.toLower(this.templateConfig.template) : undefined); + const tmplEmpty = _.toLower(BOT_TEMPLATE_NOPROMPT_EMPTY); + const tmplSimple = _.toLower(BOT_TEMPLATE_NOPROMPT_SIMPLE); + const tmplCore = _.toLower(BOT_TEMPLATE_NOPROMPT_CORE); + if (!template || (template !== tmplEmpty && template !== tmplSimple && template !== tmplCore)) { + throw new Error('Must specify a template when using --noprompt argument. Use --template or -T'); + } + + // when run using --noprompt and we have all the required templateConfig, then set final confirmation to true + // so we can go forward and create the new bot without prompting the user for confirmation + this.templateConfig.finalConfirmation = true; + + return Promise.resolve(); + } }; diff --git a/Generator/generator-botbuilder-java/generators/app/templates/README.md b/Generator/generator-botbuilder-java/generators/app/templates/README.md deleted file mode 100644 index 47ace33f4..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/README.md +++ /dev/null @@ -1,37 +0,0 @@ -# <%= botName %> Bot - -This bot has been created using [Microsoft Bot Framework](https://dev.botframework.com), - -This bot is designed to do the following: - -<%= description %> - -## About the generator - -The goal of the BotBuilder Yeoman generator is to both scaffold out a bot according to general best practices, and to provide some templates you can use when implementing commonly requested features and dialogs in your bot. - -### Dialogs - -This generator provides the following dialogs: -- Echo Dialog, for simple bots - -## Getting Started - -### Dependencies - -### Structure - -### Configuring the bot - -### The dialogs - -- Echo dialog is designed for simple Hello, World demos and to get you started. - -### Running the bot - -## Additional Resources - -- [Microsoft Virtual Academy Bots Course](http://aka.ms/botcourse) -- [Bot Framework Documentation](https://docs.botframework.com) -- [LUIS](https://luis.ai) -- [QnA Maker](https://qnamaker.ai) \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/app.java b/Generator/generator-botbuilder-java/generators/app/templates/app.java deleted file mode 100644 index 25f1d699b..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/app.java +++ /dev/null @@ -1,95 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.connector.sample; - -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.bot.connector.customizations.CredentialProvider; -import com.microsoft.bot.connector.customizations.CredentialProviderImpl; -import com.microsoft.bot.connector.customizations.JwtTokenValidation; -import com.microsoft.bot.connector.customizations.MicrosoftAppCredentials; -import com.microsoft.bot.connector.rest.ConnectorClientImpl; -import com.microsoft.bot.schema.models.Activity; -import com.microsoft.bot.schema.models.ActivityTypes; -import com.microsoft.bot.schema.models.ResourceResponse; -import com.sun.net.httpserver.HttpExchange; -import com.sun.net.httpserver.HttpHandler; -import com.sun.net.httpserver.HttpServer; - -import java.io.IOException; -import java.io.InputStream; -import java.net.InetSocketAddress; -import java.net.URLDecoder; -import java.util.logging.Level; -import java.util.logging.Logger; - -public class App { - private static final Logger LOGGER = Logger.getLogger( App.class.getName() ); - private static String appId = ""; // <-- app id --> - private static String appPassword = ""; // <-- app password --> - - public static void main( String[] args ) throws IOException { - CredentialProvider credentialProvider = new CredentialProviderImpl(appId, appPassword); - HttpServer server = HttpServer.create(new InetSocketAddress(3978), 0); - server.createContext("/api/messages", new MessageHandle(credentialProvider)); - server.setExecutor(null); - server.start(); - } - - static class MessageHandle implements HttpHandler { - private ObjectMapper objectMapper; - private CredentialProvider credentialProvider; - private MicrosoftAppCredentials credentials; - - MessageHandle(CredentialProvider credentialProvider) { - this.objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .findAndRegisterModules(); - this.credentialProvider = credentialProvider; - this.credentials = new MicrosoftAppCredentials(appId, appPassword); - } - - public void handle(HttpExchange httpExchange) throws IOException { - if (httpExchange.getRequestMethod().equalsIgnoreCase("POST")) { - Activity activity = getActivity(httpExchange); - String authHeader = httpExchange.getRequestHeaders().getFirst("Authorization"); - try { - JwtTokenValidation.assertValidActivity(activity, authHeader, credentialProvider); - - // send ack to user activity - httpExchange.sendResponseHeaders(202, 0); - httpExchange.getResponseBody().close(); - - if (activity.type().equals(ActivityTypes.MESSAGE)) { - // reply activity with the same text - ConnectorClientImpl connector = new ConnectorClientImpl(activity.serviceUrl(), this.credentials); - ResourceResponse response = connector.conversations().sendToConversation(activity.conversation().id(), - new Activity() - .withType(ActivityTypes.MESSAGE) - .withText("Echo: " + activity.text()) - .withRecipient(activity.from()) - .withFrom(activity.recipient()) - ); - } - } catch (AuthenticationException ex) { - httpExchange.sendResponseHeaders(401, 0); - httpExchange.getResponseBody().close(); - LOGGER.log(Level.WARNING, "Auth failed!", ex); - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "Execution failed", ex); - } - } - } - - private Activity getActivity(HttpExchange httpExchange) { - try (InputStream is = httpExchange.getRequestBody()) { - return objectMapper.readValue(is, Activity.class); - } catch (Exception ex) { - LOGGER.log(Level.WARNING, "Failed to get activity", ex); - return null; - } - - } - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md new file mode 100644 index 000000000..85269b70f --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md @@ -0,0 +1,85 @@ +# <%= botName %> + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. + +This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\<%= botName %>-1.0.0.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/new-rg-parameters.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..2619e0c55 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,43 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/preexisting-rg-parameters.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..f42bb04f1 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,40 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ebc520990 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,204 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": + "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": + "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": + "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": + "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": + "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": + "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": + "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": + "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": + "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": + "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": + "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved": true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..aa6d7e094 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,173 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": + "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": + "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": + "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": + "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": + "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": + "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": + "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": + "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": + "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": + "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": + "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": + "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved": true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml new file mode 100644 index 000000000..4154eca8a --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml @@ -0,0 +1,253 @@ + + + + 4.0.0 + + <%= packageName %> + <%= artifact %> + 1.0.0 + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Bot Echo. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + 1.8 + 1.8 + 1.8 + <%= packageName %>.Application + https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + + + + MyGet + ${repo.url} + + + + + + ossrh + + https://oss.sonatype.org/ + + + + + + + build + + true + + + + + src/main/resources + false + + + + + + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + <%= packageName %>.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.0.0 + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json new file mode 100644 index 000000000..ad838e77f --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json @@ -0,0 +1,21 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": { + "pattern": + "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" + } + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": { "ref": "Console-Appender", "level": "debug" } + } + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html new file mode 100644 index 000000000..88b6eaf65 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + <%= botName %> + + + + + +
+
+
+
<%= botName %>
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+
+
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java new file mode 100644 index 000000000..8c5248733 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + *

+ * This class also provides overrides for dependency injections. A class that extends the {@link + * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. + * + * @see EchoBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java new file mode 100644 index 000000000..a4628079e --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.ChannelAccount; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + * + *

+ * This is where application specific logic for interacting with the users would be added. For this + * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link + * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. + *

+ */ +@Component +public class EchoBot extends ActivityHandler { + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + return turnContext.sendActivity( + MessageFactory.text("Echo: " + turnContext.getActivity().getText()) + ).thenApply(sendResult -> null); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, + TurnContext turnContext + ) { + return membersAdded.stream() + .filter( + member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) + ).map(channel -> turnContext.sendActivity(MessageFactory.text("Hello and welcome!"))) + .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTest.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTest.java new file mode 100644 index 000000000..b3bf98287 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md new file mode 100644 index 000000000..85269b70f --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md @@ -0,0 +1,85 @@ +# <%= botName %> + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. + +This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\<%= botName %>-1.0.0.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/new-rg-parameters.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..2619e0c55 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,43 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/preexisting-rg-parameters.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..f42bb04f1 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,40 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ebc520990 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,204 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": + "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": + "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": + "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": + "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": + "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": + "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": + "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": + "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": + "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": + "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": + "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved": true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..aa6d7e094 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,173 @@ +{ + "$schema": + "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": + "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": + "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": + "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": + "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": + "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": + "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": + "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": + "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": + "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": + "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": + "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": + "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": + "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved": true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml new file mode 100644 index 000000000..4154eca8a --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml @@ -0,0 +1,253 @@ + + + + 4.0.0 + + <%= packageName %> + <%= artifact %> + 1.0.0 + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Bot Echo. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + 1.8 + 1.8 + 1.8 + <%= packageName %>.Application + https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + + + + MyGet + ${repo.url} + + + + + + ossrh + + https://oss.sonatype.org/ + + + + + + + build + + true + + + + + src/main/resources + false + + + + + + maven-compiler-plugin + 3.8.1 + + 1.8 + 1.8 + + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + <%= packageName %>.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.7.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.0.0 + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-jar-plugin + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json new file mode 100644 index 000000000..ad838e77f --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json @@ -0,0 +1,21 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": { + "pattern": + "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" + } + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": { "ref": "Console-Appender", "level": "debug" } + } + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html new file mode 100644 index 000000000..88b6eaf65 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + <%= botName %> + + + + + +
+
+
+
<%= botName %>
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java new file mode 100644 index 000000000..8c5248733 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + *

+ * This class also provides overrides for dependency injections. A class that extends the {@link + * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. + * + * @see EchoBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java new file mode 100644 index 000000000..a083e8405 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.ChannelAccount; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + * + *

+ * This is where application specific logic for interacting with the users would be added. For this + * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link + * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. + *

+ */ +@Component +public class EmptyBot extends ActivityHandler { +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTest.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTest.java new file mode 100644 index 000000000..51194d276 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTests { + + @Test + public void contextLoads() { + } + +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/pom.xml deleted file mode 100644 index 6f9fbd595..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/pom.xml +++ /dev/null @@ -1,89 +0,0 @@ - - 4.0.0 - - com.microsoft.bot - generator-botbuilder-java - jar - 1.0.0 - - - com.microsoft.bot - bot-parent - 4.0.0 - ../../../../../pom.xml - - - generator-botbuilder-java - http://maven.apache.org - - - UTF-8 - - - - - junit - junit - 4.13.1 - test - - - com.fasterxml.jackson.module - jackson-module-parameter-names - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.2 - - - org.slf4j - slf4j-api - LATEST - - - org.slf4j - slf4j-simple - LATEST - - - com.microsoft.bot.schema - botbuilder-schema - 4.0.0-a0 - - - com.microsoft.bot.connector - bot-connector - 4.0.0-a0 - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.7.0 - - 1.8 - 1.8 - - - - org.codehaus.mojo - exec-maven-plugin - 1.6.0 - - com.microsoft.bot.connector.sample.App - - - - - diff --git a/Generator/generator-botbuilder-java/package-lock.json b/Generator/generator-botbuilder-java/package-lock.json index b7df0de19..c51903834 100644 --- a/Generator/generator-botbuilder-java/package-lock.json +++ b/Generator/generator-botbuilder-java/package-lock.json @@ -1,473 +1,202 @@ { - "name": "generator-botbuilder-java", - "version": "0.0.0", + "name": "generator-java", + "version": "4.9.1", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/code-frame": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.0.0-beta.40.tgz", - "integrity": "sha512-eVXQSbu/RimU6OKcK2/gDJVTFcxXJI4sHbIqw2mhwMZeQ2as/8AhS9DGkEDoHMBBNJZ5B0US63lF56x+KDcxiA==", - "dev": true, + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", + "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", "requires": { - "@babel/highlight": "7.0.0-beta.40" + "@babel/highlight": "^7.10.4" } }, + "@babel/helper-validator-identifier": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", + "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" + }, "@babel/highlight": { - "version": "7.0.0-beta.40", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.0.0-beta.40.tgz", - "integrity": "sha512-mOhhTrzieV6VO7odgzFGFapiwRK0ei8RZRhfzHhb6cpX3QM8XXuCLXWjN8qBB7JReDdUR80V3LFfFrGUYevhNg==", - "dev": true, + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", + "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", "requires": { + "@babel/helper-validator-identifier": "^7.10.4", "chalk": "^2.0.0", - "esutils": "^2.0.2", - "js-tokens": "^3.0.0" - } - }, - "abab": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/abab/-/abab-1.0.4.tgz", - "integrity": "sha1-X6rZwsB/YN12dw9xzwJbYqY8/U4=", - "dev": true - }, - "acorn": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.5.3.tgz", - "integrity": "sha512-jd5MkIUlbbmb07nXH0DT3y7rDVtkzDi4XZOUVWAer8ajmF/DTSSbl5oNFyDOl/OXA33Bl79+ypHhl2pN20VeOQ==", - "dev": true - }, - "acorn-globals": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-4.1.0.tgz", - "integrity": "sha512-KjZwU26uG3u6eZcfGbTULzFcsoz6pegNKtHPksZPOUsiKo5bUmiBPa38FuHZ/Eun+XYh/JCCkS9AS3Lu4McQOQ==", - "dev": true, - "requires": { - "acorn": "^5.0.0" - } - }, - "acorn-jsx": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", - "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", - "dev": true, - "requires": { - "acorn": "^3.0.4" - }, - "dependencies": { - "acorn": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", - "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", - "dev": true - } - } - }, - "ajv": { - "version": "5.5.2", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-5.5.2.tgz", - "integrity": "sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU=", - "dev": true, - "requires": { - "co": "^4.6.0", - "fast-deep-equal": "^1.0.0", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.3.0" - } - }, - "ajv-keywords": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-2.1.1.tgz", - "integrity": "sha1-YXmX/F9gV2iUxDX5QNgZ4TW4B2I=", - "dev": true - }, - "ansi-escapes": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.1.0.tgz", - "integrity": "sha512-UgAb8H9D41AQnu/PbWlCofQVcnV4Gs2bBJi9eZPxfU/hgglFh3SMDMENRIqdr7H6XFnXdoknctFByVsCOotTVw==" - }, - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" - }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "any-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/any-observable/-/any-observable-0.2.0.tgz", - "integrity": "sha1-xnhwBYADV5AJCD9UrAq6+1wz0kI=", - "dev": true - }, - "anymatch": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-2.0.0.tgz", - "integrity": "sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw==", - "dev": true, - "requires": { - "micromatch": "^3.1.4", - "normalize-path": "^2.1.1" + "js-tokens": "^4.0.0" }, "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } + "color-convert": "^1.9.0" } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "kind-of": "^6.0.0" + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "kind-of": "^6.0.0" + "color-name": "1.1.3" } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, - "is-number": { + "has-flag": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { - "remove-trailing-separator": "^1.0.1" + "has-flag": "^3.0.0" } } } }, - "app-root-path": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/app-root-path/-/app-root-path-2.0.1.tgz", - "integrity": "sha1-zWLc+OT9WkF+/GZNLlsQZTxlG0Y=", - "dev": true + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "optional": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } }, - "append-transform": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-0.4.0.tgz", - "integrity": "sha1-126/jKlNJ24keja61EpLdKthGZE=", - "dev": true, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "optional": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "optional": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", + "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", + "optional": true + }, + "@types/node": { + "version": "14.14.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", + "integrity": "sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==", + "optional": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "optional": true, "requires": { - "default-require-extensions": "^1.0.0" + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" } }, - "argparse": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", - "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", - "dev": true, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "optional": true, "requires": { - "sprintf-js": "~1.0.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true, + "ansi-escapes": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", + "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", + "optional": true, + "requires": { + "type-fest": "^0.11.0" + }, + "dependencies": { + "type-fest": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", + "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", + "optional": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "requires": { - "arr-flatten": "^1.0.1" + "color-convert": "^2.0.1" } }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "optional": true + }, "arr-flatten": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true + "optional": true }, "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "dev": true + "optional": true }, "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=" - }, - "array-equal": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "optional": true }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "optional": true, "requires": { "array-uniq": "^1.0.1" } @@ -475,386 +204,126 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=" + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "optional": true }, "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "optional": true }, "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true }, "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", + "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", + "optional": true, + "requires": { + "safer-buffer": "~2.1.0" + } }, "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "optional": true }, "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "dev": true - }, - "astral-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz", - "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==", - "dev": true + "optional": true }, "async": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.0.tgz", - "integrity": "sha512-xAfGg1/NTLBBKlHFmnd7PlmUW9KhVQIUuSrYem9xzFUZy13ScvtyGGejaae9iAVRiRq9+Cx7DPFaAAhCpyxyPw==", + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", "requires": { - "lodash": "^4.14.0" + "lodash": "^4.17.14" } }, - "async-limiter": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.0.tgz", - "integrity": "sha512-jp/uFnooOiO+L211eZOoSyzpOITMXx1rBITauYykG3BRYPu8h0UcxsPNB04RR5vo4Tyz3+ay17tR6JVf9qzYWg==", - "dev": true - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true + "optional": true }, "atob": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.0.tgz", - "integrity": "sha512-SuiKH8vbsOyCALjA/+EINmt/Kdl+TQPrtFgW7XZZcwtryFu9e5kQoX3bjCW6mIvGH1fbeAZZuvwGR5IlBRznGw==", - "dev": true + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "optional": true }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "optional": true }, "aws4": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.7.0.tgz", - "integrity": "sha512-32NDda82rhwD9/JBCCkB+MRYDp0oSvlo2IL6rQWA10PQi7tDUM3eqMSltXmY+Oyl/7N3P3qNtAlv7X0d9bI28w==", - "dev": true - }, - "babel-code-frame": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", + "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", + "optional": true + }, + "axios": { + "version": "0.18.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", + "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", + "optional": true, + "requires": { + "follow-redirects": "1.5.10", + "is-buffer": "^2.0.2" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "optional": true } } }, - "babel-core": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-core/-/babel-core-6.26.0.tgz", - "integrity": "sha1-rzL3izGm/O8RnIew/Y2XU/A6C7g=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-generator": "^6.26.0", - "babel-helpers": "^6.24.1", - "babel-messages": "^6.23.0", - "babel-register": "^6.26.0", - "babel-runtime": "^6.26.0", - "babel-template": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "convert-source-map": "^1.5.0", - "debug": "^2.6.8", - "json5": "^0.5.1", - "lodash": "^4.17.4", - "minimatch": "^3.0.4", - "path-is-absolute": "^1.0.1", - "private": "^0.1.7", - "slash": "^1.0.0", - "source-map": "^0.5.6" + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "optional": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" }, "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "optional": true, "requires": { - "ms": "2.0.0" - } - } - } - }, - "babel-generator": { - "version": "6.26.1", - "resolved": "https://registry.npmjs.org/babel-generator/-/babel-generator-6.26.1.tgz", - "integrity": "sha512-HyfwY6ApZj7BYTcJURpM5tznulaBvyio7/0d4zFOeMPUmfxkCjHocCuoLa2SAGzBI8AREcH3eP3758F672DppA==", - "dev": true, - "requires": { - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "detect-indent": "^4.0.0", - "jsesc": "^1.3.0", - "lodash": "^4.17.4", - "source-map": "^0.5.7", - "trim-right": "^1.0.1" - } - }, - "babel-helpers": { - "version": "6.24.1", - "resolved": "https://registry.npmjs.org/babel-helpers/-/babel-helpers-6.24.1.tgz", - "integrity": "sha1-NHHenK7DiOXIUOWX5Yom3fN2ArI=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0", - "babel-template": "^6.24.1" - } - }, - "babel-jest": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-22.4.3.tgz", - "integrity": "sha512-BgSjmtl3mW3i+VeVHEr9d2zFSAT66G++pJcHQiUjd00pkW+voYXFctIm/indcqOWWXw5a1nUpR1XWszD9fJ1qg==", - "dev": true, - "requires": { - "babel-plugin-istanbul": "^4.1.5", - "babel-preset-jest": "^22.4.3" - } - }, - "babel-messages": { - "version": "6.23.0", - "resolved": "https://registry.npmjs.org/babel-messages/-/babel-messages-6.23.0.tgz", - "integrity": "sha1-8830cDhYA1sqKVHG7F7fbGLyYw4=", - "dev": true, - "requires": { - "babel-runtime": "^6.22.0" - } - }, - "babel-plugin-istanbul": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.6.tgz", - "integrity": "sha512-PWP9FQ1AhZhS01T/4qLSKoHGY/xvkZdVBGlKM/HuxxS3+sC66HhTNR7+MpbO/so/cz/wY94MeSWJuP1hXIPfwQ==", - "dev": true, - "requires": { - "babel-plugin-syntax-object-rest-spread": "^6.13.0", - "find-up": "^2.1.0", - "istanbul-lib-instrument": "^1.10.1", - "test-exclude": "^4.2.1" - } - }, - "babel-plugin-jest-hoist": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-22.4.3.tgz", - "integrity": "sha512-zhvv4f6OTWy2bYevcJftwGCWXMFe7pqoz41IhMi4xna7xNsX5NygdagsrE0y6kkfuXq8UalwvPwKTyAxME2E/g==", - "dev": true - }, - "babel-plugin-syntax-object-rest-spread": { - "version": "6.13.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz", - "integrity": "sha1-/WU28rzhODb/o6VFjEkDpZe7O/U=", - "dev": true - }, - "babel-preset-jest": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-22.4.3.tgz", - "integrity": "sha512-a+M3LTEXTq3gxv0uBN9Qm6ahUl7a8pj923nFbCUdqFUSsf3YrX8Uc+C3MEwji5Af3LiQjSC7w4ooYewlz8HRTA==", - "dev": true, - "requires": { - "babel-plugin-jest-hoist": "^22.4.3", - "babel-plugin-syntax-object-rest-spread": "^6.13.0" - } - }, - "babel-register": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-register/-/babel-register-6.26.0.tgz", - "integrity": "sha1-btAhFz4vy0htestFxgCahW9kcHE=", - "dev": true, - "requires": { - "babel-core": "^6.26.0", - "babel-runtime": "^6.26.0", - "core-js": "^2.5.0", - "home-or-tmp": "^2.0.0", - "lodash": "^4.17.4", - "mkdirp": "^0.5.1", - "source-map-support": "^0.4.15" - }, - "dependencies": { - "source-map-support": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.4.18.tgz", - "integrity": "sha512-try0/JqxPLF9nOjvSta7tVondkP5dwgyLDjVoyMDlmjugT2lRZ1OfsrYTkCd2hkDnJTKRbO/Rl3orm8vlsUzbA==", - "dev": true, - "requires": { - "source-map": "^0.5.6" - } - } - } - }, - "babel-runtime": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", - "dev": true, - "requires": { - "core-js": "^2.4.0", - "regenerator-runtime": "^0.11.0" - } - }, - "babel-template": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-template/-/babel-template-6.26.0.tgz", - "integrity": "sha1-3gPi0WOWsGn0bdn/+FIfsaDjXgI=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "babel-traverse": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "lodash": "^4.17.4" - } - }, - "babel-traverse": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-traverse/-/babel-traverse-6.26.0.tgz", - "integrity": "sha1-RqnL1+3MYsjlwGTi0tjQ9ANXZu4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.26.0", - "babel-messages": "^6.23.0", - "babel-runtime": "^6.26.0", - "babel-types": "^6.26.0", - "babylon": "^6.18.0", - "debug": "^2.6.8", - "globals": "^9.18.0", - "invariant": "^2.2.2", - "lodash": "^4.17.4" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "globals": { - "version": "9.18.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", - "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", - "dev": true - } - } - }, - "babel-types": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", - "dev": true, - "requires": { - "babel-runtime": "^6.26.0", - "esutils": "^2.0.2", - "lodash": "^4.17.4", - "to-fast-properties": "^1.0.3" - } - }, - "babylon": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/babylon/-/babylon-6.18.0.tgz", - "integrity": "sha512-q/UEjfGJ2Cm3oKV71DJz9d25TPnq5rhBVL2Q4fA5wcC3jcrdn7+SssEybFIxwAvvP+YCsCYNKughoF33GxgycQ==", - "dev": true - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "dev": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "^1.0.0" } }, "is-accessor-descriptor": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -863,7 +332,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -872,50 +341,28 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "optional": true, "requires": { "tweetnacl": "^0.14.3" } }, "binaryextensions": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.1.1.tgz", - "integrity": "sha512-XBaoWE9RW8pPdPQNibZsW2zh8TW6gcarXp1FZPwT8Uop8ScSNldJEWf2k9l3HeTqdrEwsOsFcq74RiJECW34yA==" - }, - "boom": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-4.3.1.tgz", - "integrity": "sha1-T4owBctKfjiJ90kDD9JbluAdLjE=", - "dev": true, - "requires": { - "hoek": "4.x.x" - } + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==" }, "brace-expansion": { "version": "1.1.11", @@ -927,64 +374,39 @@ } }, "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true, - "requires": { - "expand-range": "^1.8.1", - "preserve": "^0.2.0", - "repeat-element": "^1.1.2" - } - }, - "browser-process-hrtime": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-0.1.2.tgz", - "integrity": "sha1-Ql1opY00R/AqBKqJQYf86K+Le44=", - "dev": true - }, - "browser-resolve": { - "version": "1.11.2", - "resolved": "https://registry.npmjs.org/browser-resolve/-/browser-resolve-1.11.2.tgz", - "integrity": "sha1-j/CbCixCFxihBRwmCzLkj0QpOM4=", - "dev": true, + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "optional": true, "requires": { - "resolve": "1.1.7" + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" }, "dependencies": { - "resolve": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", - "integrity": "sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs=", - "dev": true + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } } } }, - "bser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/bser/-/bser-2.0.0.tgz", - "integrity": "sha1-mseNPtXZFYBP2HrLFYvHlxR6Fxk=", - "dev": true, - "requires": { - "node-int64": "^0.4.0" - } - }, - "buffer-from": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.0.0.tgz", - "integrity": "sha512-83apNb8KK0Se60UE1+4Ukbe3HbfELJ6UlI4ldtOGs7So4KD26orJM8hIY9lxdzP+UpItH1Yh/Y8GUvNFWFFRxA==", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=" - }, "cache-base": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "dev": true, + "optional": true, "requires": { "collection-visit": "^1.0.0", "component-emitter": "^1.2.1", @@ -995,92 +417,50 @@ "to-object-path": "^0.3.0", "union-value": "^1.0.0", "unset-value": "^1.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, - "caller-path": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", - "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", - "dev": true, - "requires": { - "callsites": "^0.2.0" - } + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "optional": true }, - "callsites": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", - "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", - "dev": true + "camelcase": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", + "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==" }, "capture-stack-trace": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.0.tgz", - "integrity": "sha1-Sm+gc5nCa7pH8LJJa00PtAjFVQ0=", - "dev": true + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" }, "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true + "optional": true }, "chalk": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.0.tgz", - "integrity": "sha512-Wr/w0f4o9LuE7K53cD0qmbAMM+2XNLzR29vFn5hqko4sxGlUsyy363NvmyGIyk5tpe9cjTr9SJYbysEyPkRnFw==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" } }, "chardet": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.4.2.tgz", - "integrity": "sha1-tUc7M9yXxCTl2Y3IfVXU2KKci/I=" - }, - "ci-info": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-1.1.3.tgz", - "integrity": "sha512-SK/846h/Rcy8q9Z9CAwGBLfCJ6EkjJWdpelWDufQpqVDYq2Wnnv8zlSO6AMQap02jvhVruKKpEtQOufo3pFhLg==", - "dev": true - }, - "circular-json": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", - "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", - "dev": true - }, - "class-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/class-extend/-/class-extend-0.1.2.tgz", - "integrity": "sha1-gFeoKwD1P4Kl1ixQ74z/3sb6vDQ=", - "dev": true, - "requires": { - "object-assign": "^2.0.0" - }, - "dependencies": { - "object-assign": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", - "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=", - "dev": true - } - } + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "optional": true }, "class-utils": { "version": "0.3.6", "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "dev": true, + "optional": true, "requires": { "arr-union": "^3.1.0", "define-property": "^0.2.5", @@ -1092,435 +472,287 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true } } }, - "cli-boxes": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" - }, "cli-cursor": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz", - "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "optional": true, "requires": { - "restore-cursor": "^2.0.0" + "restore-cursor": "^3.1.0" } }, - "cli-spinners": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-0.1.2.tgz", - "integrity": "sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw=", - "dev": true - }, "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "requires": { - "colors": "1.0.3" - } - }, - "cli-truncate": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-0.2.1.tgz", - "integrity": "sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ=", - "dev": true, + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", + "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", "requires": { - "slice-ansi": "0.0.4", - "string-width": "^1.0.1" + "chalk": "^2.4.1", + "string-width": "^4.2.0" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "requires": { - "number-is-nan": "^1.0.0" + "color-convert": "^1.9.0" } }, - "slice-ansi": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", - "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", - "dev": true + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "color-name": "1.1.3" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "requires": { - "ansi-regex": "^2.0.0" + "has-flag": "^3.0.0" } } } }, "cli-width": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz", - "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "optional": true }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=" + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "optional": true }, "clone-buffer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "optional": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "optional": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } }, "clone-stats": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=" + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "optional": true }, "cloneable-readable": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.2.tgz", - "integrity": "sha512-Bq6+4t+lbM8vhTs/Bef5c5AdEMtapp/iFb6+s4/Hh9MVTt8OLKH7ZOOZSCT+Ys7hsHvqv0GuMPJ1lnQJVHvxpg==", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "optional": true, "requires": { "inherits": "^2.0.1", "process-nextick-args": "^2.0.0", "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" - }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "dev": true, + "optional": true, "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" } }, "color-convert": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", - "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "requires": { - "color-name": "^1.1.1" + "color-name": "~1.1.4" } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "combined-stream": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.6.tgz", - "integrity": "sha1-cj599ugBrFYTETp+RFqbactjKBg=", - "dev": true, + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "optional": true, "requires": { "delayed-stream": "~1.0.0" } }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=" - }, - "compare-versions": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-3.1.0.tgz", - "integrity": "sha512-4hAxDSBypT/yp2ySFD346So6Ragw5xmBn/e/agIGl3bZr6DLUqnoRZPusxKrXdYRZpgexO9daejmIenlq/wrIQ==", - "dev": true + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "optional": true }, "component-emitter": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", - "integrity": "sha1-E3kY1teCg/ffemt8WmPhQOaUJeY=", - "dev": true + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, - "concat-stream": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", - "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", - "dev": true, - "requires": { - "buffer-from": "^1.0.0", - "inherits": "^2.0.3", - "readable-stream": "^2.2.2", - "typedarray": "^0.0.6" - } - }, - "convert-source-map": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.5.1.tgz", - "integrity": "sha1-uCeAl7m8IpNl3lxiz1/K7YtVmeU=", - "dev": true - }, "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "dev": true - }, - "core-js": { - "version": "2.5.5", - "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.5.5.tgz", - "integrity": "sha1-sU3ek2xkDAV5prUMq8wTLdYSfjs=", - "dev": true + "optional": true }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" - }, - "cosmiconfig": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-4.0.0.tgz", - "integrity": "sha512-6e5vDdrXZD+t5v0L8CrurPeybg4Fmf+FCSYxXKYVAqLUtyCSbuyqE059d0kDthTNRzKVjL7QMgNpEUlsoYH3iQ==", - "dev": true, - "requires": { - "is-directory": "^0.3.1", - "js-yaml": "^3.9.0", - "parse-json": "^4.0.0", - "require-from-string": "^2.0.1" - } - }, - "coveralls": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/coveralls/-/coveralls-3.0.0.tgz", - "integrity": "sha512-ZppXR9y5PraUOrf/DzHJY6gzNUhXYE3b9D43xEXs4QYZ7/Oe0Gy0CS+IPKWFfvQFXB3RG9QduaQUFehzSpGAFw==", - "dev": true, - "requires": { - "js-yaml": "^3.6.1", - "lcov-parse": "^0.0.10", - "log-driver": "^1.2.5", - "minimist": "^1.2.0", - "request": "^2.79.0" - } + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true }, "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "dev": true, "requires": { "capture-stack-trace": "^1.0.0" } }, "cross-spawn": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", "requires": { - "lru-cache": "^4.0.1", + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", "shebang-command": "^1.2.0", "which": "^1.2.9" - } - }, - "cryptiles": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-3.1.2.tgz", - "integrity": "sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=", - "dev": true, - "requires": { - "boom": "5.x.x" }, "dependencies": { - "boom": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/boom/-/boom-5.2.0.tgz", - "integrity": "sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==", - "dev": true, - "requires": { - "hoek": "4.x.x" - } + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" } } }, - "cssom": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/cssom/-/cssom-0.3.2.tgz", - "integrity": "sha1-uANhcMefB6kP8vFuIihAJ6JDhIs=", - "dev": true - }, - "cssstyle": { - "version": "0.2.37", - "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-0.2.37.tgz", - "integrity": "sha1-VBCXI0yyUTyDzu06zdwn/yeYfVQ=", - "dev": true, - "requires": { - "cssom": "0.3.x" - } - }, "dargs": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-5.1.0.tgz", - "integrity": "sha1-7H6lDHhWTNNsnV7Bj2Yyn63ieCk=" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", + "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==" }, "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, + "optional": true, "requires": { "assert-plus": "^1.0.0" } }, - "data-urls": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-1.0.0.tgz", - "integrity": "sha512-ai40PPQR0Fn1lD2PPie79CibnlMN2AYiDhwFX/rZHVsxbs5kNJSjegqXIprhouGXlRdEnfybva7kqRGnB6mypA==", - "dev": true, - "requires": { - "abab": "^1.0.4", - "whatwg-mimetype": "^2.0.0", - "whatwg-url": "^6.4.0" - } - }, - "date-fns": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-1.29.0.tgz", - "integrity": "sha512-lbTXWZ6M20cWH8N9S6afb0SBm6tMk+uUg6z3MqHPKE9atmsY3kJkTm8vKe93izJ2B2+q5MV990sM2CHgtAZaOw==", - "dev": true - }, "dateformat": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" }, "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", "requires": { - "ms": "2.0.0" + "ms": "2.1.2" } }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "dev": true - }, - "decompress-response": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-3.3.0.tgz", - "integrity": "sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=", - "requires": { - "mimic-response": "^1.0.0" - } - }, - "dedent": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/dedent/-/dedent-0.7.0.tgz", - "integrity": "sha1-JJXduvbrh0q7Dhvp3yLS5aVEMmw=", - "dev": true + "optional": true }, "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=" - }, - "deep-is": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", - "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", - "dev": true - }, - "default-require-extensions": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", - "integrity": "sha1-836hXT4T/9m0N9M+GnW1+5eHTLg=", - "dev": true, - "requires": { - "strip-bom": "^2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "define-properties": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", - "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", - "dev": true, - "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" - } + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true }, "define-property": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.2", "isobject": "^3.0.1" @@ -1530,7 +762,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1539,7 +771,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -1548,61 +780,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "del": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/del/-/del-2.2.2.tgz", - "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", - "dev": true, - "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" - }, - "dependencies": { - "globby": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-5.0.0.tgz", - "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true } } }, @@ -1610,49 +793,31 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "detect-conflict": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/detect-conflict/-/detect-conflict-1.0.1.tgz", - "integrity": "sha1-CIZXpmqWHAUBnbfEIwiDsca0F24=" - }, - "detect-indent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/detect-indent/-/detect-indent-4.0.0.tgz", - "integrity": "sha1-920GQ1LN9Docts5hnE7jqUdd4gg=", - "dev": true, - "requires": { - "repeating": "^2.0.0" - } - }, - "detect-newline": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-2.1.0.tgz", - "integrity": "sha1-9B8cEL5LAOh7XxPaaAdZ8sW/0+I=", - "dev": true + "optional": true }, "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, - "doctrine": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", - "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", - "dev": true, + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "optional": true, "requires": { - "esutils": "^2.0.2" + "path-type": "^3.0.0" } }, - "domexception": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/domexception/-/domexception-1.0.1.tgz", - "integrity": "sha512-raigMkn7CJNNo6Ihro1fzG7wr3fHuYVytzquZKX5n0yizGsTcYgzdIUwj1X9pK0VvjeihV+XiclP+DjwbsSKug==", - "dev": true, + "download-stats": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", + "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", + "optional": true, "requires": { - "webidl-conversions": "^4.0.2" + "JSONStream": "^1.2.1", + "lazy-cache": "^2.0.1", + "moment": "^2.15.1" } }, "duplexer3": { @@ -1661,70 +826,70 @@ "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "optional": true, "requires": { - "jsbn": "~0.1.0" + "jsbn": "~0.1.0", + "safer-buffer": "^2.1.0" } }, "editions": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/editions/-/editions-1.3.4.tgz", - "integrity": "sha512-gzao+mxnYDzIysXKMQi/+M1mjy/rjestjg6OPoYTtI+3Izp23oiGZitsl9lPDPiTGXbcSIk1iJWhliSaglxnUg==" + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", + "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", + "requires": { + "errlop": "^2.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } }, "ejs": { - "version": "2.5.9", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.5.9.tgz", - "integrity": "sha512-GJCAeDBKfREgkBtgrYSf9hQy9kTb3helv0zGdzqhM7iAkW8FA/ZF97VQDbwFiwIT8MQLLOe5VlPZOEvZAqtUAQ==" + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "optional": true }, - "elegant-spinner": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/elegant-spinner/-/elegant-spinner-1.0.1.tgz", - "integrity": "sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4=", - "dev": true + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" }, - "error": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/error/-/error-7.0.2.tgz", - "integrity": "sha1-pfdf/02ZJhJt2sDqXcOOaJFTywI=", + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, "requires": { - "string-template": "~0.2.1", - "xtend": "~4.0.0" + "once": "^1.4.0" } }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "requires": { - "is-arrayish": "^0.2.1" - } + "errlop": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", + "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==" }, - "es-abstract": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.11.0.tgz", - "integrity": "sha512-ZnQrE/lXTTQ39ulXZ+J1DTFazV9qBy61x2bY071B+qGco8Z8q1QddsLdt/EF8Ai9hcWH72dWS0kFqXLxOxqslA==", - "dev": true, + "error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", "requires": { - "es-to-primitive": "^1.1.1", - "function-bind": "^1.1.1", - "has": "^1.0.1", - "is-callable": "^1.1.3", - "is-regex": "^1.0.4" + "string-template": "~0.2.1" } }, - "es-to-primitive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", - "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", - "dev": true, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", "requires": { - "is-callable": "^1.1.1", - "is-date-object": "^1.0.1", - "is-symbol": "^1.0.1" + "is-arrayish": "^0.2.1" } }, "escape-string-regexp": { @@ -1732,254 +897,142 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, - "escodegen": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.9.1.tgz", - "integrity": "sha512-6hTjO1NAWkHnDk3OqQ4YrCuwwmGHL9S3nPlzBOUG/R44rda3wLNrfvQ5fkSGjyhHFKM7ALPKcKGrwvCLe0lC7Q==", - "dev": true, - "requires": { - "esprima": "^3.1.3", - "estraverse": "^4.2.0", - "esutils": "^2.0.2", - "optionator": "^0.8.1", - "source-map": "~0.6.1" - }, - "dependencies": { - "esprima": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", - "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=", - "dev": true + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "optional": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "optional": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "optional": true, + "requires": { + "pump": "^3.0.0" + } }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "optional": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "optional": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } } } }, - "eslint": { - "version": "4.19.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-4.19.1.tgz", - "integrity": "sha512-bT3/1x1EbZB7phzYu7vCr1v3ONuzDtX8WjuM9c0iYxe+cq+pwcKEoQjl7zd3RpC6YOLgnSy3cTN58M2jcoPDIQ==", - "dev": true, - "requires": { - "ajv": "^5.3.0", - "babel-code-frame": "^6.22.0", - "chalk": "^2.1.0", - "concat-stream": "^1.6.0", - "cross-spawn": "^5.1.0", - "debug": "^3.1.0", - "doctrine": "^2.1.0", - "eslint-scope": "^3.7.1", - "eslint-visitor-keys": "^1.0.0", - "espree": "^3.5.4", - "esquery": "^1.0.0", - "esutils": "^2.0.2", - "file-entry-cache": "^2.0.0", - "functional-red-black-tree": "^1.0.1", - "glob": "^7.1.2", - "globals": "^11.0.1", - "ignore": "^3.3.3", - "imurmurhash": "^0.1.4", - "inquirer": "^3.0.6", - "is-resolvable": "^1.0.0", - "js-yaml": "^3.9.1", - "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.3.0", - "lodash": "^4.17.4", - "minimatch": "^3.0.2", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "optionator": "^0.8.2", - "path-is-inside": "^1.0.2", - "pluralize": "^7.0.0", - "progress": "^2.0.0", - "regexpp": "^1.0.1", - "require-uncached": "^1.0.3", - "semver": "^5.3.0", - "strip-ansi": "^4.0.0", - "strip-json-comments": "~2.0.1", - "table": "4.0.2", - "text-table": "~0.2.0" - } - }, - "eslint-config-prettier": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-2.9.0.tgz", - "integrity": "sha512-ag8YEyBXsm3nmOv1Hz991VtNNDMRa+MNy8cY47Pl4bw6iuzqKbJajXdqUpiw13STdLLrznxgm1hj9NhxeOYq0A==", - "dev": true, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "optional": true, "requires": { - "get-stdin": "^5.0.1" + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" }, "dependencies": { - "get-stdin": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", - "dev": true + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true } } }, - "eslint-config-xo": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/eslint-config-xo/-/eslint-config-xo-0.20.1.tgz", - "integrity": "sha512-bhDRezvlbYNZn8SHv0WE8aPsdPtH3sq1IU2SznyOtmRwi6e/XQkzs+Kaw1hA9Pz4xmkG796egIsFY2RD6fwUeQ==", - "dev": true - }, - "eslint-plugin-prettier": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-2.6.0.tgz", - "integrity": "sha512-floiaI4F7hRkTrFe8V2ItOK97QYrX75DjmdzmVITZoAP6Cn06oEDPQRsO6MlHEP/u2SxI3xQ52Kpjw6j5WGfeQ==", - "dev": true, - "requires": { - "fast-diff": "^1.1.1", - "jest-docblock": "^21.0.0" - } - }, - "eslint-scope": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-3.7.1.tgz", - "integrity": "sha1-PWPD7f2gLgbgGkUq2IyqzHzctug=", - "dev": true, - "requires": { - "esrecurse": "^4.1.0", - "estraverse": "^4.1.1" - } - }, - "eslint-visitor-keys": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz", - "integrity": "sha512-qzm/XxIbxm/FHyH341ZrbnMUpe+5Bocte9xkmFMzPMjRaZMcXww+MpBptFvtU+79L362nqiLhekCxCxDPaUMBQ==", - "dev": true - }, - "espree": { - "version": "3.5.4", - "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", - "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", - "dev": true, - "requires": { - "acorn": "^5.5.0", - "acorn-jsx": "^3.0.0" - } - }, - "esprima": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.0.tgz", - "integrity": "sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw==", - "dev": true - }, - "esquery": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz", - "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==", - "dev": true, - "requires": { - "estraverse": "^4.0.0" - } - }, - "esrecurse": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", - "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==", - "dev": true, - "requires": { - "estraverse": "^4.1.0" - } - }, - "estraverse": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.2.0.tgz", - "integrity": "sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=", - "dev": true - }, - "esutils": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.2.tgz", - "integrity": "sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=", - "dev": true - }, - "exec-sh": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/exec-sh/-/exec-sh-0.2.1.tgz", - "integrity": "sha512-aLt95pexaugVtQerpmE51+4QfWrNc304uez7jvj6fWnN8GeEHpttB8F36n8N7uVhUMbH/1enbxQ9HImZ4w/9qg==", - "dev": true, - "requires": { - "merge": "^1.1.3" - } - }, - "execa": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha1-BjJjj42HfMghB9MKD/8aF8uhzQw=", - "dev": true - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true, - "requires": { - "is-posix-bracket": "^0.1.0" - } - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true, - "requires": { - "fill-range": "^2.1.0" - } - }, - "expect": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/expect/-/expect-22.4.3.tgz", - "integrity": "sha512-XcNXEPehqn8b/jm8FYotdX0YrXn36qp4HWlrVT4ktwQas1l1LPxiVWncYnnL2eyMtKAmVIaG0XAp0QlrqJaxaA==", - "dev": true, - "requires": { - "ansi-styles": "^3.2.0", - "jest-diff": "^22.4.3", - "jest-get-type": "^22.4.3", - "jest-matcher-utils": "^22.4.3", - "jest-message-util": "^22.4.3", - "jest-regex-util": "^22.4.3" - } - }, "extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "dev": true + "optional": true }, "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "dev": true, + "optional": true, "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -1989,7 +1042,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -1997,196 +1050,248 @@ } }, "external-editor": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-2.2.0.tgz", - "integrity": "sha512-bSn6gvGxKt+b7+6TKEv1ZycHleA7aHhRHyAqJyp5pbUFuYYNIzpZnQDk7AsYckyWdEnTeAnay0aCy2aV6iTk9A==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "optional": true, "requires": { - "chardet": "^0.4.0", - "iconv-lite": "^0.4.17", + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", "tmp": "^0.0.33" } }, "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true - }, - "fast-deep-equal": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", - "integrity": "sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ=", - "dev": true - }, - "fast-diff": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.1.2.tgz", - "integrity": "sha512-KaJUt+M9t1qaIteSvjc6P3RbMdXsNhK61GRftR6SNxqmhthcd9MGIi4T+o0jD8LUSpSnSKXE20nLtJ3fOHxQig==", - "dev": true - }, - "fast-json-stable-stringify": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", - "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=", - "dev": true - }, - "fast-levenshtein": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", - "dev": true - }, - "fb-watchman": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", - "integrity": "sha1-VOmr99+i8mzZsWNsWIwa/AXeXVg=", - "dev": true, - "requires": { - "bser": "^2.0.0" - } - }, - "figures": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz", - "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "optional": true, "requires": { - "escape-string-regexp": "^1.0.5" - } + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } }, - "file-entry-cache": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", - "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", - "dev": true, + "extsprintf": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", + "optional": true + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "optional": true + }, + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "optional": true, "requires": { - "flat-cache": "^1.2.1", - "object-assign": "^4.0.1" + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" } }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "optional": true }, - "fileset": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/fileset/-/fileset-2.0.3.tgz", - "integrity": "sha1-jnVIqW08wjJ+5eZ0FocjozO7oqA=", - "dev": true, + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "optional": true, "requires": { - "glob": "^7.0.3", - "minimatch": "^3.0.3" + "escape-string-regexp": "^1.0.5" } }, "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true, - "requires": { - "is-number": "^2.1.0", - "isobject": "^2.0.0", - "randomatic": "^1.1.3", - "repeat-element": "^1.1.2", - "repeat-string": "^1.5.2" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } } }, - "find-parent-dir": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/find-parent-dir/-/find-parent-dir-0.3.0.tgz", - "integrity": "sha1-M8RLQpqysvBkYpnF+fcY83b/jVQ=", - "dev": true - }, "find-up": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", "requires": { - "locate-path": "^2.0.0" + "locate-path": "^3.0.0" } }, "first-chunk-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "optional": true, "requires": { "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } } }, - "flat-cache": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.0.tgz", - "integrity": "sha1-0wMLMrOBVPTjt+nHCfSQ9++XxIE=", - "dev": true, + "follow-redirects": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", + "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", + "optional": true, "requires": { - "circular-json": "^0.3.1", - "del": "^2.0.2", - "graceful-fs": "^4.1.2", - "write": "^0.2.1" + "debug": "=3.1.0" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", + "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true + } } }, "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true, - "requires": { - "for-in": "^1.0.1" - } - }, - "foreach": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", - "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", - "dev": true + "optional": true }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "optional": true }, "form-data": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.2.tgz", - "integrity": "sha1-SXBJi+YEwgwAXU9cI67NIda0kJk=", - "dev": true, + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", + "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", + "optional": true, "requires": { "asynckit": "^0.4.0", - "combined-stream": "1.0.6", + "combined-stream": "^1.0.6", "mime-types": "^2.1.12" } }, - "formatio": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/formatio/-/formatio-1.2.0.tgz", - "integrity": "sha1-87IWfZBoxGmKjVH092CjmlTYGOs=", - "dev": true, - "requires": { - "samsam": "1.x" - } - }, "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "dev": true, + "optional": true, "requires": { "map-cache": "^0.2.2" } @@ -2196,2377 +1301,590 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, - "fsevents": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.3.tgz", - "integrity": "sha512-WIr7iDkdmdbxu/Gh6eKEZJL6KPE74/5MEsf2whTOFNxbIoIixogroLdKYqB6FDav4Wavh/lZdzzd3b2KxIXC5Q==", - "dev": true, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "optional": true + }, + "getpass": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "optional": true, "requires": { - "nan": "^2.3.0", - "node-pre-gyp": "^0.6.39" - }, - "dependencies": { - "abbrev": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "ajv": { - "version": "4.11.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "co": "^4.6.0", - "json-stable-stringify": "^1.0.1" - } - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "aproba": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "asn1": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "block-stream": { - "version": "0.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "inherits": "~2.0.0" - } - }, - "boom": { - "version": "2.10.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.x.x" - } - }, - "brace-expansion": { - "version": "1.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "balanced-match": "^0.4.1", - "concat-map": "0.0.1" - } - }, - "buffer-shims": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "caseless": { - "version": "0.12.0", - "bundled": true, - "dev": true, - "optional": true - }, - "co": { - "version": "4.6.0", - "bundled": true, - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "combined-stream": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "dev": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "cryptiles": { - "version": "2.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.x.x" - } - }, - "dashdash": { - "version": "1.14.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "debug": { - "version": "2.6.8", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "bundled": true, - "dev": true, - "optional": true - }, - "delayed-stream": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "ecc-jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, - "extsprintf": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "forever-agent": { - "version": "0.6.1", - "bundled": true, - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.5", - "mime-types": "^2.1.12" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "fstream": { - "version": "1.0.11", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "graceful-fs": "^4.1.2", - "inherits": "~2.0.0", - "mkdirp": ">=0.5 0", - "rimraf": "2" - } - }, - "fstream-ignore": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fstream": "^1.0.0", - "inherits": "2", - "minimatch": "^3.0.0" - } - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "getpass": { - "version": "0.1.7", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "glob": { - "version": "7.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.1.11", - "bundled": true, - "dev": true, - "optional": true - }, - "har-schema": { - "version": "1.0.5", - "bundled": true, - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ajv": "^4.9.1", - "har-schema": "^1.0.5" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "hawk": { - "version": "3.1.3", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "boom": "2.x.x", - "cryptiles": "2.x.x", - "hoek": "2.x.x", - "sntp": "1.x.x" - } - }, - "hoek": { - "version": "2.16.3", - "bundled": true, - "dev": true, - "optional": true - }, - "http-signature": { - "version": "1.1.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "^0.2.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "bundled": true, - "dev": true, - "optional": true - }, - "ini": { - "version": "1.3.4", - "bundled": true, - "dev": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-typedarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "isstream": { - "version": "0.1.2", - "bundled": true, - "dev": true, - "optional": true - }, - "jodid25519": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsbn": "~0.1.0" - } - }, - "jsbn": { - "version": "0.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "json-schema": { - "version": "0.2.3", - "bundled": true, - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stringify-safe": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "jsonify": { - "version": "0.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.0.2", - "json-schema": "0.2.3", - "verror": "1.3.6" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "mime-db": { - "version": "1.27.0", - "bundled": true, - "dev": true, - "optional": true - }, - "mime-types": { - "version": "2.1.15", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "mime-db": "~1.27.0" - } - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "0.0.8", - "bundled": true, - "dev": true, - "optional": true - }, - "mkdirp": { - "version": "0.5.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "minimist": "0.0.8" - } - }, - "ms": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "node-pre-gyp": { - "version": "0.6.39", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "hawk": "3.1.3", - "mkdirp": "^0.5.1", - "nopt": "^4.0.1", - "npmlog": "^4.0.2", - "rc": "^1.1.7", - "request": "2.81.0", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^2.2.1", - "tar-pack": "^3.4.0" - } - }, - "nopt": { - "version": "4.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npmlog": { - "version": "4.1.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "oauth-sign": { - "version": "0.8.2", - "bundled": true, - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "dev": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "osenv": { - "version": "0.1.4", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "performance-now": { - "version": "0.2.0", - "bundled": true, - "dev": true, - "optional": true - }, - "process-nextick-args": { - "version": "1.0.7", - "bundled": true, - "dev": true, - "optional": true - }, - "punycode": { - "version": "1.4.1", - "bundled": true, - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "bundled": true, - "dev": true, - "optional": true - }, - "rc": { - "version": "1.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - }, - "dependencies": { - "minimist": { - "version": "1.2.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "readable-stream": { - "version": "2.2.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "buffer-shims": "~1.0.0", - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "string_decoder": "~1.0.0", - "util-deprecate": "~1.0.1" - } - }, - "request": { - "version": "2.81.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "aws-sign2": "~0.6.0", - "aws4": "^1.2.1", - "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.0", - "forever-agent": "~0.6.1", - "form-data": "~2.1.1", - "har-validator": "~4.2.1", - "hawk": "~3.1.3", - "http-signature": "~1.1.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.7", - "oauth-sign": "~0.8.1", - "performance-now": "^0.2.0", - "qs": "~6.4.0", - "safe-buffer": "^5.0.1", - "stringstream": "~0.0.4", - "tough-cookie": "~2.3.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.0.0" - } - }, - "rimraf": { - "version": "2.6.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "glob": "^7.0.5" - } - }, - "safe-buffer": { - "version": "5.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "semver": { - "version": "5.3.0", - "bundled": true, - "dev": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "dev": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "sntp": { - "version": "1.0.9", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "hoek": "2.x.x" - } - }, - "sshpk": { - "version": "1.13.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jodid25519": "^1.0.0", - "jsbn": "~0.1.0", - "tweetnacl": "~0.14.0" - }, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "tar": { - "version": "2.2.1", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "block-stream": "*", - "fstream": "^1.0.2", - "inherits": "2" - } - }, - "tar-pack": { - "version": "3.4.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "debug": "^2.2.0", - "fstream": "^1.0.10", - "fstream-ignore": "^1.0.5", - "once": "^1.3.3", - "readable-stream": "^2.1.4", - "rimraf": "^2.5.1", - "tar": "^2.2.1", - "uid-number": "^0.0.6" - } - }, - "tough-cookie": { - "version": "2.3.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "punycode": "^1.4.1" - } - }, - "tunnel-agent": { - "version": "0.6.0", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "bundled": true, - "dev": true, - "optional": true - }, - "uid-number": { - "version": "0.0.6", - "bundled": true, - "dev": true, - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - }, - "uuid": { - "version": "3.0.1", - "bundled": true, - "dev": true, - "optional": true - }, - "verror": { - "version": "1.3.6", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "extsprintf": "1.0.2" - } - }, - "wide-align": { - "version": "1.1.2", - "bundled": true, - "dev": true, - "optional": true, - "requires": { - "string-width": "^1.0.2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "dev": true, - "optional": true - } - } - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", - "dev": true - }, - "functional-red-black-tree": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", - "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "get-own-enumerable-property-symbols": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-2.0.1.tgz", - "integrity": "sha512-TtY/sbOemiMKPRUDDanGCSgBYe7Mf0vbRsWnBZ+9yghpZ1MvcpSpuZFjHdEeY/LZjZy0vdLjS77L6HosisFiug==", - "dev": true - }, - "get-stdin": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "gh-got": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-6.0.0.tgz", - "integrity": "sha512-F/mS+fsWQMo1zfgG9MD8KWvTWPPzzhuVwY++fhQ5Ggd+0P+CAMHtzMZhNxG+TqGfHDChJKsbh6otfMGqO2AKBw==", - "requires": { - "got": "^7.0.0", - "is-plain-obj": "^1.1.0" - } - }, - "github-username": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-4.1.0.tgz", - "integrity": "sha1-y+KABBiDIG2kISrp5LXxacML9Bc=", - "requires": { - "gh-got": "^6.0.0" - } - }, - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true, - "requires": { - "glob-parent": "^2.0.0", - "is-glob": "^2.0.0" - } - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true, - "requires": { - "is-glob": "^2.0.0" - } - }, - "globals": { - "version": "11.4.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-11.4.0.tgz", - "integrity": "sha512-Dyzmifil8n/TmSqYDEXbm+C8yitzJQqQIlJQLNRMwa+BOUJpRC19pyVeN12JAjt61xonvXjtff+hJruTRXn5HA==", - "dev": true - }, - "globby": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", - "requires": { - "array-union": "^1.0.1", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - } - } - }, - "got": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/got/-/got-7.1.0.tgz", - "integrity": "sha512-Y5WMo7xKKq1muPsxD+KmrR8DH5auG7fBdDVueZwETwV6VytKyU9OX/ddpq2/1hp1vIPvVb4T81dKQz3BivkNLw==", - "requires": { - "decompress-response": "^3.2.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-plain-obj": "^1.1.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "isurl": "^1.0.0-alpha5", - "lowercase-keys": "^1.0.0", - "p-cancelable": "^0.3.0", - "p-timeout": "^1.1.1", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "url-parse-lax": "^1.0.0", - "url-to-options": "^1.0.1" - } - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=" - }, - "grouped-queue": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-0.3.3.tgz", - "integrity": "sha1-wWfSpTGcWg4JZO9qJbfC34mWyFw=", - "requires": { - "lodash": "^4.17.2" - } - }, - "growly": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/growly/-/growly-1.3.0.tgz", - "integrity": "sha1-8QdIy+dq+WS3yWyTxrzCivEgwIE=", - "dev": true - }, - "handlebars": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz", - "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==", - "dev": true, - "requires": { - "neo-async": "^2.6.0", - "optimist": "^0.6.1", - "source-map": "^0.6.1", - "uglify-js": "^3.1.4" - }, - "dependencies": { - "commander": { - "version": "2.20.3", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", - "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", - "dev": true, - "optional": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - }, - "uglify-js": { - "version": "3.6.9", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz", - "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==", - "dev": true, - "optional": true, - "requires": { - "commander": "~2.20.3", - "source-map": "~0.6.1" - } - } - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true - }, - "har-validator": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.0.3.tgz", - "integrity": "sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=", - "dev": true, - "requires": { - "ajv": "^5.1.0", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.1.tgz", - "integrity": "sha1-hGFzP1OLCDfJNh45qauelwTcLyg=", - "dev": true, - "requires": { - "function-bind": "^1.0.2" + "assert-plus": "^1.0.0" } }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "gh-got": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", + "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", "requires": { - "ansi-regex": "^2.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - } + "got": "^6.2.0", + "is-plain-obj": "^1.1.0" } }, - "has-flag": { + "github-username": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "has-symbol-support-x": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz", - "integrity": "sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==" - }, - "has-to-string-tag-x": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz", - "integrity": "sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==", - "requires": { - "has-symbol-support-x": "^1.4.1" - } - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "dev": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hawk": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-6.0.2.tgz", - "integrity": "sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==", - "dev": true, - "requires": { - "boom": "4.x.x", - "cryptiles": "3.x.x", - "hoek": "4.x.x", - "sntp": "2.x.x" - } - }, - "hoek": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-4.2.1.tgz", - "integrity": "sha512-QLg82fGkfnJ/4iy1xZ81/9SIJiq1NGFUMGs6ParyjBZr6jW2Ufj/snDqTHixNlHdPNwN2RLVD0Pi3igeK9+JfA==", - "dev": true - }, - "home-or-tmp": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/home-or-tmp/-/home-or-tmp-2.0.0.tgz", - "integrity": "sha1-42w/LSyufXRqhX440Y1fMqeILbg=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.1" - } - }, - "hosted-git-info": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.6.0.tgz", - "integrity": "sha512-lIbgIIQA3lz5XaB6vxakj6sDHADJiZadYEJB+FgA+C4nubM1NwcuvUr9EJPmnH1skZqpqUzWborWo8EIUi0Sdw==" - }, - "html-encoding-sniffer": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-1.0.2.tgz", - "integrity": "sha512-71lZziiDnsuabfdYiUeWdCVyKuqwWi23L8YeIgV9jSSZHCtb6wB1BKWooH7L3tn4/FuZJMVWyNaIDr4RGmaSYw==", - "dev": true, - "requires": { - "whatwg-encoding": "^1.0.1" - } - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "dev": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "husky": { - "version": "0.14.3", - "resolved": "https://registry.npmjs.org/husky/-/husky-0.14.3.tgz", - "integrity": "sha512-e21wivqHpstpoiWA/Yi8eFti8E+sQDSS53cpJsPptPs295QTOQR0ZwnHo2TXy1XOpZFD9rPOd3NpmqTK6uMLJA==", - "dev": true, - "requires": { - "is-ci": "^1.0.10", - "normalize-path": "^1.0.0", - "strip-indent": "^2.0.0" - } - }, - "iconv-lite": { - "version": "0.4.21", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.21.tgz", - "integrity": "sha512-En5V9za5mBt2oUA03WGD3TwDv0MKAruqsuxstbMUZaj9W9k/m1CV/9py3l0L5kw9Bln8fdHQmzHSYtvpvTLpKw==", - "requires": { - "safer-buffer": "^2.1.0" - } - }, - "ignore": { - "version": "3.3.7", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.7.tgz", - "integrity": "sha512-YGG3ejvBNHRqu0559EOxxNFihD0AjpvHlC/pdGKd3X3ofe+CoJkYazwNJYTNebqpPKN+VVQbh4ZFn1DivMNuHA==", - "dev": true - }, - "import-local": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/import-local/-/import-local-1.0.0.tgz", - "integrity": "sha512-vAaZHieK9qjGo58agRBg+bhHX3hoTZU/Oa3GESWLz7t1U62fk63aHuDJJEteXoDeTCcPmUT+z38gkHPZkkmpmQ==", - "dev": true, - "requires": { - "pkg-dir": "^2.0.0", - "resolve-cwd": "^2.0.0" - } - }, - "imurmurhash": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", - "dev": true - }, - "indent-string": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", - "dev": true, + "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", + "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", "requires": { - "repeating": "^2.0.0" + "gh-got": "^5.0.0" } }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - }, - "inquirer": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-3.3.0.tgz", - "integrity": "sha512-h+xtnyk4EwKvFWHrUYsWErEVR+igKtLdchu+o0Z1RL7VU/jVMFbYir2bp6bAj8efFNxWqHX0dIss6fJQ+/+qeQ==", - "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.0", - "cli-cursor": "^2.1.0", - "cli-width": "^2.0.0", - "external-editor": "^2.0.4", - "figures": "^2.0.0", - "lodash": "^4.3.0", - "mute-stream": "0.0.7", - "run-async": "^2.2.0", - "rx-lite": "^4.0.8", - "rx-lite-aggregates": "^4.0.8", - "string-width": "^2.1.0", - "strip-ansi": "^4.0.0", - "through": "^2.3.6" - } - }, - "interpret": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.1.0.tgz", - "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=" - }, - "invariant": { - "version": "2.2.4", - "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", - "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", - "dev": true, - "requires": { - "loose-envify": "^1.0.0" - } - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "requires": { - "builtin-modules": "^1.0.0" - } - }, - "is-callable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", - "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", - "dev": true - }, - "is-ci": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-1.1.0.tgz", - "integrity": "sha512-c7TnwxLePuqIlxHgr7xtxzycJPegNHFuIrBkwbf8hc58//+Op1CqFkyS+xnIMkwn9UsJIwc174BIjkyBmSpjKg==", - "dev": true, - "requires": { - "ci-info": "^1.0.0" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-date-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", - "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", - "dev": true - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "is-directory": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=", - "dev": true - }, - "is-dotfile": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.3.tgz", - "integrity": "sha1-pqLzL/0t+wT1yiXs0Pa4PPeYoeE=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true, - "requires": { - "is-primitive": "^2.0.0" - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-finite": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-finite/-/is-finite-1.0.2.tgz", - "integrity": "sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" - }, - "is-generator-fn": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-generator-fn/-/is-generator-fn-1.0.0.tgz", - "integrity": "sha1-lp1J4bszKfa7fwkIm+JleLLd1Go=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true, - "requires": { - "is-extglob": "^1.0.0" - } - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-obj": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=", - "dev": true - }, - "is-object": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-object/-/is-object-1.0.1.tgz", - "integrity": "sha1-iVJojF7C/9awPsyF52ngKQMINHA=" - }, - "is-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-observable/-/is-observable-0.2.0.tgz", - "integrity": "sha1-s2ExHYPG5dcmyr9eJQsCNxBvWuI=", - "dev": true, - "requires": { - "symbol-observable": "^0.2.2" + "path-is-absolute": "^1.0.0" } }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "optional": true, "requires": { - "is-number": "^4.0.0" + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" }, "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } } } }, - "is-path-cwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", - "integrity": "sha1-0iXsIxMuie3Tj9p2dHLmLmXxEG0=", - "dev": true + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "optional": true }, - "is-path-in-cwd": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-in-cwd/-/is-path-in-cwd-1.0.1.tgz", - "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", - "dev": true, + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "optional": true, "requires": { - "is-path-inside": "^1.0.0" + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" } }, - "is-path-inside": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", - "dev": true, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { - "path-is-inside": "^1.0.1" + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" } }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "optional": true }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, + "grouped-queue": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", + "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", + "optional": true, "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "lodash": "^4.17.15" } }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { + "har-schema": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=" - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=", - "dev": true + "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", + "optional": true }, - "is-regex": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", - "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", - "dev": true, + "har-validator": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", + "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "optional": true, "requires": { - "has": "^1.0.1" + "ajv": "^6.12.3", + "har-schema": "^2.0.0" } }, - "is-regexp": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-regexp/-/is-regexp-1.0.0.tgz", - "integrity": "sha1-/S2INUXEa6xaYz57mgnof6LLUGk=", - "dev": true - }, - "is-resolvable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", - "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", - "dev": true - }, - "is-retry-allowed": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz", - "integrity": "sha1-EaBgVotnM5REAz0BJaYaINVk+zQ=" - }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", "requires": { - "scoped-regex": "^1.0.0" + "function-bind": "^1.1.1" } }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-symbol": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", - "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "dev": true + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" }, - "isarray": { + "has-value": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "optional": true, "requires": { - "isarray": "1.0.0" + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" } }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true - }, - "istanbul-api": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/istanbul-api/-/istanbul-api-1.3.1.tgz", - "integrity": "sha512-duj6AlLcsWNwUpfyfHt0nWIeRiZpuShnP40YTxOGQgtaN8fd6JYSxsvxUphTDy8V5MfDXo4s/xVCIIvVCO808g==", - "dev": true, - "requires": { - "async": "^2.1.4", - "compare-versions": "^3.1.0", - "fileset": "^2.0.2", - "istanbul-lib-coverage": "^1.2.0", - "istanbul-lib-hook": "^1.2.0", - "istanbul-lib-instrument": "^1.10.1", - "istanbul-lib-report": "^1.1.4", - "istanbul-lib-source-maps": "^1.2.4", - "istanbul-reports": "^1.3.0", - "js-yaml": "^3.7.0", - "mkdirp": "^0.5.1", - "once": "^1.4.0" + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "optional": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" }, "dependencies": { - "istanbul-lib-source-maps": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.4.tgz", - "integrity": "sha512-UzuK0g1wyQijiaYQxj/CdNycFhAd2TLtO2obKQMTZrZ1jzEMRY3rvpASEKkaxbRR6brvdovfA03znPa/pXcejg==", - "dev": true, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "optional": true, "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.2.0", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" + "is-buffer": "^1.1.5" } } } }, - "istanbul-lib-coverage": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-1.2.0.tgz", - "integrity": "sha512-GvgM/uXRwm+gLlvkWHTjDAvwynZkL9ns15calTrmhGgowlwJBbWMYzWbKqE2DT6JDP1AFXKa+Zi0EkqNCUqY0A==", - "dev": true + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" }, - "istanbul-lib-hook": { + "http-signature": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-1.2.0.tgz", - "integrity": "sha512-p3En6/oGkFQV55Up8ZPC2oLxvgSxD8CzA0yBrhRZSh3pfv3OFj9aSGVC0yoerAi/O4u7jUVnOGVX1eVFM+0tmQ==", - "dev": true, + "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", + "optional": true, "requires": { - "append-transform": "^0.4.0" + "assert-plus": "^1.0.0", + "jsprim": "^1.2.2", + "sshpk": "^1.7.0" } }, - "istanbul-lib-instrument": { - "version": "1.10.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-1.10.1.tgz", - "integrity": "sha512-1dYuzkOCbuR5GRJqySuZdsmsNKPL3PTuyPevQfoCXJePT9C8y1ga75neU+Tuy9+yS3G/dgx8wgOmp2KLpgdoeQ==", - "dev": true, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "optional": true, "requires": { - "babel-generator": "^6.18.0", - "babel-template": "^6.16.0", - "babel-traverse": "^6.18.0", - "babel-types": "^6.18.0", - "babylon": "^6.18.0", - "istanbul-lib-coverage": "^1.2.0", - "semver": "^5.3.0" + "safer-buffer": ">= 2.1.2 < 3" } }, - "istanbul-lib-report": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-1.1.4.tgz", - "integrity": "sha512-Azqvq5tT0U09nrncK3q82e/Zjkxa4tkFZv7E6VcqP0QCPn6oNljDPfrZEC/umNXds2t7b8sRJfs6Kmpzt8m2kA==", - "dev": true, - "requires": { - "istanbul-lib-coverage": "^1.2.0", - "mkdirp": "^0.5.1", - "path-parse": "^1.0.5", - "supports-color": "^3.1.2" + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "optional": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" }, "dependencies": { + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "optional": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "optional": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "optional": true + }, "has-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", - "integrity": "sha1-nZ55MWXOAXoA8AQYxD+UKnsdEfo=", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "optional": true }, "supports-color": { - "version": "3.2.3", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", - "integrity": "sha1-ZawFBLOVQXHYpklGsq48u4pfVPY=", - "dev": true, + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "optional": true, "requires": { - "has-flag": "^1.0.0" + "has-flag": "^4.0.0" } } } }, - "istanbul-lib-source-maps": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.3.tgz", - "integrity": "sha512-fDa0hwU/5sDXwAklXgAoCJCOsFsBplVQ6WBldz5UwaqOzmDhUK4nfuR7/G//G2lERlblUNJB8P6e8cXq3a7MlA==", - "dev": true, - "requires": { - "debug": "^3.1.0", - "istanbul-lib-coverage": "^1.1.2", - "mkdirp": "^0.5.1", - "rimraf": "^2.6.1", - "source-map": "^0.5.3" - } + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, - "istanbul-reports": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-1.3.0.tgz", - "integrity": "sha512-y2Z2IMqE1gefWUaVjrBm0mSKvUkaBy9Vqz8iwr/r40Y9hBbIteH5wqHG/9DLTfJ9xUnUT2j7A3+VVJ6EaYBllA==", - "dev": true, + "is-accessor-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "optional": true, "requires": { - "handlebars": "^4.0.3" + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, - "istextorbinary": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.2.1.tgz", - "integrity": "sha512-TS+hoFl8Z5FAFMK38nhBkdLt44CclNRgDHWeMgsV8ko3nDlr/9UI2Sf839sW7enijf8oKsZYXRvM8g0it9Zmcw==", - "requires": { - "binaryextensions": "2", - "editions": "^1.3.3", - "textextensions": "2" - } + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, - "isurl": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isurl/-/isurl-1.0.0.tgz", - "integrity": "sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==", + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "optional": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", "requires": { - "has-to-string-tag-x": "^1.2.0", - "is-object": "^1.0.1" + "has": "^1.0.3" } }, - "jest": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest/-/jest-22.4.3.tgz", - "integrity": "sha512-FFCdU/pXOEASfHxFDOWUysI/+FFoqiXJADEIXgDKuZyqSmBD3tZ4BEGH7+M79v7czj7bbkhwtd2LaEDcJiM/GQ==", - "dev": true, + "is-data-descriptor": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "optional": true, "requires": { - "import-local": "^1.0.0", - "jest-cli": "^22.4.3" + "kind-of": "^3.0.2" }, "dependencies": { - "jest-cli": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-22.4.3.tgz", - "integrity": "sha512-IiHybF0DJNqZPsbjn4Cy4vcqcmImpoFwNFnkehzVw8lTUSl4axZh5DHewu5bdpZF2Y5gUqFKYzH0FH4Qx2k+UA==", - "dev": true, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, "requires": { - "ansi-escapes": "^3.0.0", - "chalk": "^2.0.1", - "exit": "^0.1.2", - "glob": "^7.1.2", - "graceful-fs": "^4.1.11", - "import-local": "^1.0.0", - "is-ci": "^1.0.10", - "istanbul-api": "^1.1.14", - "istanbul-lib-coverage": "^1.1.1", - "istanbul-lib-instrument": "^1.8.0", - "istanbul-lib-source-maps": "^1.2.1", - "jest-changed-files": "^22.4.3", - "jest-config": "^22.4.3", - "jest-environment-jsdom": "^22.4.3", - "jest-get-type": "^22.4.3", - "jest-haste-map": "^22.4.3", - "jest-message-util": "^22.4.3", - "jest-regex-util": "^22.4.3", - "jest-resolve-dependencies": "^22.4.3", - "jest-runner": "^22.4.3", - "jest-runtime": "^22.4.3", - "jest-snapshot": "^22.4.3", - "jest-util": "^22.4.3", - "jest-validate": "^22.4.3", - "jest-worker": "^22.4.3", - "micromatch": "^2.3.11", - "node-notifier": "^5.2.1", - "realpath-native": "^1.0.0", - "rimraf": "^2.5.4", - "slash": "^1.0.0", - "string-length": "^2.0.0", - "strip-ansi": "^4.0.0", - "which": "^1.2.12", - "yargs": "^10.0.3" + "is-buffer": "^1.1.5" } } } }, - "jest-changed-files": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-22.4.3.tgz", - "integrity": "sha512-83Dh0w1aSkUNFhy5d2dvqWxi/y6weDwVVLU6vmK0cV9VpRxPzhTeGimbsbRDSnEoszhF937M4sDLLeS7Cu/Tmw==", - "dev": true, - "requires": { - "throat": "^4.0.0" - } - }, - "jest-config": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-22.4.3.tgz", - "integrity": "sha512-KSg3EOToCgkX+lIvenKY7J8s426h6ahXxaUFJxvGoEk0562Z6inWj1TnKoGycTASwiLD+6kSYFALcjdosq9KIQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "glob": "^7.1.1", - "jest-environment-jsdom": "^22.4.3", - "jest-environment-node": "^22.4.3", - "jest-get-type": "^22.4.3", - "jest-jasmine2": "^22.4.3", - "jest-regex-util": "^22.4.3", - "jest-resolve": "^22.4.3", - "jest-util": "^22.4.3", - "jest-validate": "^22.4.3", - "pretty-format": "^22.4.3" - } - }, - "jest-diff": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-22.4.3.tgz", - "integrity": "sha512-/QqGvCDP5oZOF6PebDuLwrB2BMD8ffJv6TAGAdEVuDx1+uEgrHpSFrfrOiMRx2eJ1hgNjlQrOQEHetVwij90KA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "diff": "^3.2.0", - "jest-get-type": "^22.4.3", - "pretty-format": "^22.4.3" - } - }, - "jest-docblock": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-21.2.0.tgz", - "integrity": "sha512-5IZ7sY9dBAYSV+YjQ0Ovb540Ku7AO9Z5o2Cg789xj167iQuZ2cG+z0f3Uct6WeYLbU6aQiM2pCs7sZ+4dotydw==", - "dev": true - }, - "jest-environment-jsdom": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-22.4.3.tgz", - "integrity": "sha512-FviwfR+VyT3Datf13+ULjIMO5CSeajlayhhYQwpzgunswoaLIPutdbrnfUHEMyJCwvqQFaVtTmn9+Y8WCt6n1w==", - "dev": true, - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3", - "jsdom": "^11.5.1" - } - }, - "jest-environment-node": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-22.4.3.tgz", - "integrity": "sha512-reZl8XF6t/lMEuPWwo9OLfttyC26A5AMgDyEQ6DBgZuyfyeNUzYT8BFo6uxCCP/Av/b7eb9fTi3sIHFPBzmlRA==", - "dev": true, - "requires": { - "jest-mock": "^22.4.3", - "jest-util": "^22.4.3" - } - }, - "jest-get-type": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-22.4.3.tgz", - "integrity": "sha512-/jsz0Y+V29w1chdXVygEKSz2nBoHoYqNShPe+QgxSNjAuP1i8+k4LbQNrfoliKej0P45sivkSCh7yiD6ubHS3w==", - "dev": true - }, - "jest-haste-map": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-22.4.3.tgz", - "integrity": "sha512-4Q9fjzuPVwnaqGKDpIsCSoTSnG3cteyk2oNVjBX12HHOaF1oxql+uUiqZb5Ndu7g/vTZfdNwwy4WwYogLh29DQ==", - "dev": true, - "requires": { - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.1.11", - "jest-docblock": "^22.4.3", - "jest-serializer": "^22.4.3", - "jest-worker": "^22.4.3", - "micromatch": "^2.3.11", - "sane": "^2.0.0" + "is-descriptor": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" }, "dependencies": { - "jest-docblock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-22.4.3.tgz", - "integrity": "sha512-uPKBEAw7YrEMcXueMKZXn/rbMxBiSv48fSqy3uEnmgOlQhSX+lthBqHb1fKWNVmFqAp9E/RsSdBfiV31LbzaOg==", - "dev": true, - "requires": { - "detect-newline": "^2.1.0" - } + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "optional": true } } }, - "jest-jasmine2": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-22.4.3.tgz", - "integrity": "sha512-yZCPCJUcEY6R5KJB/VReo1AYI2b+5Ky+C+JA1v34jndJsRcLpU4IZX4rFJn7yDTtdNbO/nNqg+3SDIPNH2ecnw==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "co": "^4.6.0", - "expect": "^22.4.3", - "graceful-fs": "^4.1.11", - "is-generator-fn": "^1.0.0", - "jest-diff": "^22.4.3", - "jest-matcher-utils": "^22.4.3", - "jest-message-util": "^22.4.3", - "jest-snapshot": "^22.4.3", - "jest-util": "^22.4.3", - "source-map-support": "^0.5.0" - } - }, - "jest-leak-detector": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-22.4.3.tgz", - "integrity": "sha512-NZpR/Ls7+ndO57LuXROdgCGz2RmUdC541tTImL9bdUtU3WadgFGm0yV+Ok4Fuia/1rLAn5KaJ+i76L6e3zGJYQ==", - "dev": true, - "requires": { - "pretty-format": "^22.4.3" - } - }, - "jest-matcher-utils": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-22.4.3.tgz", - "integrity": "sha512-lsEHVaTnKzdAPR5t4B6OcxXo9Vy4K+kRRbG5gtddY8lBEC+Mlpvm1CJcsMESRjzUhzkz568exMV1hTB76nAKbA==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^22.4.3", - "pretty-format": "^22.4.3" - } - }, - "jest-message-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-22.4.3.tgz", - "integrity": "sha512-iAMeKxhB3Se5xkSjU0NndLLCHtP4n+GtCqV0bISKA5dmOXQfEbdEmYiu2qpnWBDCQdEafNDDU6Q+l6oBMd/+BA==", - "dev": true, - "requires": { - "@babel/code-frame": "^7.0.0-beta.35", - "chalk": "^2.0.1", - "micromatch": "^2.3.11", - "slash": "^1.0.0", - "stack-utils": "^1.0.1" - } - }, - "jest-mock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-22.4.3.tgz", - "integrity": "sha512-+4R6mH5M1G4NK16CKg9N1DtCaFmuxhcIqF4lQK/Q1CIotqMs/XBemfpDPeVZBFow6iyUNu6EBT9ugdNOTT5o5Q==", - "dev": true - }, - "jest-regex-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-22.4.3.tgz", - "integrity": "sha512-LFg1gWr3QinIjb8j833bq7jtQopiwdAs67OGfkPrvy7uNUbVMfTXXcOKXJaeY5GgjobELkKvKENqq1xrUectWg==", - "dev": true - }, - "jest-resolve": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-22.4.3.tgz", - "integrity": "sha512-u3BkD/MQBmwrOJDzDIaxpyqTxYH+XqAXzVJP51gt29H8jpj3QgKof5GGO2uPGKGeA1yTMlpbMs1gIQ6U4vcRhw==", - "dev": true, - "requires": { - "browser-resolve": "^1.11.2", - "chalk": "^2.0.1" + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "optional": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "optional": true, + "requires": { + "is-extglob": "^2.1.1" } }, - "jest-resolve-dependencies": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-22.4.3.tgz", - "integrity": "sha512-06czCMVToSN8F2U4EvgSB1Bv/56gc7MpCftZ9z9fBgUQM7dzHGCMBsyfVA6dZTx8v0FDcnALf7hupeQxaBCvpA==", - "dev": true, - "requires": { - "jest-regex-util": "^22.4.3" - } - }, - "jest-runner": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-22.4.3.tgz", - "integrity": "sha512-U7PLlQPRlWNbvOHWOrrVay9sqhBJmiKeAdKIkvX4n1G2tsvzLlf77nBD28GL1N6tGv4RmuTfI8R8JrkvCa+IBg==", - "dev": true, - "requires": { - "exit": "^0.1.2", - "jest-config": "^22.4.3", - "jest-docblock": "^22.4.3", - "jest-haste-map": "^22.4.3", - "jest-jasmine2": "^22.4.3", - "jest-leak-detector": "^22.4.3", - "jest-message-util": "^22.4.3", - "jest-runtime": "^22.4.3", - "jest-util": "^22.4.3", - "jest-worker": "^22.4.3", - "throat": "^4.0.0" + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" }, "dependencies": { - "jest-docblock": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-22.4.3.tgz", - "integrity": "sha512-uPKBEAw7YrEMcXueMKZXn/rbMxBiSv48fSqy3uEnmgOlQhSX+lthBqHb1fKWNVmFqAp9E/RsSdBfiV31LbzaOg==", - "dev": true, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, "requires": { - "detect-newline": "^2.1.0" + "is-buffer": "^1.1.5" } } } }, - "jest-runtime": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-22.4.3.tgz", - "integrity": "sha512-Eat/esQjevhx9BgJEC8udye+FfoJ2qvxAZfOAWshYGS22HydHn5BgsvPdTtt9cp0fSl5LxYOFA1Pja9Iz2Zt8g==", - "dev": true, - "requires": { - "babel-core": "^6.0.0", - "babel-jest": "^22.4.3", - "babel-plugin-istanbul": "^4.1.5", - "chalk": "^2.0.1", - "convert-source-map": "^1.4.0", - "exit": "^0.1.2", - "graceful-fs": "^4.1.11", - "jest-config": "^22.4.3", - "jest-haste-map": "^22.4.3", - "jest-regex-util": "^22.4.3", - "jest-resolve": "^22.4.3", - "jest-util": "^22.4.3", - "jest-validate": "^22.4.3", - "json-stable-stringify": "^1.0.1", - "micromatch": "^2.3.11", - "realpath-native": "^1.0.0", - "slash": "^1.0.0", - "strip-bom": "3.0.0", - "write-file-atomic": "^2.1.0", - "yargs": "^10.0.3" - } - }, - "jest-serializer": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-serializer/-/jest-serializer-22.4.3.tgz", - "integrity": "sha512-uPaUAppx4VUfJ0QDerpNdF43F68eqKWCzzhUlKNDsUPhjOon7ZehR4C809GCqh765FoMRtTVUVnGvIoskkYHiw==", - "dev": true - }, - "jest-snapshot": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-22.4.3.tgz", - "integrity": "sha512-JXA0gVs5YL0HtLDCGa9YxcmmV2LZbwJ+0MfyXBBc5qpgkEYITQFJP7XNhcHFbUvRiniRpRbGVfJrOoYhhGE0RQ==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-diff": "^22.4.3", - "jest-matcher-utils": "^22.4.3", - "mkdirp": "^0.5.1", - "natural-compare": "^1.4.0", - "pretty-format": "^22.4.3" - } - }, - "jest-util": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-22.4.3.tgz", - "integrity": "sha512-rfDfG8wyC5pDPNdcnAlZgwKnzHvZDu8Td2NJI/jAGKEGxJPYiE4F0ss/gSAkG4778Y23Hvbz+0GMrDJTeo7RjQ==", - "dev": true, - "requires": { - "callsites": "^2.0.0", - "chalk": "^2.0.1", - "graceful-fs": "^4.1.11", - "is-ci": "^1.0.10", - "jest-message-util": "^22.4.3", - "mkdirp": "^0.5.1", - "source-map": "^0.6.0" - }, - "dependencies": { - "callsites": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=", - "dev": true - }, - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" }, - "jest-validate": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-22.4.3.tgz", - "integrity": "sha512-CfFM18W3GSP/xgmA4UouIx0ljdtfD2mjeBC6c89Gg17E44D4tQhAcTrZmf9djvipwU30kSTnk6CzcxdCCeSXfA==", - "dev": true, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "optional": true, "requires": { - "chalk": "^2.0.1", - "jest-config": "^22.4.3", - "jest-get-type": "^22.4.3", - "leven": "^2.1.0", - "pretty-format": "^22.4.3" + "isobject": "^3.0.1" } }, - "jest-worker": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-22.4.3.tgz", - "integrity": "sha512-B1ucW4fI8qVAuZmicFxI1R3kr2fNeYJyvIQ1rKcuLYnenFV5K5aMbxFj6J0i00Ju83S8jP2d7Dz14+AvbIHRYQ==", - "dev": true, + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-scoped": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", + "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", + "optional": true, "requires": { - "merge-stream": "^1.0.1" + "scoped-regex": "^1.0.0" } }, - "js-tokens": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", - "dev": true + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-typedarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", + "optional": true + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "optional": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "optional": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, - "js-yaml": { - "version": "3.13.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", - "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", - "dev": true, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "optional": true + }, + "isstream": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", + "optional": true + }, + "istextorbinary": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", + "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "binaryextensions": "^2.1.2", + "editions": "^2.2.0", + "textextensions": "^2.5.0" } }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, "optional": true }, - "jsdom": { - "version": "11.8.0", - "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-11.8.0.tgz", - "integrity": "sha512-fZZSH6P8tVqYIQl0WKpZuQljPu2cW41Uj/c9omtyGwjwZCB8c82UAi7BSQs/F1FgWovmZsoU02z3k28eHp0Cdw==", - "dev": true, - "requires": { - "abab": "^1.0.4", - "acorn": "^5.3.0", - "acorn-globals": "^4.1.0", - "array-equal": "^1.0.0", - "cssom": ">= 0.3.2 < 0.4.0", - "cssstyle": ">= 0.2.37 < 0.3.0", - "data-urls": "^1.0.0", - "domexception": "^1.0.0", - "escodegen": "^1.9.0", - "html-encoding-sniffer": "^1.0.2", - "left-pad": "^1.2.0", - "nwmatcher": "^1.4.3", - "parse5": "4.0.0", - "pn": "^1.1.0", - "request": "^2.83.0", - "request-promise-native": "^1.0.5", - "sax": "^1.2.4", - "symbol-tree": "^3.2.2", - "tough-cookie": "^2.3.3", - "w3c-hr-time": "^1.0.1", - "webidl-conversions": "^4.0.2", - "whatwg-encoding": "^1.0.3", - "whatwg-mimetype": "^2.1.0", - "whatwg-url": "^6.4.0", - "ws": "^4.0.0", - "xml-name-validator": "^3.0.0" - } - }, - "jsesc": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-1.3.0.tgz", - "integrity": "sha1-RsP+yMGJKxKwgz25vHYiF226s0s=", - "dev": true - }, - "json-parse-better-errors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", - "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==" + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" }, "json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true + "optional": true }, "json-schema-traverse": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz", - "integrity": "sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=", - "dev": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "requires": { - "jsonify": "~0.0.0" - } - }, - "json-stable-stringify-without-jsonify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", - "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", - "dev": true + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "optional": true }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true - }, - "json5": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/json5/-/json5-0.5.1.tgz", - "integrity": "sha1-Hq3nrMASA0rYTiOWdn6tn6VJWCE=", - "dev": true + "optional": true }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "optional": true }, "jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "dev": true, + "optional": true, "requires": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", @@ -4575,686 +1893,237 @@ } }, "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true, - "requires": { - "invert-kv": "^1.0.0" - } - }, - "lcov-parse": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/lcov-parse/-/lcov-parse-0.0.10.tgz", - "integrity": "sha1-GwuP+ayceIklBYK3C3ExXZ2m2aM=", - "dev": true - }, - "left-pad": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/left-pad/-/left-pad-1.3.0.tgz", - "integrity": "sha512-XI5MPzVNApjAyhQzphX8BkmKsKUxD4LdyK24iZeQGinBN9yTQT3bFlCBy/aVx2HrNcqQGsdot8ghrjyrvMCoEA==", - "dev": true - }, - "leven": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz", - "integrity": "sha1-wuep93IJTe6dNCAq6KzORoeHVYA=", - "dev": true - }, - "levn": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2" - } + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "optional": true }, - "lint-staged": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-6.1.1.tgz", - "integrity": "sha512-M/7bwLdXbeG7ZNLcasGeLMBDg60/w6obj3KOtINwJyxAxb53XGY0yH5FSZlWklEzuVbTtqtIfAajh6jYIN90AA==", - "dev": true, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "optional": true, "requires": { - "app-root-path": "^2.0.0", - "chalk": "^2.1.0", - "commander": "^2.11.0", - "cosmiconfig": "^4.0.0", - "debug": "^3.1.0", - "dedent": "^0.7.0", - "execa": "^0.8.0", - "find-parent-dir": "^0.3.0", - "is-glob": "^4.0.0", - "jest-validate": "^21.1.0", - "listr": "^0.13.0", - "lodash": "^4.17.4", - "log-symbols": "^2.0.0", - "minimatch": "^3.0.0", - "npm-which": "^3.0.1", - "p-map": "^1.1.1", - "path-is-inside": "^1.0.2", - "pify": "^3.0.0", - "staged-git-files": "1.0.0", - "stringify-object": "^3.2.0" - }, - "dependencies": { - "execa": { - "version": "0.8.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-0.8.0.tgz", - "integrity": "sha1-2NdrvBtVIX7RkP1t1J08d07PyNo=", - "dev": true, - "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" - } - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "dev": true - }, - "is-glob": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.0.tgz", - "integrity": "sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A=", - "dev": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "jest-get-type": { - "version": "21.2.0", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-21.2.0.tgz", - "integrity": "sha512-y2fFw3C+D0yjNSDp7ab1kcd6NUYfy3waPTlD8yWkAtiocJdBRQqNoRqVfMNxgj+IjT0V5cBIHJO0z9vuSSZ43Q==", - "dev": true - }, - "jest-validate": { - "version": "21.2.1", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-21.2.1.tgz", - "integrity": "sha512-k4HLI1rZQjlU+EC682RlQ6oZvLrE5SCh3brseQc24vbZTxzT/k/3urar5QMCVgjadmSO7lECeGdc6YxnM3yEGg==", - "dev": true, - "requires": { - "chalk": "^2.0.1", - "jest-get-type": "^21.2.0", - "leven": "^2.1.0", - "pretty-format": "^21.2.1" - } - }, - "pretty-format": { - "version": "21.2.1", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-21.2.1.tgz", - "integrity": "sha512-ZdWPGYAnYfcVP8yKA3zFjCn8s4/17TeYH28MXuC8vTp0o21eXjbFGcOAXZEaDaOFJjc3h2qa7HQNHNshhvoh2A==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - } - } - }, - "listr": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/listr/-/listr-0.13.0.tgz", - "integrity": "sha1-ILsLowuuZg7oTMBQPfS+PVYjiH0=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "figures": "^1.7.0", - "indent-string": "^2.1.0", - "is-observable": "^0.2.0", - "is-promise": "^2.1.0", - "is-stream": "^1.1.0", - "listr-silent-renderer": "^1.1.1", - "listr-update-renderer": "^0.4.0", - "listr-verbose-renderer": "^0.4.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "ora": "^0.2.3", - "p-map": "^1.1.1", - "rxjs": "^5.4.2", - "stream-to-observable": "^0.2.0", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-silent-renderer": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz", - "integrity": "sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4=", - "dev": true - }, - "listr-update-renderer": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/listr-update-renderer/-/listr-update-renderer-0.4.0.tgz", - "integrity": "sha1-NE2YDaLKLosUW6MFkI8yrj9MyKc=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-truncate": "^0.2.1", - "elegant-spinner": "^1.0.1", - "figures": "^1.7.0", - "indent-string": "^3.0.0", - "log-symbols": "^1.0.2", - "log-update": "^1.0.2", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "indent-string": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-3.2.0.tgz", - "integrity": "sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok=", - "dev": true - }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, - "requires": { - "chalk": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "listr-verbose-renderer": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz", - "integrity": "sha1-ggb0z21S3cWCfl/RSYng6WWTOjU=", - "dev": true, - "requires": { - "chalk": "^1.1.3", - "cli-cursor": "^1.0.2", - "date-fns": "^1.27.2", - "figures": "^1.7.0" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, - "requires": { - "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } + "set-getter": "^0.1.0" } }, - "load-json-file": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", - "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^4.0.0", - "pify": "^3.0.0", - "strip-bom": "^3.0.0" - } + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" }, "locate-path": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", "requires": { - "p-locate": "^2.0.0", + "p-locate": "^3.0.0", "path-exists": "^3.0.0" } }, "lodash": { - "version": "4.17.19", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.19.tgz", - "integrity": "sha512-JNvd8XER9GQX0v2qJgsaN/mzFCNA5BRe/j8JN9d+tWyGLSodKQHKFicdwNYzWwI3wjRnaKPsGj1XkBjx/F96DQ==" - }, - "lodash.sortby": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", - "dev": true - }, - "log-driver": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/log-driver/-/log-driver-1.2.7.tgz", - "integrity": "sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg==", - "dev": true + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" }, "log-symbols": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "optional": true, "requires": { "chalk": "^2.0.1" - } - }, - "log-update": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-update/-/log-update-1.0.2.tgz", - "integrity": "sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE=", - "dev": true, - "requires": { - "ansi-escapes": "^1.0.0", - "cli-cursor": "^1.0.2" }, "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, + "requires": { + "color-convert": "^1.9.0" + } }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "optional": true, "requires": { - "restore-cursor": "^1.0.1" + "color-name": "1.1.3" } }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "optional": true }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "optional": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" + "has-flag": "^3.0.0" } } } }, - "lolex": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-1.6.0.tgz", - "integrity": "sha1-OpoCg0UqR9dDnnJzG54H1zhuSfY=", - "dev": true - }, - "loose-envify": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.3.1.tgz", - "integrity": "sha1-0aitM/qc4OcT1l/dCsi3SNR4yEg=", - "dev": true, - "requires": { - "js-tokens": "^3.0.0" - } - }, "lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" }, "lru-cache": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.2.tgz", - "integrity": "sha512-wgeVXhrDwAWnIF/yZARsFnMBtdFXOg1b8RIrhilp+0iDYN4mdQcNZElDZ0e4B64BhaxeQ5zN7PMyvu7we1kPeQ==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "yallist": "^4.0.0" } }, "make-dir": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-1.2.0.tgz", - "integrity": "sha512-aNUAa4UMg/UougV25bbrU4ZaaKNjJ/3/xnvg/twpmKROPdKZPZ9wGgI0opdZzO8q/zUFawoUuixuOv33eZ61Iw==", - "requires": { - "pify": "^3.0.0" - } - }, - "makeerror": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/makeerror/-/makeerror-1.0.11.tgz", - "integrity": "sha1-4BpckQnyr3lmDk6LlYd5AYT1qWw=", - "dev": true, + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", "requires": { - "tmpl": "1.0.x" + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } } }, "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true + "optional": true }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "dev": true, + "optional": true, "requires": { "object-visit": "^1.0.0" } }, - "mem": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/mem/-/mem-1.1.0.tgz", - "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", - "dev": true, + "mem-fs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", + "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", + "optional": true, "requires": { - "mimic-fn": "^1.0.0" + "through2": "^3.0.0", + "vinyl": "^2.0.1", + "vinyl-file": "^3.0.0" } }, - "mem-fs": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.1.3.tgz", - "integrity": "sha1-uK6NLj/Lb10/kWXBLUVRoGXZicw=", + "mem-fs-editor": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", + "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", + "optional": true, "requires": { - "through2": "^2.0.0", - "vinyl": "^1.1.0", - "vinyl-file": "^2.0.0" + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^2.6.1", + "glob": "^7.1.4", + "globby": "^9.2.0", + "isbinaryfile": "^4.0.0", + "mkdirp": "^0.5.0", + "multimatch": "^4.0.0", + "rimraf": "^2.6.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0" }, "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "optional": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" + "minimist": "^1.2.5" } } } }, - "mem-fs-editor": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-3.0.2.tgz", - "integrity": "sha1-3Qpuryu4prN3QAZ6pUnrUwEFr58=", - "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.4.0", - "ejs": "^2.3.1", - "glob": "^7.0.3", - "globby": "^6.1.0", - "mkdirp": "^0.5.0", - "multimatch": "^2.0.0", - "rimraf": "^2.2.8", - "through2": "^2.0.0", - "vinyl": "^2.0.1" - } - }, - "merge": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/merge/-/merge-1.2.1.tgz", - "integrity": "sha512-VjFo4P5Whtj4vsLzsYBu5ayHhoHJ0UqNm7ibvShmbmoz7tGi0vXaoJbGdB+GmDMLUdg8DpQXEIeVDAe8MaABvQ==", - "dev": true - }, "merge-stream": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-1.0.1.tgz", - "integrity": "sha1-QEEgLVCKNCugAXQAjfDCUbjBNeE=", - "dev": true, - "requires": { - "readable-stream": "^2.0.1" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "optional": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "optional": true }, "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true, - "requires": { - "arr-diff": "^2.0.0", - "array-unique": "^0.2.1", - "braces": "^1.8.2", - "expand-brackets": "^0.1.4", - "extglob": "^0.3.1", - "filename-regex": "^2.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.1", - "kind-of": "^3.0.2", - "normalize-path": "^2.0.1", - "object.omit": "^2.0.0", - "parse-glob": "^3.0.4", - "regex-cache": "^0.4.2" - }, - "dependencies": { - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true, - "requires": { - "remove-trailing-separator": "^1.0.1" - } - } + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" } }, "mime-db": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", - "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==", - "dev": true + "version": "1.44.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", + "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", + "optional": true }, "mime-types": { - "version": "2.1.18", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", - "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", - "dev": true, + "version": "2.1.27", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", + "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", + "optional": true, "requires": { - "mime-db": "~1.33.0" + "mime-db": "1.44.0" } }, "mimic-fn": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz", - "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==" - }, - "mimic-response": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.0.tgz", - "integrity": "sha1-3z02Uqc/3ta5sLJBRub9BSNTRY4=" + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "optional": true }, "minimatch": { "version": "3.0.4", @@ -5265,15 +2134,15 @@ } }, "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "dev": true, + "optional": true, "requires": { "for-in": "^1.0.2", "is-extendable": "^1.0.1" @@ -5283,7 +2152,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, + "optional": true, "requires": { "is-plain-object": "^2.0.4" } @@ -5291,474 +2160,132 @@ } }, "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "requires": { - "minimist": "0.0.8" - }, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=" - } - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "optional": true }, "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" }, "multimatch": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-2.1.0.tgz", - "integrity": "sha1-nHkGoi+0wCkZ4vX3UWG0zb1LKis=", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "optional": true, "requires": { - "array-differ": "^1.0.0", - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "minimatch": "^3.0.0" + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "optional": true + } } }, "mute-stream": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz", - "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=" - }, - "nan": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.10.0.tgz", - "integrity": "sha512-bAdJv7fBLhWC+/Bls0Oza+mvTaNQtP+1RyhhhvD95pgUJz6XM5IzgmxOkItJ9tkoCiplvAnXI1tNmmUD/eScyA==", - "dev": true, + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", "optional": true }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "optional": true, "requires": { "arr-diff": "^4.0.0", "array-unique": "^0.3.2", "define-property": "^2.0.2", "extend-shallow": "^3.0.2", "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", "is-windows": "^1.0.2", "kind-of": "^6.0.2", "object.pick": "^1.3.0", "regex-not": "^1.0.0", "snapdragon": "^0.8.1", "to-regex": "^3.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } } }, - "native-promise-only": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/native-promise-only/-/native-promise-only-0.8.1.tgz", - "integrity": "sha1-IKMYwwy0X3H+et+/eyHJnBRy7xE=", - "dev": true - }, - "natural-compare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", - "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", - "dev": true - }, - "neo-async": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz", - "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==", - "dev": true - }, - "node-int64": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/node-int64/-/node-int64-0.4.0.tgz", - "integrity": "sha1-h6kGXNs1XTGC2PlM4RGIuCXGijs=", - "dev": true - }, - "node-notifier": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/node-notifier/-/node-notifier-5.2.1.tgz", - "integrity": "sha512-MIBs+AAd6dJ2SklbbE8RUDRlIVhU8MaNLh1A9SUZDUHPiZkWLFde6UNwG41yQHZEToHgJMXqyVZ9UcS/ReOVTg==", - "dev": true, - "requires": { - "growly": "^1.3.0", - "semver": "^5.4.1", - "shellwords": "^0.1.1", - "which": "^1.3.0" - } + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-9jjUFbTPfEy3R/ad/2oNbKtW9Hgovl5O1FvFWKkKblNXoN/Oou6+9+KKohPK13Yc3/TyunyWhJp6gvRNR/PPAw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", "requires": { "hosted-git-info": "^2.1.4", - "is-builtin-module": "^1.0.0", + "resolve": "^1.10.0", "semver": "2 || 3 || 4 || 5", "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } } }, - "normalize-path": { + "npm-api": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-1.0.0.tgz", - "integrity": "sha1-MtDkcvkf80VwHBWoMRAY07CpA3k=", - "dev": true - }, - "npm-path": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/npm-path/-/npm-path-2.0.4.tgz", - "integrity": "sha512-IFsj0R9C7ZdR5cP+ET342q77uSRdtWOlWpih5eC+lu29tIDbNEgDbzgVJ5UFvYHWhxDZ5TFkJafFioO0pPQjCw==", - "dev": true, + "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.0.tgz", + "integrity": "sha512-gtJhIhGq07g9H5sIAB9TZzTySW8MYtcYqg+e+J+5q1GmDsDLLVfyvVBL1VklzjtRsElph11GUtLBS191RDOJxQ==", + "optional": true, "requires": { - "which": "^1.2.10" + "JSONStream": "^1.3.5", + "clone-deep": "^4.0.1", + "download-stats": "^0.3.4", + "moment": "^2.24.0", + "paged-request": "^2.0.1", + "request": "^2.88.0" } }, "npm-run-path": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", - "dev": true, - "requires": { - "path-key": "^2.0.0" - } - }, - "npm-which": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/npm-which/-/npm-which-3.0.1.tgz", - "integrity": "sha1-kiXybsOihcIJyuZ8OxGmtKtxQKo=", - "dev": true, - "requires": { - "commander": "^2.9.0", - "npm-path": "^2.0.2", - "which": "^1.2.10" - } - }, - "nsp": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/nsp/-/nsp-2.8.1.tgz", - "integrity": "sha512-jvjDg2Gsw4coD/iZ5eQddsDlkvnwMCNnpG05BproSnuG+Gr1bSQMwWMcQeYje+qdDl3XznmhblMPLpZLecTORQ==", - "dev": true, + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "optional": true, "requires": { - "chalk": "^1.1.1", - "cli-table": "^0.3.1", - "cvss": "^1.0.0", - "https-proxy-agent": "^1.0.0", - "joi": "^6.9.1", - "nodesecurity-npm-utils": "^5.0.0", - "path-is-absolute": "^1.0.0", - "rc": "^1.1.2", - "semver": "^5.0.3", - "subcommand": "^2.0.3", - "wreck": "^6.3.0" + "path-key": "^3.0.0" }, "dependencies": { - "agent-base": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.1.tgz", - "integrity": "sha1-1t4Q1a9hMtW9aSQn1G/FOFOQlMc=", - "dev": true, - "requires": { - "extend": "~3.0.0", - "semver": "~5.0.1" - }, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } - } - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-table": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.1.tgz", - "integrity": "sha1-9TsFJmqLGguTSz0IIebi3FkUriM=", - "dev": true, - "requires": { - "colors": "1.0.3" - } - }, - "cliclopts": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/cliclopts/-/cliclopts-1.1.1.tgz", - "integrity": "sha1-aUMcfLWvcjd0sNORG0w3USQxkQ8=", - "dev": true - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", - "dev": true - }, - "cvss": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cvss/-/cvss-1.0.2.tgz", - "integrity": "sha1-32fpK/EqeW9J6Sh5nI2zunS5/NY=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "deep-extend": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.4.2.tgz", - "integrity": "sha1-SLaZwn4zS/ifEIkr5DL25MfTSn8=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", - "dev": true, - "requires": { - "agent-base": "2", - "debug": "2", - "extend": "3" - } - }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "isemail": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", - "integrity": "sha1-vgPfjMPineTSxd9lASY/H6RZXpo=", - "dev": true - }, - "joi": { - "version": "6.10.1", - "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", - "integrity": "sha1-TVDDGAeRIgAP5fFq8f+OGRe3fgY=", - "dev": true, - "requires": { - "hoek": "2.x.x", - "isemail": "1.x.x", - "moment": "2.x.x", - "topo": "1.x.x" - } - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "moment": { - "version": "2.18.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.18.1.tgz", - "integrity": "sha1-w2GT3Tzhwu7SrbfIAtu8d6gbHA8=", - "dev": true - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "nodesecurity-npm-utils": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/nodesecurity-npm-utils/-/nodesecurity-npm-utils-5.0.0.tgz", - "integrity": "sha1-Baow3jDKjIRcQEjpT9eOXgi1Xtk=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "rc": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.1.tgz", - "integrity": "sha1-LgPo5C7kULjLPc5lvhv4l04d/ZU=", - "dev": true, - "requires": { - "deep-extend": "~0.4.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "semver": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.4.1.tgz", - "integrity": "sha512-WfG/X9+oATh81XtllIo/I8gOiY9EXRdv1cQdyykeXK17YcUW3EXUAi2To4pcH6nZtJPr7ZOpM5OMyWJZm+8Rsg==", - "dev": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "subcommand": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/subcommand/-/subcommand-2.1.0.tgz", - "integrity": "sha1-XkzspaN3njNlsVEeBfhmh3MC92A=", - "dev": true, - "requires": { - "cliclopts": "^1.1.0", - "debug": "^2.1.3", - "minimist": "^1.2.0", - "xtend": "^4.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "topo": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", - "integrity": "sha1-6ddRYV0buH3IZdsYL6HKCl71NtU=", - "dev": true, - "requires": { - "hoek": "2.x.x" - } - }, - "wreck": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/wreck/-/wreck-6.3.0.tgz", - "integrity": "sha1-oTaXafB7u2LWo3gzanhx/Hc8dAs=", - "dev": true, - "requires": { - "boom": "2.x.x", - "hoek": "2.x.x" - } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "optional": true } } }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" - }, - "nwmatcher": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/nwmatcher/-/nwmatcher-1.4.4.tgz", - "integrity": "sha512-3iuY4N5dhgMpCUrOVnuAdGrgxVqV2cJpM+XNccjR2DKOB1RUP0aA+wGXEiNziG/UKboFyGBIoKOaNlJxx8bciQ==", - "dev": true - }, "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", + "optional": true }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "dev": true, + "optional": true, "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -5769,71 +2296,38 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, "requires": { - "is-descriptor": "^0.1.0" + "is-buffer": "^1.1.5" } } } }, - "object-keys": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", - "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", - "dev": true - }, "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "dev": true, + "optional": true, "requires": { "isobject": "^3.0.0" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } - } - }, - "object.getownpropertydescriptors": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz", - "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "es-abstract": "^1.5.1" - } - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true, - "requires": { - "for-own": "^0.1.4", - "is-extendable": "^0.1.1" } }, "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, + "optional": true, "requires": { "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } } }, "once": { @@ -5845,162 +2339,19 @@ } }, "onetime": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz", - "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=", - "requires": { - "mimic-fn": "^1.0.0" - } - }, - "optimist": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz", - "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", - "dev": true, - "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" - }, - "dependencies": { - "minimist": { - "version": "0.0.10", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz", - "integrity": "sha1-3j+YVD2/lggr5IrRoMfNqDYwHc8=", - "dev": true - }, - "wordwrap": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz", - "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=", - "dev": true - } - } - }, - "optionator": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.2.tgz", - "integrity": "sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=", - "dev": true, - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.4", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "wordwrap": "~1.0.0" - } - }, - "ora": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/ora/-/ora-0.2.3.tgz", - "integrity": "sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q=", - "dev": true, - "requires": { - "chalk": "^1.1.1", - "cli-cursor": "^1.0.2", - "cli-spinners": "^0.1.2", - "object-assign": "^4.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - } - } - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-2.1.0.tgz", - "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", - "dev": true, + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "optional": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "mimic-fn": "^2.1.0" } }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "p-cancelable": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-0.3.0.tgz", - "integrity": "sha512-RVbZPLso8+jFeq1MfNvgXtCRED2raz/dKpacfTNxsx6pLEpEomM7gah6VeHSYV3+vo0OAi4MkArtQcWWXuQoyw==" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "optional": true }, "p-finally": { "version": "1.0.0", @@ -6008,77 +2359,57 @@ "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.2.0.tgz", - "integrity": "sha512-Y/OtIaXtUPr4/YpMv1pCL5L5ed0rumAaAeBSj12F+bSlMdys7i8oQF/GUJmfpTS/QoaRrS/k6pma29haJpsMng==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", "requires": { - "p-try": "^1.0.0" + "p-try": "^2.0.0" } }, "p-locate": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", - "requires": { - "p-limit": "^1.1.0" - } - }, - "p-map": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-1.2.0.tgz", - "integrity": "sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA==", - "dev": true - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", "requires": { - "p-finally": "^1.0.0" + "p-limit": "^2.0.0" } }, "p-try": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" - }, - "pad-component": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/pad-component/-/pad-component-0.0.1.tgz", - "integrity": "sha1-rR8izhvw/cDW3dkIrxfzUaQEuKw=" + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true, + "paged-request": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.1.tgz", + "integrity": "sha512-C0bB/PFk9rQskD1YEiz7uuchzqKDQGgdsEHN1ahify0UUWzgmMK4NDG9fhlQg2waogmNFwEvEeHfMRvJySpdVw==", + "optional": true, "requires": { - "glob-base": "^0.3.0", - "is-dotfile": "^1.0.0", - "is-extglob": "^1.0.0", - "is-glob": "^2.0.0" + "axios": "^0.18.0" } }, "parse-json": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", + "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", "requires": { + "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", - "json-parse-better-errors": "^1.0.1" + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" } }, - "parse5": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/parse5/-/parse5-4.0.0.tgz", - "integrity": "sha512-VrZ7eOd3T1Fk4XWNXMgiGBK/z0MG48BWG2uQNU4I72fkQuKUTZpl+u9k+CxEG0twMVzSmXEEz12z5Fnw1jIQFA==", - "dev": true - }, "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "dev": true + "optional": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "optional": true }, "path-exists": { "version": "3.0.0", @@ -6090,261 +2421,131 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, - "path-is-inside": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", - "dev": true - }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=", - "dev": true + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=" - }, - "path-to-regexp": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.7.0.tgz", - "integrity": "sha1-Wf3g9DW62suhA6hOnTvGTpa5k30=", - "dev": true, - "requires": { - "isarray": "0.0.1" - }, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - } - } + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" }, "path-type": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "optional": true, "requires": { "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "optional": true + } } }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "dev": true + "optional": true }, "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "requires": { - "pinkie": "^2.0.0" - } - }, - "pkg-dir": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", - "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", - "dev": true, - "requires": { - "find-up": "^2.1.0" - } - }, - "pluralize": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-7.0.0.tgz", - "integrity": "sha512-ARhBOdzS3e41FbkW/XWrTEtukqqLoK5+Z/4UeDaLuSW+39JPeFgs4gCGqsrJHVZX0fUrx//4OF0K1CUGwlIFow==", - "dev": true - }, - "pn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/pn/-/pn-1.1.0.tgz", - "integrity": "sha512-2qHaIQr2VLRFoxe2nASzsV6ef4yOOH+Fi9FBOVH6cqeSgUnoyySPZkxzLuzd+RYOQTRpROA0ztTMqxROKSb/nA==", - "dev": true + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "dev": true - }, - "prelude-ls": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", - "dev": true + "optional": true }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "prettier": { - "version": "1.12.1", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.12.1.tgz", - "integrity": "sha1-wa0g6APndJ+vkFpAnSNn4Gu+cyU=", - "dev": true - }, "pretty-bytes": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-4.0.2.tgz", - "integrity": "sha1-sr+C5zUNZcbDOqlaqlpPYyf2HNk=" - }, - "pretty-format": { - "version": "22.4.3", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-22.4.3.tgz", - "integrity": "sha512-S4oT9/sT6MN7/3COoOy+ZJeA92VmOnveLHgrwBE3Z1W5N9S2A1QGNYiE1z75DAENbJrXXUb+OWXhpJcg05QKQQ==", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0", - "ansi-styles": "^3.2.0" - } - }, - "private": { - "version": "0.1.8", - "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", - "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==", - "dev": true + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", + "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" }, "process-nextick-args": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", - "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "optional": true }, - "progress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.0.tgz", - "integrity": "sha1-ihvjZr+Pwj2yvSPxDG/pILQ4nR8=", - "dev": true + "psl": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", + "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", + "optional": true }, - "pseudomap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "optional": true }, "qs": { - "version": "6.5.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", - "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==", - "dev": true - }, - "randomatic": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", - "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", - "dev": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", + "optional": true }, "read-chunk": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-2.1.0.tgz", - "integrity": "sha1-agTAkoAF7Z1C4aasVgDhnLx/9lU=", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", + "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", "requires": { - "pify": "^3.0.0", - "safe-buffer": "^5.1.1" + "pify": "^4.0.1", + "with-open-file": "^0.1.6" } }, "read-pkg": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz", - "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=", + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", "requires": { - "load-json-file": "^4.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^3.0.0" + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" } }, "read-pkg-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-3.0.0.tgz", - "integrity": "sha1-PtSWaF26D4/hGNBpHcUfSh/5bwc=", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", + "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", "requires": { - "find-up": "^2.0.0", - "read-pkg": "^3.0.0" + "find-up": "^3.0.0", + "read-pkg": "^5.0.0" } }, "readable-stream": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", - "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "realpath-native": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/realpath-native/-/realpath-native-1.0.0.tgz", - "integrity": "sha512-XJtlRJ9jf0E1H1SLeJyQ9PGzQD7S65h1pRXEcAeK48doKOnKxcgPeNohJvD5u/2sI9J1oke6E8bZHS/fmW1UiQ==", - "dev": true, + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", "requires": { - "util.promisify": "^1.0.0" + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" } }, "rechoir": { @@ -6355,189 +2556,98 @@ "resolve": "^1.1.6" } }, - "regenerator-runtime": { - "version": "0.11.1", - "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz", - "integrity": "sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg==", - "dev": true - }, - "regex-cache": { - "version": "0.4.4", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.4.tgz", - "integrity": "sha512-nVIZwtCjkC9YgvWkpM55B5rBhBYRZhAaJbgcFYXXsHnbZ9UZI9nnVWYZpBlCqv9ho2eZryPnWrZGsOdPwVWXWQ==", - "dev": true, - "requires": { - "is-equal-shallow": "^0.1.3" - } - }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.2", "safe-regex": "^1.1.0" } }, - "regexpp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-1.1.0.tgz", - "integrity": "sha512-LOPw8FpgdQF9etWMaAfG/WRthIdXJGYp4mJ2Jgn/2lpkbod9jPn0t9UqN7AxBOKNfzRbYyVfgc7Vk4t/MpnXgw==", - "dev": true - }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "optional": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "optional": true }, "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "repeating": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", - "dev": true, - "requires": { - "is-finite": "^1.0.0" - } + "optional": true }, "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "optional": true }, "request": { - "version": "2.85.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.85.0.tgz", - "integrity": "sha512-8H7Ehijd4js+s6wuVPLjwORxD4zeuyjYugprdOXlPSqaApmL/QOy+EB/beICHVCHkGMKNh5rvihb5ov+IDw4mg==", - "dev": true, + "version": "2.88.2", + "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", + "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", + "optional": true, "requires": { "aws-sign2": "~0.7.0", - "aws4": "^1.6.0", + "aws4": "^1.8.0", "caseless": "~0.12.0", - "combined-stream": "~1.0.5", - "extend": "~3.0.1", + "combined-stream": "~1.0.6", + "extend": "~3.0.2", "forever-agent": "~0.6.1", - "form-data": "~2.3.1", - "har-validator": "~5.0.3", - "hawk": "~6.0.2", + "form-data": "~2.3.2", + "har-validator": "~5.1.3", "http-signature": "~1.2.0", "is-typedarray": "~1.0.0", "isstream": "~0.1.2", "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.17", - "oauth-sign": "~0.8.2", + "mime-types": "~2.1.19", + "oauth-sign": "~0.9.0", "performance-now": "^2.1.0", - "qs": "~6.5.1", - "safe-buffer": "^5.1.1", - "stringstream": "~0.0.5", - "tough-cookie": "~2.3.3", + "qs": "~6.5.2", + "safe-buffer": "^5.1.2", + "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", - "uuid": "^3.1.0" - } - }, - "request-promise-core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.1.tgz", - "integrity": "sha1-Pu4AssWqgyOc+wTFcA2jb4HNCLY=", - "dev": true, - "requires": { - "lodash": "^4.13.1" - } - }, - "request-promise-native": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.5.tgz", - "integrity": "sha1-UoF3D2jgyXGeUWP9P6tIIhX0/aU=", - "dev": true, - "requires": { - "request-promise-core": "1.1.1", - "stealthy-require": "^1.1.0", - "tough-cookie": ">=2.3.3" - } - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-from-string": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.1.tgz", - "integrity": "sha1-xUUjPp19pmFunVmt+zn8n1iGdv8=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "require-uncached": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", - "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", - "dev": true, - "requires": { - "caller-path": "^0.1.0", - "resolve-from": "^1.0.0" - } - }, - "resolve": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", - "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", - "requires": { - "path-parse": "^1.0.5" - } - }, - "resolve-cwd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-2.0.0.tgz", - "integrity": "sha1-AKn3OHVW4nA46uIyyqNypqWbZlo=", - "dev": true, - "requires": { - "resolve-from": "^3.0.0" + "uuid": "^3.3.2" }, "dependencies": { - "resolve-from": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=", - "dev": true + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "optional": true } } }, - "resolve-from": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", - "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", - "dev": true + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } }, "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "dev": true + "optional": true }, "restore-cursor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz", - "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "optional": true, "requires": { - "onetime": "^2.0.0", + "onetime": "^5.1.0", "signal-exit": "^3.0.2" } }, @@ -6545,70 +2655,40 @@ "version": "0.1.15", "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "dev": true + "optional": true }, "rimraf": { - "version": "2.6.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", - "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", - "requires": { - "glob": "^7.0.5" - } - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "requires": { - "is-promise": "^2.1.0" - } - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - }, - "rx-lite": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-4.0.8.tgz", - "integrity": "sha1-Cx4Rr4vESDbwSmQH6S2kJGe3lEQ=" - }, - "rx-lite-aggregates": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz", - "integrity": "sha1-dTuHqJoRyVRnxKwWJsTvxOBcZ74=", + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", "requires": { - "rx-lite": "*" + "glob": "^7.1.3" } }, + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, "rxjs": { - "version": "5.5.8", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-5.5.8.tgz", - "integrity": "sha512-Bz7qou7VAIoGiglJZbzbXa4vpX5BmTTN2Dj/se6+SwADtw4SihqBIiEa7VmTXJ8pynvq0iFr5Gx9VLyye1rIxQ==", - "dev": true, + "version": "6.6.3", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", + "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", + "optional": true, "requires": { - "symbol-observable": "1.0.1" - }, - "dependencies": { - "symbol-observable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-1.0.1.tgz", - "integrity": "sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ=", - "dev": true - } + "tslib": "^1.9.0" } }, "safe-buffer": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", - "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "dev": true, + "optional": true, "requires": { "ret": "~0.1.10" } @@ -6616,342 +2696,37 @@ "safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" - }, - "samsam": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/samsam/-/samsam-1.3.0.tgz", - "integrity": "sha512-1HwIYD/8UlOtFS3QO3w7ey+SdSDFE4HRNLZoZRYVQefrOY3l17epswImeB1ijgJFQJodIaHcwkp3r/myBjFVbg==", - "dev": true - }, - "sane": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/sane/-/sane-2.5.0.tgz", - "integrity": "sha512-glfKd7YH4UCrh/7dD+UESsr8ylKWRE7UQPoXuz28FgmcF0ViJQhCTCCZHICRKxf8G8O1KdLEn20dcICK54c7ew==", - "dev": true, - "requires": { - "anymatch": "^2.0.0", - "exec-sh": "^0.2.0", - "fb-watchman": "^2.0.0", - "fsevents": "^1.1.1", - "micromatch": "^3.1.4", - "minimist": "^1.1.1", - "walker": "~1.0.5", - "watch": "~0.18.0" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - } - } - }, - "sax": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", - "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", - "dev": true + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true }, "scoped-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=" + "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", + "optional": true }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==" + "version": "7.3.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", + "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "requires": { + "lru-cache": "^6.0.0" + } }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "optional": true, + "requires": { + "to-object-path": "^0.3.0" + } }, "set-value": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.0.tgz", - "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", - "dev": true, + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "optional": true, "requires": { "extend-shallow": "^2.0.1", "is-extendable": "^0.1.1", @@ -6963,13 +2738,22 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } } } }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "optional": true, + "requires": { + "kind-of": "^6.0.2" + } + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -6984,62 +2768,32 @@ "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shelljs": { - "version": "0.8.1", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.1.tgz", - "integrity": "sha512-YA/iYtZpzFe5HyWVGrb02FjPxc4EMCfpoU/Phg9fQoyMC72u9598OUBrsU8IrtwAKG0tO8IYaqbaLIw+k3IRGA==", + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", "requires": { "glob": "^7.0.0", "interpret": "^1.0.0", "rechoir": "^0.6.2" } }, - "shellwords": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/shellwords/-/shellwords-0.1.1.tgz", - "integrity": "sha512-vFwSUfQvqybiICwZY5+DAWIPLKsWO31Q91JSKl3UYv+K5c2QRPzn0qzec6QPu1Qc9eHYItiP3NdJqNVqetYAww==", - "dev": true - }, "signal-exit": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", - "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" - }, - "sinon": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-2.4.1.tgz", - "integrity": "sha512-vFTrO9Wt0ECffDYIPSP/E5bBugt0UjcBQOfQUMh66xzkyPEnhl/vM2LRZi2ajuTdkH07sA6DzrM6KvdvGIH8xw==", - "dev": true, - "requires": { - "diff": "^3.1.0", - "formatio": "1.2.0", - "lolex": "^1.6.0", - "native-promise-only": "^0.8.1", - "path-to-regexp": "^1.7.0", - "samsam": "^1.1.3", - "text-encoding": "0.6.4", - "type-detect": "^4.0.0" - } + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "optional": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "dev": true - }, - "slice-ansi": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-1.0.0.tgz", - "integrity": "sha512-POqxBK6Lb3q6s047D/XsDVNPnF9Dl8JSaqe9h9lURl0OdNqy/ujDrOiIHtsqXMGbWWTIomRzAMaTyawAU//Reg==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0" - } + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "optional": true }, "snapdragon": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "dev": true, + "optional": true, "requires": { "base": "^0.11.1", "debug": "^2.2.0", @@ -7055,7 +2809,7 @@ "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "optional": true, "requires": { "ms": "2.0.0" } @@ -7064,7 +2818,7 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } @@ -7073,10 +2827,16 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, + "optional": true, "requires": { "is-extendable": "^0.1.0" } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true } } }, @@ -7084,7 +2844,7 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "dev": true, + "optional": true, "requires": { "define-property": "^1.0.0", "isobject": "^3.0.0", @@ -7095,7 +2855,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, + "optional": true, "requires": { "is-descriptor": "^1.0.0" } @@ -7104,7 +2864,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -7113,7 +2873,7 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^6.0.0" } @@ -7122,24 +2882,12 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, + "optional": true, "requires": { "is-accessor-descriptor": "^1.0.0", "is-data-descriptor": "^1.0.0", "kind-of": "^6.0.2" } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true } } }, @@ -7147,120 +2895,89 @@ "version": "3.0.1", "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "dev": true, + "optional": true, "requires": { "kind-of": "^3.2.0" - } - }, - "sntp": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-2.1.0.tgz", - "integrity": "sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==", - "dev": true, - "requires": { - "hoek": "4.x.x" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "dev": true + "optional": true }, "source-map-resolve": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.1.tgz", - "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", - "dev": true, + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "optional": true, "requires": { - "atob": "^2.0.0", + "atob": "^2.1.2", "decode-uri-component": "^0.2.0", "resolve-url": "^0.2.1", "source-map-url": "^0.4.0", "urix": "^0.1.0" } }, - "source-map-support": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.4.tgz", - "integrity": "sha512-PETSPG6BjY1AHs2t64vS2aqAgu6dMIMXJULWFBGbh2Gr8nVLbCFDo6i/RMMvviIQ2h1Z8+5gQhVKSn2je9nmdg==", - "dev": true, - "requires": { - "source-map": "^0.6.0" - }, - "dependencies": { - "source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true - } - } - }, "source-map-url": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "dev": true - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true, - "requires": { - "concat-stream": "^1.4.7", - "os-shim": "^0.1.2" - } + "optional": true }, "spdx-correct": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.0.0.tgz", - "integrity": "sha512-N19o9z5cEyc8yQQPukRCZ9EUmb4HUpnrmaL/fxS2pBo2jbfcFRVuFZ/oFC+vZz0MNNk0h80iMn5/S6qGZOL5+g==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz", - "integrity": "sha512-4K1NsmrlCU1JJgUrtgEeTVyfx8VaYea9J9LvARxhbHtVtohPs/gFGG5yy49beySjlIMhhXZ4QqujIZEfS4l6Cg==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz", - "integrity": "sha512-2+EPwgbnmOIl8HjGBXXMd9NAu02vLjOO1nWw4kmeRDFyHn+M/ETfHxQUK0oXg8ctgVnl9t3rosNVsZ1jG61nDA==" + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" }, "split-string": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "dev": true, + "optional": true, "requires": { "extend-shallow": "^3.0.0" } }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, "sshpk": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.14.1.tgz", - "integrity": "sha1-Ew9Zde3a2WPx1W+SuaxsUfqfg+s=", - "dev": true, + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", + "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", + "optional": true, "requires": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -7269,26 +2986,15 @@ "ecc-jsbn": "~0.1.1", "getpass": "^0.1.1", "jsbn": "~0.1.0", + "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" } }, - "stack-utils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-1.0.1.tgz", - "integrity": "sha1-1PM6tU6OOHeLDKXP07OvsS22hiA=", - "dev": true - }, - "staged-git-files": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/staged-git-files/-/staged-git-files-1.0.0.tgz", - "integrity": "sha1-zbhHg3wfzFLAioctSIPMCHdmioA=", - "dev": true - }, "static-extend": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "dev": true, + "optional": true, "requires": { "define-property": "^0.2.5", "object-copy": "^0.1.0" @@ -7298,561 +3004,85 @@ "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, + "optional": true, "requires": { "is-descriptor": "^0.1.0" } } } }, - "stealthy-require": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz", - "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=", - "dev": true - }, - "stream-to-observable": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/stream-to-observable/-/stream-to-observable-0.2.0.tgz", - "integrity": "sha1-WdbqOT2HwsDdrBCqDVYbxrpvDhA=", - "dev": true, - "requires": { - "any-observable": "^0.2.0" - } - }, - "string-length": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/string-length/-/string-length-2.0.0.tgz", - "integrity": "sha1-1A27aGo6zpYMHP/KVivyxF+DY+0=", - "dev": true, - "requires": { - "astral-regex": "^1.0.0", - "strip-ansi": "^4.0.0" - } - }, "string-template": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" }, "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", + "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" } }, "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "stringify-object": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/stringify-object/-/stringify-object-3.2.2.tgz", - "integrity": "sha512-O696NF21oLiDy8PhpWu8AEqoZHw++QW6mUv0UvKZe8gWSdSvMXkiLufK7OmnP27Dro4GU5kb9U7JIO0mBuCRQg==", - "dev": true, + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { - "get-own-enumerable-property-symbols": "^2.0.1", - "is-obj": "^1.0.1", - "is-regexp": "^1.0.0" + "safe-buffer": "~5.2.0" } }, - "stringstream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.6.tgz", - "integrity": "sha512-87GEBAkegbBcweToUrdzf3eLhWNg06FJTebl4BVJz/JgWy8CvEr9dRtX5qWphiynMSQlxxi+QqN0z5T32SLlhA==", - "dev": true - }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "requires": { - "ansi-regex": "^3.0.0" - } - }, - "strip-bom": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=" - }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "requires": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" - }, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - } - } - }, - "strip-eof": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz", - "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=", - "dev": true - }, - "strip-indent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-2.0.0.tgz", - "integrity": "sha1-XvjbKV0B5u1sv3qrlpmNeCJSe2g=", - "dev": true - }, - "strip-json-comments": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", - "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", - "dev": true - }, - "supports-color": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.4.0.tgz", - "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", "requires": { - "has-flag": "^3.0.0" + "ansi-regex": "^5.0.0" } }, - "symbol-observable": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-0.2.4.tgz", - "integrity": "sha1-lag9smGG1q9+ehjb2XYKL4bQj0A=", - "dev": true - }, - "symbol-tree": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.2.tgz", - "integrity": "sha1-rifbOPZgp64uHDt9G8KQgZuFGeY=", - "dev": true - }, - "table": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/table/-/table-4.0.2.tgz", - "integrity": "sha512-UUkEAPdSGxtRpiV9ozJ5cMTtYiqz7Ni1OGqLXRCynrvzdtR1p+cfOWe2RJLwvUG8hNanaSRjecIqwOjqeatDsA==", - "dev": true, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "optional": true, "requires": { - "ajv": "^5.2.3", - "ajv-keywords": "^2.1.0", - "chalk": "^2.1.0", - "lodash": "^4.17.4", - "slice-ansi": "1.0.0", - "string-width": "^2.1.1" + "is-utf8": "^0.2.0" } }, - "taketalk": { + "strip-bom-buf": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/taketalk/-/taketalk-1.0.0.tgz", - "integrity": "sha1-tNTw3u0gauffd1sSnqLKbeUvJt0=", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", + "optional": true, "requires": { - "get-stdin": "^4.0.1", - "minimist": "^1.1.0" + "is-utf8": "^0.2.1" } }, - "test-exclude": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-4.2.1.tgz", - "integrity": "sha512-qpqlP/8Zl+sosLxBcVKl9vYy26T9NPalxSzzCP/OY6K7j938ui2oKgo+kRZYfxAeIpLqpbVnsHq1tyV70E4lWQ==", - "dev": true, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "optional": true, "requires": { - "arrify": "^1.0.1", - "micromatch": "^3.1.8", - "object-assign": "^4.1.0", - "read-pkg-up": "^1.0.1", - "require-main-filename": "^1.0.1" - }, - "dependencies": { - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "dev": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "dev": true - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "requires": { - "ms": "2.0.0" - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "dev": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "dev": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "dev": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true, - "requires": { - "path-exists": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "dev": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - }, - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true, - "requires": { - "pinkie-promise": "^2.0.0" - } - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - } - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true, - "requires": { - "load-json-file": "^1.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^1.0.0" - } - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true, - "requires": { - "find-up": "^1.0.0", - "read-pkg": "^1.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true, - "requires": { - "is-utf8": "^0.2.0" - } - } + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" } }, - "text-encoding": { - "version": "0.6.4", - "resolved": "https://registry.npmjs.org/text-encoding/-/text-encoding-0.6.4.tgz", - "integrity": "sha1-45mpgiV6J22uQou5KEXLcb3CbRk=", - "dev": true + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "optional": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } }, "text-table": { "version": "0.2.0", @@ -7860,28 +3090,23 @@ "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" }, "textextensions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.2.0.tgz", - "integrity": "sha512-j5EMxnryTvKxwH2Cq+Pb43tsf6sdEgw6Pdwxk83mPaq0ToeFJt6WE4J3s5BqY7vmjlLgkgXvhtXUxo80FyBhCA==" - }, - "throat": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/throat/-/throat-4.1.0.tgz", - "integrity": "sha1-iQN8vJLFarGJJua6TLsgDhVnKmo=", - "dev": true + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", + "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" }, "through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "optional": true }, "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "inherits": "^2.0.4", + "readable-stream": "2 || 3" } }, "timed-out": { @@ -7893,36 +3118,36 @@ "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "optional": true, "requires": { "os-tmpdir": "~1.0.2" } }, - "tmpl": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.4.tgz", - "integrity": "sha1-I2QN17QtAEM5ERQIIOXPRA5SHdE=", - "dev": true - }, - "to-fast-properties": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-1.0.3.tgz", - "integrity": "sha1-uDVx+k2MJbguIxsG46MFXeTKGkc=", - "dev": true - }, "to-object-path": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "dev": true, + "optional": true, "requires": { "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } } }, "to-regex": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "dev": true, + "optional": true, "requires": { "define-property": "^2.0.2", "extend-shallow": "^3.0.2", @@ -7934,60 +3159,33 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "dev": true, + "optional": true, "requires": { "is-number": "^3.0.0", "repeat-string": "^1.6.1" - }, - "dependencies": { - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - } } }, "tough-cookie": { - "version": "2.3.4", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.4.tgz", - "integrity": "sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA==", - "dev": true, - "requires": { - "punycode": "^1.4.1" - } - }, - "tr46": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-1.0.1.tgz", - "integrity": "sha1-qLE/1r/SSJUZZ0zN5VujaTtwbQk=", - "dev": true, + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", + "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", + "optional": true, "requires": { - "punycode": "^2.1.0" - }, - "dependencies": { - "punycode": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", - "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", - "dev": true - } + "psl": "^1.1.28", + "punycode": "^2.1.1" } }, - "trim-right": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/trim-right/-/trim-right-1.0.1.tgz", - "integrity": "sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM=", - "dev": true + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true }, "tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.0.1" } @@ -7996,76 +3194,30 @@ "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, "optional": true }, - "type-check": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", - "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", - "dev": true, - "requires": { - "prelude-ls": "~1.1.2" - } - }, - "type-detect": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", - "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", - "dev": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "ultron": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/ultron/-/ultron-1.1.1.tgz", - "integrity": "sha512-UIEXBNeYmKptWH6z8ZnqTeS8fV74zG0/eRU9VGkpzz+LIJNs8W/zM/L+7ctCkRrgbNnnR0xxw4bKOr0cW0N0Og==", - "dev": true + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" }, "union-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.0.tgz", - "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", - "dev": true, + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "optional": true, "requires": { "arr-union": "^3.1.0", "get-value": "^2.0.6", "is-extendable": "^0.1.1", - "set-value": "^0.4.3" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "dev": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "set-value": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-0.4.3.tgz", - "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", - "dev": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" - } - } + "set-value": "^2.0.1" } }, "unset-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "dev": true, + "optional": true, "requires": { "has-value": "^0.3.1", "isobject": "^3.0.0" @@ -8075,7 +3227,7 @@ "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "dev": true, + "optional": true, "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -8086,7 +3238,7 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true, + "optional": true, "requires": { "isarray": "1.0.0" } @@ -8097,32 +3249,35 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "dev": true - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true + "optional": true } } }, "untildify": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.2.tgz", - "integrity": "sha1-fx8wIFWz/qDz6B3HjrNnZstl4/E=" + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", + "optional": true }, "unzip-response": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=", - "dev": true + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "optional": true, + "requires": { + "punycode": "^2.1.0" + } }, "urix": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "dev": true + "optional": true }, "url-parse-lax": { "version": "1.0.0", @@ -8132,62 +3287,21 @@ "prepend-http": "^1.0.1" } }, - "url-to-options": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/url-to-options/-/url-to-options-1.0.1.tgz", - "integrity": "sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=" - }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - }, - "dependencies": { - "kind-of": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz", - "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==", - "dev": true - } - } - }, - "user-home": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", - "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "optional": true }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, - "util.promisify": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/util.promisify/-/util.promisify-1.0.0.tgz", - "integrity": "sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA==", - "dev": true, - "requires": { - "define-properties": "^1.1.2", - "object.getownpropertydescriptors": "^2.0.3" - } - }, - "uuid": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.2.1.tgz", - "integrity": "sha512-jZnMwlb9Iku/O3smGWvZhauCf6cvvpKi4BKRiliS3cxnI+Gz9j5MEpTz2UFuXiKPJocb7gnsLHwiS05ige5BEA==", - "dev": true - }, "validate-npm-package-license": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.3.tgz", - "integrity": "sha512-63ZOUnL4SIXj4L0NixR3L1lcjO38crAbgrTpl28t8jjrfuiOBL5Iygm+60qPs/KsZGzPNg6Smnc/oY16QTjF0g==", + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", "requires": { "spdx-correct": "^3.0.0", "spdx-expression-parse": "^3.0.0" @@ -8197,7 +3311,7 @@ "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "dev": true, + "optional": true, "requires": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", @@ -8205,9 +3319,10 @@ } }, "vinyl": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.1.0.tgz", - "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "optional": true, "requires": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -8218,186 +3333,42 @@ } }, "vinyl-file": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-2.0.0.tgz", - "integrity": "sha1-p+v1/779obfRjRQPyweyI++2dRo=", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", + "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", + "optional": true, "requires": { "graceful-fs": "^4.1.2", "pify": "^2.3.0", - "pinkie-promise": "^2.0.0", - "strip-bom": "^2.0.0", + "strip-bom-buf": "^1.0.0", "strip-bom-stream": "^2.0.0", - "vinyl": "^1.1.0" + "vinyl": "^2.0.1" }, "dependencies": { - "clone": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", - "integrity": "sha1-2jCcwmPfFZlMaIypAheco8fNfH4=" - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=" - }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=" - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=" - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "requires": { - "is-utf8": "^0.2.0" - } - }, - "vinyl": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-1.2.0.tgz", - "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", - "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", - "replace-ext": "0.0.1" - } - } - } - }, - "w3c-hr-time": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/w3c-hr-time/-/w3c-hr-time-1.0.1.tgz", - "integrity": "sha1-gqwr/2PZUOqeMYmlimViX+3xkEU=", - "dev": true, - "requires": { - "browser-process-hrtime": "^0.1.2" - } - }, - "walker": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz", - "integrity": "sha1-L3+bj9ENZ3JisYqITijRlhjgKPs=", - "dev": true, - "requires": { - "makeerror": "1.0.x" - } - }, - "watch": { - "version": "0.18.0", - "resolved": "https://registry.npmjs.org/watch/-/watch-0.18.0.tgz", - "integrity": "sha1-KAlUdsbffJDJYxOJkMClQj60uYY=", - "dev": true, - "requires": { - "exec-sh": "^0.2.0", - "minimist": "^1.2.0" - } - }, - "webidl-conversions": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-4.0.2.tgz", - "integrity": "sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==", - "dev": true - }, - "whatwg-encoding": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-1.0.3.tgz", - "integrity": "sha512-jLBwwKUhi8WtBfsMQlL4bUUcT8sMkAtQinscJAe/M4KHCkHuUJAF6vuB0tueNIw4c8ziO6AkRmgY+jL3a0iiPw==", - "dev": true, - "requires": { - "iconv-lite": "0.4.19" - }, - "dependencies": { - "iconv-lite": { - "version": "0.4.19", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", - "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==", - "dev": true + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "optional": true } } }, - "whatwg-mimetype": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-2.1.0.tgz", - "integrity": "sha512-FKxhYLytBQiUKjkYteN71fAUA3g6KpNXoho1isLiLSB3N1G4F35Q5vUxWfKFhBwi5IWF27VE6WxhrnnC+m0Mew==", - "dev": true - }, - "whatwg-url": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-6.4.0.tgz", - "integrity": "sha512-Z0CVh/YE217Foyb488eo+iBv+r7eAQ0wSTyApi9n06jhcA3z6Nidg/EGvl0UFkg7kMdKxfBzzr+o9JF+cevgMg==", - "dev": true, - "requires": { - "lodash.sortby": "^4.7.0", - "tr46": "^1.0.0", - "webidl-conversions": "^4.0.1" - } - }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { "isexe": "^2.0.0" } }, - "which-module": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", - "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", + "with-open-file": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", + "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "requires": { - "ansi-regex": "^2.0.0" - } - } + "p-finally": "^1.0.0", + "p-try": "^2.1.0", + "pify": "^4.0.1" } }, "wrappy": { @@ -8405,620 +3376,246 @@ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "write": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", - "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", - "dev": true, - "requires": { - "mkdirp": "^0.5.1" - } - }, - "write-file-atomic": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.3.0.tgz", - "integrity": "sha512-xuPeK4OdjWqtfi59ylvVL0Yn35SF3zgcAcv7rBPFHVaEapaDr4GdGgm3j7ckTwH9wHL7fGmgfAnb0+THrHb8tA==", - "dev": true, - "requires": { - "graceful-fs": "^4.1.11", - "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.2" - } - }, - "ws": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-4.0.0.tgz", - "integrity": "sha512-QYslsH44bH8O7/W2815u5DpnCpXWpEK44FmaHffNwgJI4JMaSZONgPBTOfrxJ29mXKbXak+LsJ2uAkDTYq2ptQ==", - "dev": true, - "requires": { - "async-limiter": "~1.0.0", - "safe-buffer": "~5.1.0", - "ultron": "~1.1.0" - } - }, - "xml-name-validator": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-3.0.0.tgz", - "integrity": "sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=" - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, "yallist": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz", - "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" - }, - "yargs": { - "version": "10.1.2", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-10.1.2.tgz", - "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", - "dev": true, - "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^8.1.0" - }, - "dependencies": { - "cliui": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.0.0.tgz", - "integrity": "sha512-nY3W5Gu2racvdDk//ELReY+dHjb9PlIcVDFXP72nVIhq2Gy3LuVXYwJoPVudwQnv1shtohpgkdCKT2YaKY0CKw==", - "dev": true, - "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" - } - } - } - }, - "yargs-parser": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-8.1.0.tgz", - "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", - "dev": true, - "requires": { - "camelcase": "^4.1.0" - }, - "dependencies": { - "camelcase": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=", - "dev": true - } - } - }, - "yeoman-assert": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/yeoman-assert/-/yeoman-assert-3.1.1.tgz", - "integrity": "sha512-bCuLb/j/WzpvrJZCTdJJLFzm7KK8IYQJ3+dF9dYtNs2CUYyezFJDuULiZ2neM4eqjf45GN1KH/MzCTT3i90wUQ==", - "dev": true + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "yeoman-environment": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.0.6.tgz", - "integrity": "sha512-jzHBTTy8EPI4ImV8dpUMt+Q5zELkSU5xvGpndHcHudQ4tqN6YgIWaCGmRFl+HDchwRUkcgyjQ+n6/w5zlJBCPg==", + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", + "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", + "optional": true, "requires": { - "chalk": "^2.1.0", + "chalk": "^2.4.1", "debug": "^3.1.0", - "diff": "^3.3.1", + "diff": "^3.5.0", "escape-string-regexp": "^1.0.2", - "globby": "^6.1.0", - "grouped-queue": "^0.3.3", - "inquirer": "^3.3.0", + "execa": "^4.0.0", + "globby": "^8.0.1", + "grouped-queue": "^1.1.0", + "inquirer": "^7.1.0", "is-scoped": "^1.0.0", - "lodash": "^4.17.4", - "log-symbols": "^2.1.0", + "lodash": "^4.17.10", + "log-symbols": "^2.2.0", "mem-fs": "^1.1.0", + "mem-fs-editor": "^6.0.0", + "npm-api": "^1.0.0", + "semver": "^7.1.3", + "strip-ansi": "^4.0.0", "text-table": "^0.2.0", - "untildify": "^3.0.2" - } - }, - "yeoman-generator": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-2.0.4.tgz", - "integrity": "sha512-Sgvz3MAkOpEIobcpW3rjEl6bOTNnl8SkibP9z7hYKfIGIlw0QDC2k0MAeXvyE2pLqc2M0Duql+6R7/W9GrJojg==", - "requires": { - "async": "^2.6.0", - "chalk": "^2.3.0", - "cli-table": "^0.3.1", - "cross-spawn": "^5.1.0", - "dargs": "^5.1.0", - "dateformat": "^3.0.2", - "debug": "^3.1.0", - "detect-conflict": "^1.0.0", - "error": "^7.0.2", - "find-up": "^2.1.0", - "github-username": "^4.0.0", - "istextorbinary": "^2.1.0", - "lodash": "^4.17.4", - "make-dir": "^1.1.0", - "mem-fs-editor": "^3.0.2", - "minimist": "^1.2.0", - "pretty-bytes": "^4.0.2", - "read-chunk": "^2.1.0", - "read-pkg-up": "^3.0.0", - "rimraf": "^2.6.2", - "run-async": "^2.0.0", - "shelljs": "^0.8.0", - "text-table": "^0.2.0", - "through2": "^2.0.0", - "yeoman-environment": "^2.0.5" - } - }, - "yeoman-test": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/yeoman-test/-/yeoman-test-1.7.0.tgz", - "integrity": "sha512-vJeg2gpWfhbq0HvQ7/yqmsQpYmADBfo9kaW+J6uJASkI7ChLBXNLIBQqaXCA65kWtHXOco+nBm0Km/O9YWk25Q==", - "dev": true, - "requires": { - "inquirer": "^3.0.1", - "lodash": "^4.3.0", - "mkdirp": "^0.5.1", - "pinkie-promise": "^2.0.1", - "rimraf": "^2.4.4", - "sinon": "^2.3.6", - "yeoman-environment": "^2.0.0", - "yeoman-generator": "^1.1.0" + "untildify": "^3.0.3", + "yeoman-generator": "^4.8.2" }, "dependencies": { - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "optional": true }, "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true, - "requires": { - "restore-cursor": "^1.0.1" - } - }, - "dateformat": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.2.0.tgz", - "integrity": "sha1-QGXiATz5+5Ft39gu+1Bq1MZ2kGI=", - "dev": true - }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, "requires": { - "ms": "2.0.0" + "color-convert": "^1.9.0" } }, - "diff": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-2.2.3.tgz", - "integrity": "sha1-YOr9DSjukG5Oj/ClLBIpUhAzv5k=", - "dev": true - }, - "external-editor": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", - "dev": true, - "requires": { - "extend": "^3.0.0", - "spawn-sync": "^1.0.15", - "tmp": "^0.0.29" - } + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "optional": true }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, "requires": { + "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", - "object-assign": "^4.1.0" - } - }, - "gh-got": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", - "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", - "dev": true, - "requires": { - "got": "^6.2.0", - "is-plain-obj": "^1.1.0" + "supports-color": "^5.3.0" } }, - "github-username": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", - "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", - "dev": true, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "optional": true, "requires": { - "gh-got": "^5.0.0" + "color-name": "1.1.3" } }, - "globby": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-4.1.0.tgz", - "integrity": "sha1-CA9UVJ7BuCpsYOYx/ILhIR2+lfg=", - "dev": true, - "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^6.0.1", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" - }, - "dependencies": { - "glob": { - "version": "6.0.4", - "resolved": "https://registry.npmjs.org/glob/-/glob-6.0.4.tgz", - "integrity": "sha1-DwiGD2oVUSey+t1PnOJLGqtuTSI=", - "dev": true, - "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - } - } + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "optional": true }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "dev": true, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "optional": true, "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "ms": "^2.1.1" } }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true, - "requires": { - "number-is-nan": "^1.0.0" - } + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "optional": true }, - "load-json-file": { + "dir-glob": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-2.0.0.tgz", - "integrity": "sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg=", - "dev": true, + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "optional": true, "requires": { - "graceful-fs": "^4.1.2", - "parse-json": "^2.2.0", - "pify": "^2.0.0", - "strip-bom": "^3.0.0" + "arrify": "^1.0.1", + "path-type": "^3.0.0" } }, - "log-symbols": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-1.0.2.tgz", - "integrity": "sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg=", - "dev": true, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "optional": true, "requires": { - "chalk": "^1.0.0" + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" } }, - "mute-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", - "dev": true - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true, - "requires": { - "error-ex": "^1.2.0" - } + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "optional": true }, - "path-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-2.0.0.tgz", - "integrity": "sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM=", - "dev": true, - "requires": { - "pify": "^2.0.0" - } + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "optional": true }, "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "read-pkg": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-2.0.0.tgz", - "integrity": "sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg=", - "dev": true, - "requires": { - "load-json-file": "^2.0.0", - "normalize-package-data": "^2.3.2", - "path-type": "^2.0.0" - } - }, - "read-pkg-up": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-2.0.0.tgz", - "integrity": "sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4=", - "dev": true, - "requires": { - "find-up": "^2.0.0", - "read-pkg": "^2.0.0" - } - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true, - "requires": { - "exit-hook": "^1.0.0", - "onetime": "^1.0.0" - } - }, - "shelljs": { - "version": "0.7.8", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", - "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", - "dev": true, - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "optional": true }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "optional": true }, "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "optional": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "^3.0.0" } }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "dev": true, - "requires": { - "os-tmpdir": "~1.0.1" - } - }, - "untildify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-2.1.0.tgz", - "integrity": "sha1-F+soB5h/dpUunASF/DEdBqgmouA=", - "dev": true, - "requires": { - "os-homedir": "^1.0.0" - } - }, - "yeoman-generator": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-1.1.1.tgz", - "integrity": "sha1-QMK09s374F4ZUv3XKTPw2JJdvfU=", - "dev": true, + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, "requires": { - "async": "^2.0.0", - "chalk": "^1.0.0", - "class-extend": "^0.1.0", - "cli-table": "^0.3.1", - "cross-spawn": "^5.0.1", - "dargs": "^5.1.0", - "dateformat": "^2.0.0", - "debug": "^2.1.0", - "detect-conflict": "^1.0.0", - "error": "^7.0.2", - "find-up": "^2.1.0", - "github-username": "^3.0.0", - "glob": "^7.0.3", - "istextorbinary": "^2.1.0", - "lodash": "^4.11.1", - "mem-fs-editor": "^3.0.0", - "minimist": "^1.2.0", - "mkdirp": "^0.5.0", - "path-exists": "^3.0.0", - "path-is-absolute": "^1.0.0", - "pretty-bytes": "^4.0.2", - "read-chunk": "^2.0.0", - "read-pkg-up": "^2.0.0", - "rimraf": "^2.2.0", - "run-async": "^2.0.0", - "shelljs": "^0.7.0", - "text-table": "^0.2.0", - "through2": "^2.0.0", - "user-home": "^2.0.0", - "yeoman-environment": "^1.1.0" - }, - "dependencies": { - "inquirer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", - "dev": true, - "requires": { - "ansi-escapes": "^1.1.0", - "chalk": "^1.0.0", - "cli-cursor": "^1.0.1", - "cli-width": "^2.0.0", - "external-editor": "^1.1.0", - "figures": "^1.3.5", - "lodash": "^4.3.0", - "mute-stream": "0.0.6", - "pinkie-promise": "^2.0.0", - "run-async": "^2.2.0", - "rx": "^4.1.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.0", - "through": "^2.3.6" - } - }, - "yeoman-environment": { - "version": "1.6.6", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-1.6.6.tgz", - "integrity": "sha1-zYX6Z9FWBg5EDXgH1+988NLR1nE=", - "dev": true, - "requires": { - "chalk": "^1.0.0", - "debug": "^2.0.0", - "diff": "^2.1.2", - "escape-string-regexp": "^1.0.2", - "globby": "^4.0.0", - "grouped-queue": "^0.3.0", - "inquirer": "^1.0.2", - "lodash": "^4.11.1", - "log-symbols": "^1.0.1", - "mem-fs": "^1.1.0", - "text-table": "^0.2.0", - "untildify": "^2.0.0" - } - } + "has-flag": "^3.0.0" } } } }, - "yosay": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/yosay/-/yosay-2.0.2.tgz", - "integrity": "sha512-avX6nz2esp7IMXGag4gu6OyQBsMh/SEn+ZybGu3yKPlOTE6z9qJrzG/0X5vCq/e0rPFy0CUYCze0G5hL310ibA==", - "requires": { - "ansi-regex": "^2.0.0", - "ansi-styles": "^3.0.0", - "chalk": "^1.0.0", - "cli-boxes": "^1.0.0", - "pad-component": "0.0.1", - "string-width": "^2.0.0", - "strip-ansi": "^3.0.0", - "taketalk": "^1.0.0", - "wrap-ansi": "^2.0.0" + "yeoman-generator": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.10.1.tgz", + "integrity": "sha512-QgbtHSaqBAkyJJM0heQUhT63ubCt34NBFMEBydOBUdAuy8RBvGSzeqVBSZOjdh1tSLrwWXlU3Ck6y14awinF6Q==", + "requires": { + "async": "^2.6.2", + "chalk": "^2.4.2", + "cli-table": "^0.3.1", + "cross-spawn": "^6.0.5", + "dargs": "^6.1.0", + "dateformat": "^3.0.3", + "debug": "^4.1.1", + "diff": "^4.0.1", + "error": "^7.0.2", + "find-up": "^3.0.0", + "github-username": "^3.0.0", + "grouped-queue": "^1.1.0", + "istextorbinary": "^2.5.1", + "lodash": "^4.17.11", + "make-dir": "^3.0.0", + "mem-fs-editor": "^6.0.0", + "minimist": "^1.2.5", + "pretty-bytes": "^5.2.0", + "read-chunk": "^3.2.0", + "read-pkg-up": "^5.0.0", + "rimraf": "^2.6.3", + "run-async": "^2.0.0", + "semver": "^7.2.1", + "shelljs": "^0.8.3", + "text-table": "^0.2.0", + "through2": "^3.0.1", + "yeoman-environment": "^2.9.5" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } }, "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" - } + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" } }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "requires": { - "ansi-regex": "^2.0.0" + "color-name": "1.1.3" } }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=" + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } } } } diff --git a/Generator/generator-botbuilder-java/package.json b/Generator/generator-botbuilder-java/package.json index 82a38d3f9..c4e374cf5 100644 --- a/Generator/generator-botbuilder-java/package.json +++ b/Generator/generator-botbuilder-java/package.json @@ -1,87 +1,33 @@ { - "name": "generator-botbuilder-java", - "version": "0.0.0", - "description": "Template to create conversational bots in Java using Microsoft Bot Framework.", - "homepage": "https://github.com/Microsoft/botbuilder-java", - "author": { - "name": "Microsoft", - "email": "", - "url": "" - }, - "files": [ - "generators" - ], - "main": "generators/index.js", - "keywords": [ - "bot", - "bots", - "chatbots", - "bot framework", - "yeoman-generator" - ], - "devDependencies": { - "yeoman-test": "^1.7.0", - "yeoman-assert": "^3.1.0", - "coveralls": "^3.0.0", - "nsp": "^2.8.0", - "eslint": "^4.19.1", - "prettier": "^1.11.1", - "husky": "^0.14.3", - "lint-staged": "^6.1.1", - "eslint-config-prettier": "^2.9.0", - "eslint-plugin-prettier": "^2.6.0", - "eslint-config-xo": "^0.20.1", - "jest": "^22.0.6" - }, - "engines": { - "npm": ">= 4.0.0" - }, - "dependencies": { - "yeoman-generator": "^2.0.1", - "chalk": "^2.1.0", - "yosay": "^2.0.1" - }, - "jest": { - "testEnvironment": "node" - }, - "scripts": { - "prepublishOnly": "nsp check", - "pretest": "eslint .", - "precommit": "lint-staged", - "test": "jest" - }, - "lint-staged": { - "*.js": [ - "eslint --fix", - "git add" + "name": "generator-java", + "version": "4.9.1", + "description": "A yeoman generator for creating Java bots built with Bot Framework v4", + "homepage": "https://github.com/Microsoft/BotBuilder-Samples/tree/master/generators/generator-botbuilder", + "author": { + "name": "Microsoft", + "email": "botframework@microsoft.com", + "url": "http://dev.botframework.com" + }, + "license": "SEE LICENSE IN LICENSE.md", + "repository": "https://github.com/Microsoft/BotBuilder-Samples.git", + "files": [ + "components", + "generators" ], - "*.json": [ - "prettier --write", - "git add" - ] - }, - "eslintConfig": { - "extends": [ - "xo", - "prettier" + "keywords": [ + "botbuilder", + "bots", + "bot framework", + "yeoman-generator", + "Microsoft AI", + "Microsoft Teams", + "Conversational AI" ], - "env": { - "jest": true, - "node": true - }, - "rules": { - "prettier/prettier": [ - "error", - { - "singleQuote": true, - "printWidth": 90 - } - ] - }, - "plugins": [ - "prettier" - ] - }, - "repository": "https://github.com/Microsoft/botbuilder-java.git", - "license": "MIT" + "dependencies": { + "chalk": "~4.0.0", + "lodash": "~4.17.15", + "mkdirp": "^1.0.4", + "camelcase": "~6.0.0", + "yeoman-generator": "~4.10.1" + } } From 398ecc609f1337a04f5f1af75e92010113825371 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 21 Dec 2020 09:24:16 -0600 Subject: [PATCH 037/221] Samples tests now run on build again (#885) Co-authored-by: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> --- samples/02.echo-bot/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/03.welcome-user/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/08.suggested-actions/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/16.proactive-messages/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/45.state-management/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/47.inspection/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- .../pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- .../pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- .../pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- .../pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/54.teams-task-module/pom.xml | 6 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/55.teams-link-unfurling/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/56.teams-file-upload/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- samples/57.teams-conversation-bot/pom.xml | 5 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- .../pom.xml | 6 +++ ...icationTests.java => ApplicationTest.java} | 38 +++++++++---------- 30 files changed, 362 insertions(+), 285 deletions(-) rename samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/{ApplicationTests.java => ApplicationTest.java} (89%) rename samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/{ApplicationTests.java => ApplicationTest.java} (89%) diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index 8bb55de88..58da4dc5b 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTests.java b/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java similarity index 89% rename from samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTests.java rename to samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java index 68c51b15c..7cab25783 100644 --- a/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTests.java +++ b/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.echo; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.echo; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index f9ebd8cd0..72edaa5ef 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -57,6 +57,11 @@ 4.13.1 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTests.java b/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java similarity index 89% rename from samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTests.java rename to samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java index 5cfb1289b..693bec16e 100644 --- a/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTests.java +++ b/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.welcomeuser; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.welcomeuser; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 26774790a..1070cd534 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -57,6 +57,11 @@ 4.13.1 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTests.java b/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java similarity index 89% rename from samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTests.java rename to samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java index 8b23b71e3..387aac524 100644 --- a/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTests.java +++ b/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.suggestedactions; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.suggestedactions; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index 2c38c8c8b..d57074b94 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -57,6 +57,11 @@ 4.13.1 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTests.java b/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java similarity index 89% rename from samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTests.java rename to samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java index 865705c44..5e8974cb9 100644 --- a/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTests.java +++ b/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.proactive; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 6b6809f6a..810b2ff63 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -56,6 +56,11 @@ 4.13.1 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTests.java b/samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTest.java similarity index 89% rename from samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTests.java rename to samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTest.java index 5df365585..0702c93b6 100644 --- a/samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTests.java +++ b/samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.statemanagement; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.statemanagement; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index 6f0912c2b..c9234069f 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTests.java b/samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTest.java similarity index 89% rename from samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTests.java rename to samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTest.java index 7bbace3ad..4b995f565 100644 --- a/samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTests.java +++ b/samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.inspection; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.inspection; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index a2ea70374..a0f0cee95 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTests.java b/samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTest.java similarity index 89% rename from samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTests.java rename to samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTest.java index 43ab7a1b2..38d711971 100644 --- a/samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTests.java +++ b/samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamssearch; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamssearch; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index 1e176b13f..7c1bcf328 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTests.java b/samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTest.java similarity index 89% rename from samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTests.java rename to samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTest.java index aa0503096..b7da9a55e 100644 --- a/samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTests.java +++ b/samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsaction; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsaction; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index d477d6420..75875b517 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -62,6 +62,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTests.java b/samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTest.java similarity index 89% rename from samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTests.java rename to samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTest.java index 01c209f17..5bb912daf 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTests.java +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamssearchauth; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamssearchauth; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index ac9a5c38b..520a821ea 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTests.java b/samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTest.java similarity index 89% rename from samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTests.java rename to samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTest.java index 68f70f1e7..b519a8d0d 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTests.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsactionpreview; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsactionpreview; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index b8b781653..df0eb0454 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -57,6 +57,12 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + + org.slf4j slf4j-api diff --git a/samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTests.java b/samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTest.java similarity index 89% rename from samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTests.java rename to samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTest.java index 87a369897..299bf9f73 100644 --- a/samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTests.java +++ b/samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamstaskmodule; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamstaskmodule; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index 43750d1b2..31149dbf3 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTests.java b/samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTest.java similarity index 89% rename from samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTests.java rename to samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTest.java index b653c4e76..82d21bd8f 100644 --- a/samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTests.java +++ b/samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsunfurl; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsunfurl; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index b9c97ec68..8561fb30c 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTests.java b/samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTest.java similarity index 89% rename from samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTests.java rename to samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTest.java index cdd838d56..11a2cc7f3 100644 --- a/samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTests.java +++ b/samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsfileupload; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsfileupload; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index dff36819f..b89168a1f 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -57,6 +57,11 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + org.slf4j diff --git a/samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTests.java b/samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTest.java similarity index 89% rename from samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTests.java rename to samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTest.java index 9b98c3963..03d5ab006 100644 --- a/samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTests.java +++ b/samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsconversation; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsconversation; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index 3a79037ed..3967d715e 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -57,6 +57,12 @@ 2.4.0 test + + org.junit.vintage + junit-vintage-engine + test + + org.slf4j slf4j-api diff --git a/samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTests.java b/samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTest.java similarity index 89% rename from samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTests.java rename to samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTest.java index 85964b3ba..2ff78e9ba 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTests.java +++ b/samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTest.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.teamsstartnewthread; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsstartnewthread; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 1eca55688c6e9160f3272a368c6a000757816512 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 7 Jan 2021 16:02:53 -0600 Subject: [PATCH 038/221] Refactored restclient to remove uneeded code (#895) --- .../bot/builder/BotFrameworkAdapter.java | 2 +- .../bot/builder/MemoryConnectorClient.java | 4 +- .../bot/builder/MockConnectorClient.java | 4 +- .../bot/builder/TurnContextTests.java | 4 +- .../microsoft/bot/builder/base/TestBase.java | 14 +- .../teams/TeamsActivityHandlerTests.java | 2 +- libraries/bot-connector/pom.xml | 4 +- .../bot/azure/AzureAsyncOperation.java | 154 ----- .../microsoft/bot/azure/AzureEnvironment.java | 329 ---------- .../bot/azure/AzureResponseBuilder.java | 113 ---- .../bot/azure/AzureServiceClient.java | 92 --- .../bot/azure/AzureServiceFuture.java.dep | 124 ---- .../com/microsoft/bot/azure/CloudError.java | 119 ---- .../microsoft/bot/azure/CloudException.java | 51 -- .../bot/azure/ListOperationCallback.java | 103 --- .../bot/azure/LongRunningFinalState.java | 29 - .../azure/LongRunningOperationOptions.java | 40 -- .../java/com/microsoft/bot/azure/Page.java | 30 - .../com/microsoft/bot/azure/PagedList.java | 413 ------------ .../microsoft/bot/azure/PolicyViolation.java | 47 -- .../bot/azure/PolicyViolationErrorInfo.java | 186 ------ .../com/microsoft/bot/azure/PollingState.java | 590 ------------------ .../microsoft/bot/azure/ProxyResource.java | 59 -- .../com/microsoft/bot/azure/Resource.java | 67 -- .../com/microsoft/bot/azure/SubResource.java | 37 -- .../microsoft/bot/azure/TypedErrorInfo.java | 48 -- .../credentials/AzureTokenCredentials.java | 174 ------ .../AzureTokenCredentialsInterceptor.java | 42 -- .../bot/azure/credentials/package-info.java | 6 - .../com/microsoft/bot/azure/package-info.java | 6 - .../azure/serializer/AzureJacksonAdapter.java | 25 - .../serializer/CloudErrorDeserializer.java | 67 -- .../TypedErrorInfoDeserializer.java | 67 -- .../bot/azure/serializer/package-info.java | 5 - .../bot/connector/ConnectorClient.java | 4 +- .../bot/connector/Conversations.java | 4 +- .../authentication/AppCredentials.java | 2 +- .../rest/ErrorResponseException.java | 2 +- .../bot/connector/rest/RestAttachments.java | 2 +- .../bot/connector/rest/RestBotSignIn.java | 2 +- .../connector/rest/RestConnectorClient.java | 18 +- .../bot/connector/rest/RestConversations.java | 8 +- .../bot/connector/rest/RestOAuthClient.java | 8 +- .../rest/RestTeamsConnectorClient.java | 18 +- .../connector/rest/RestTeamsOperations.java | 2 +- .../bot/connector/rest/RestUserToken.java | 4 +- .../connector/teams/TeamsConnectorClient.java | 4 +- .../bot/rest/DateTimeRfc1123.java.dep | 79 --- .../com/microsoft/bot/rest/ServiceClient.java | 83 --- .../microsoft/bot/rest/ServiceFuture.java.dep | 216 ------- .../bot/rest/credentials/package-info.java | 5 - .../DateTimeRfc1123Serializer.java.dep | 42 -- .../serializer/DateTimeSerializer.java.dep | 45 -- .../bot/{rest => restclient}/Base64Url.java | 10 +- .../CollectionFormat.java | 12 +- .../ExpandableStringEnum.java | 15 +- .../bot/{rest => restclient}/LogLevel.java | 9 +- .../bot/{rest => restclient}/RestClient.java | 33 +- .../{rest => restclient}/RestException.java | 11 +- .../{rest => restclient}/ServiceCallback.java | 9 +- .../bot/restclient/ServiceClient.java | 145 +++++ .../{rest => restclient}/ServiceResponse.java | 9 +- .../ServiceResponseBuilder.java | 36 +- .../ServiceResponseWithHeaders.java | 11 +- .../SkipParentValidation.java | 9 +- .../bot/{rest => restclient}/Validator.java | 11 +- .../BasicAuthenticationCredentials.java | 28 +- ...cAuthenticationCredentialsInterceptor.java | 26 +- .../credentials/ServiceClientCredentials.java | 9 +- .../credentials/TokenCredentials.java | 32 +- .../TokenCredentialsInterceptor.java | 11 +- .../restclient/credentials/package-info.java | 8 + .../interceptors/BaseUrlHandler.java | 9 +- .../CustomHeadersInterceptor.java | 17 +- .../interceptors/LoggingInterceptor.java | 22 +- .../RequestIdHeaderInterceptor.java | 9 +- .../interceptors/UserAgentInterceptor.java | 9 +- .../interceptors/package-info.java | 2 +- .../{rest => restclient}/package-info.java | 2 +- .../protocol/Environment.java | 9 +- .../protocol/ResponseBuilder.java | 15 +- .../protocol/SerializerAdapter.java | 11 +- .../protocol/package-info.java | 2 +- .../ExponentialBackoffRetryStrategy.java | 15 +- .../retry/RetryHandler.java | 11 +- .../retry/RetryStrategy.java | 13 +- .../retry/package-info.java | 2 +- .../AdditionalPropertiesDeserializer.java | 11 +- .../AdditionalPropertiesSerializer.java | 11 +- .../serializer/Base64UrlSerializer.java | 11 +- .../serializer/ByteArraySerializer.java | 9 +- .../serializer/FlatteningDeserializer.java | 23 +- .../serializer/FlatteningSerializer.java | 19 +- .../serializer/HeadersSerializer.java | 11 +- .../serializer/JacksonAdapter.java | 15 +- .../serializer/JacksonConverterFactory.java | 25 +- .../serializer/JsonFlatten.java | 9 +- .../serializer/package-info.java | 2 +- .../AzureAsyncOperationDeserializerTests.java | 66 -- .../azure/CloudErrorDeserializerTests.java | 176 ------ .../microsoft/bot/azure/PagedListTests.java | 298 --------- .../RequestIdHeaderInterceptorTests.java | 119 ---- .../bot/connector/BotAccessTokenStub.java | 2 +- .../bot/connector/BotConnectorTestBase.java | 2 +- .../bot/connector/OAuthTestBase.java | 2 +- .../bot/connector/base/TestBase.java | 14 +- .../AdditionalPropertiesSerializerTests.java | 8 +- .../{rest => restclient}/AnimalShelter.java | 4 +- .../AnimalWithTypeIdContainingDot.java | 2 +- .../CatWithTypeIdContainingDot.java | 2 +- .../{rest => restclient}/ComposeTurtles.java | 2 +- .../ConnectionPoolTests.java.dep | 0 .../CredentialsTests.java | 7 +- .../DogWithTypeIdContainingDot.java | 2 +- .../FlattenableAnimalInfo.java | 2 +- .../FlatteningSerializerTests.java | 8 +- ...NonEmptyAnimalWithTypeIdContainingDot.java | 2 +- .../RabbitWithTypeIdContainingDot.java | 2 +- .../{rest => restclient}/RestClientTests.java | 19 +- .../RetryHandlerTests.java | 5 +- .../ServiceClientTests.java | 3 +- .../TurtleWithTypeIdContainingDot.java | 2 +- .../{rest => restclient}/UserAgentTests.java | 5 +- .../{rest => restclient}/ValidatorTests.java | 4 +- .../bot/{rest => restclient}/util/Foo.java | 4 +- .../{rest => restclient}/util/FooChild.java | 4 +- 126 files changed, 485 insertions(+), 4614 deletions(-) delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureAsyncOperation.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureEnvironment.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureResponseBuilder.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceClient.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceFuture.java.dep delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudError.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudException.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ListOperationCallback.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningFinalState.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningOperationOptions.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Page.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PagedList.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolation.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolationErrorInfo.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PollingState.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ProxyResource.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Resource.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/SubResource.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/TypedErrorInfo.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentials.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentialsInterceptor.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/package-info.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/package-info.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/AzureJacksonAdapter.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/CloudErrorDeserializer.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/TypedErrorInfoDeserializer.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/package-info.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/DateTimeRfc1123.java.dep delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceClient.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceFuture.java.dep delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/package-info.java delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeRfc1123Serializer.java.dep delete mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeSerializer.java.dep rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/Base64Url.java (90%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/CollectionFormat.java (85%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/ExpandableStringEnum.java (91%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/LogLevel.java (85%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/RestClient.java (95%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/RestException.java (85%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/ServiceCallback.java (70%) create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceClient.java rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/ServiceResponse.java (90%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/ServiceResponseBuilder.java (90%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/ServiceResponseWithHeaders.java (85%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/SkipParentValidation.java (67%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/Validator.java (94%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/credentials/BasicAuthenticationCredentials.java (56%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/credentials/BasicAuthenticationCredentialsInterceptor.java (60%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/credentials/ServiceClientCredentials.java (66%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/credentials/TokenCredentials.java (57%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/credentials/TokenCredentialsInterceptor.java (78%) create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/package-info.java rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/BaseUrlHandler.java (89%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/CustomHeadersInterceptor.java (89%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/LoggingInterceptor.java (93%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/RequestIdHeaderInterceptor.java (77%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/UserAgentInterceptor.java (90%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/interceptors/package-info.java (60%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/package-info.java (86%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/protocol/Environment.java (72%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/protocol/ResponseBuilder.java (93%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/protocol/SerializerAdapter.java (88%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/protocol/package-info.java (73%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/retry/ExponentialBackoffRetryStrategy.java (92%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/retry/RetryHandler.java (90%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/retry/RetryStrategy.java (89%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/retry/package-info.java (72%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/AdditionalPropertiesDeserializer.java (94%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/AdditionalPropertiesSerializer.java (95%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/Base64UrlSerializer.java (79%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/ByteArraySerializer.java (83%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/FlatteningDeserializer.java (93%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/FlatteningSerializer.java (94%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/HeadersSerializer.java (83%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/JacksonAdapter.java (94%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/JacksonConverterFactory.java (80%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/JsonFlatten.java (70%) rename libraries/bot-connector/src/main/java/com/microsoft/bot/{rest => restclient}/serializer/package-info.java (70%) delete mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/azure/AzureAsyncOperationDeserializerTests.java delete mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/azure/CloudErrorDeserializerTests.java delete mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/azure/PagedListTests.java delete mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/azure/RequestIdHeaderInterceptorTests.java rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/AdditionalPropertiesSerializerTests.java (95%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/AnimalShelter.java (88%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/AnimalWithTypeIdContainingDot.java (95%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/CatWithTypeIdContainingDot.java (95%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/ComposeTurtles.java (98%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/ConnectionPoolTests.java.dep (100%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/CredentialsTests.java (93%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/DogWithTypeIdContainingDot.java (96%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/FlattenableAnimalInfo.java (94%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/FlatteningSerializerTests.java (99%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/NonEmptyAnimalWithTypeIdContainingDot.java (95%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/RabbitWithTypeIdContainingDot.java (96%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/RestClientTests.java (92%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/RetryHandlerTests.java (96%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/ServiceClientTests.java (96%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/TurtleWithTypeIdContainingDot.java (94%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/UserAgentTests.java (95%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/ValidatorTests.java (98%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/util/Foo.java (91%) rename libraries/bot-connector/src/test/java/com/microsoft/bot/{rest => restclient}/util/FooChild.java (83%) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index a150fa6f8..9274bb97a 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -41,7 +41,7 @@ import com.microsoft.bot.schema.TokenExchangeState; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; -import com.microsoft.bot.rest.retry.RetryStrategy; +import com.microsoft.bot.restclient.retry.RetryStrategy; import java.util.Collections; import org.apache.commons.lang3.StringUtils; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java index 6e0320648..d18d99dc4 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java @@ -3,8 +3,8 @@ import com.microsoft.bot.connector.Attachments; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; public class MemoryConnectorClient implements ConnectorClient { private MemoryConversations conversations = new MemoryConversations(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java index cda3dd92f..757c51894 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java @@ -4,8 +4,8 @@ import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; import com.microsoft.bot.connector.authentication.AppCredentials; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; public class MockConnectorClient implements ConnectorClient { diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java index e60272940..c46567d31 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java @@ -8,13 +8,13 @@ import com.microsoft.bot.connector.Attachments; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ConversationAccount; import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.ResourceResponse; -import com.microsoft.bot.rest.RestClient; +import com.microsoft.bot.restclient.RestClient; import org.junit.Assert; import org.junit.Test; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/base/TestBase.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/base/TestBase.java index 18d5c90ed..7e0f5b4eb 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/base/TestBase.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/base/TestBase.java @@ -4,13 +4,13 @@ package com.microsoft.bot.builder.base; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; -import com.microsoft.bot.rest.LogLevel; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.ServiceResponseBuilder; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import com.microsoft.bot.rest.credentials.TokenCredentials; -import com.microsoft.bot.rest.interceptors.LoggingInterceptor; -import com.microsoft.bot.rest.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.LogLevel; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.ServiceResponseBuilder; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.credentials.TokenCredentials; +import com.microsoft.bot.restclient.interceptors.LoggingInterceptor; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import org.junit.*; import org.junit.rules.TestName; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java index c656c1627..0dddcac0f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java @@ -15,7 +15,7 @@ import com.microsoft.bot.connector.Conversations; import com.microsoft.bot.connector.authentication.AppCredentials; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; -import com.microsoft.bot.rest.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index d764843bf..17af92eb6 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -54,7 +54,7 @@ com.google.guava guava - 20.0 + 30.1-jre com.squareup.retrofit2 @@ -174,7 +174,7 @@ org.apache.maven.plugins maven-checkstyle-plugin - com/microsoft/bot/rest/**,com/microsoft/bot/azure/** + com/microsoft/bot/restclient/** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureAsyncOperation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureAsyncOperation.java deleted file mode 100644 index c1f450a1e..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureAsyncOperation.java +++ /dev/null @@ -1,154 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import okhttp3.ResponseBody; -import retrofit2.Response; - -import java.io.IOException; -import java.util.Arrays; -import java.util.List; - -/** - * The response body contains the status of the specified - * asynchronous operation, indicating whether it has succeeded, is in - * progress, or has failed. Note that this status is distinct from the - * HTTP status code returned for the Get Operation Status operation - * itself. If the asynchronous operation succeeded, the response body - * includes the HTTP status code for the successful request. If the - * asynchronous operation failed, the response body includes the HTTP - * status code for the failed request, and also includes error - * information regarding the failure. - */ -final class AzureAsyncOperation { - /** - * Default delay in seconds for long running operations. - */ - static final int DEFAULT_DELAY = 30; - - /** - * Successful status for long running operations. - */ - static final String SUCCESS_STATUS = "Succeeded"; - - /** - * In progress status for long running operations. - */ - static final String IN_PROGRESS_STATUS = "InProgress"; - - /** - * Failed status for long running operations. - */ - static final String FAILED_STATUS = "Failed"; - - /** - * Canceled status for long running operations. - */ - static final String CANCELED_STATUS = "Canceled"; - - /** - * @return a list of statuses indicating a failed operation - */ - static List failedStatuses() { - return Arrays.asList(FAILED_STATUS, CANCELED_STATUS); - } - - /** - * @return a list of terminal statuses for long running operations - */ - static List terminalStatuses() { - return Arrays.asList(FAILED_STATUS, CANCELED_STATUS, SUCCESS_STATUS); - } - - /** - * The status of the asynchronous request. - */ - private String status; - - /** - * @return the status of the asynchronous request - */ - String status() { - return this.status; - } - - /** - * Sets the status of the asynchronous request. - * - * @param status the status of the asynchronous request. - */ - void setStatus(String status) { - this.status = status; - } - - /** - * If the asynchronous operation failed, the response body includes - * the HTTP status code for the failed request, and also includes - * error information regarding the failure. - */ - private CloudError error; - - /** - * Gets the cloud error. - * - * @return the cloud error. - */ - CloudError getError() { - return this.error; - } - - /** - * Sets the cloud error. - * - * @param error the cloud error. - */ - void setError(CloudError error) { - this.error = error; - } - - /** - * Async operation in string format. - */ - private String rawString; - - /** - * @return the raw string - */ - String rawString() { - return this.rawString; - } - - /** - * Creates AzureAsyncOperation from the given HTTP response. - * - * @param serializerAdapter the adapter to use for deserialization - * @param response the response - * @return the async operation object - * @throws CloudException if the deserialization fails or response contains invalid body - */ - static AzureAsyncOperation fromResponse(SerializerAdapter serializerAdapter, Response response) throws CloudException { - AzureAsyncOperation asyncOperation = null; - String rawString = null; - if (response.body() != null) { - try { - rawString = response.body().string(); - asyncOperation = serializerAdapter.deserialize(rawString, AzureAsyncOperation.class); - } catch (IOException ignore) { - // Exception will be handled below - } finally { - response.body().close(); - } - } - if (asyncOperation == null || asyncOperation.status() == null) { - throw new CloudException("polling response does not contain a valid body: " + rawString, response); - } else { - asyncOperation.rawString = rawString; - } - return asyncOperation; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureEnvironment.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureEnvironment.java deleted file mode 100644 index 9f9f5ed61..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureEnvironment.java +++ /dev/null @@ -1,329 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.rest.protocol.Environment; - -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -/** - * An instance of this class describes an environment in Azure. - */ -public final class AzureEnvironment implements Environment { - /** the map of all endpoints. */ - private final Map endpoints; - - /** - * Initializes an instance of AzureEnvironment class. - * - * @param endpoints a map storing all the endpoint info - */ - public AzureEnvironment(Map endpoints) { - this.endpoints = endpoints; - } - - /** - * Provides the settings for authentication with Azure. - */ - public static final AzureEnvironment AZURE = new AzureEnvironment(new HashMap() {{ - put("portalUrl", "http://go.microsoft.com/fwlink/?LinkId=254433"); - put("publishingProfileUrl", "http://go.microsoft.com/fwlink/?LinkId=254432"); - put("managementEndpointUrl", "https://management.core.windows.net/"); - put("resourceManagerEndpointUrl", "https://management.azure.com/"); - put("sqlManagementEndpointUrl", "https://management.core.windows.net:8443/"); - put("sqlServerHostnameSuffix", ".database.windows.net"); - put("galleryEndpointUrl", "https://gallery.azure.com/"); - put("activeDirectoryEndpointUrl", "https://login.microsoftonline.com/"); - put("activeDirectoryResourceId", "https://management.core.windows.net/"); - put("activeDirectoryGraphResourceId", "https://graph.windows.net/"); - put("dataLakeEndpointResourceId", "https://datalake.azure.net/"); - put("activeDirectoryGraphApiVersion", "2013-04-05"); - put("storageEndpointSuffix", ".core.windows.net"); - put("keyVaultDnsSuffix", ".vault.azure.net"); - put("azureDataLakeStoreFileSystemEndpointSuffix", "azuredatalakestore.net"); - put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "azuredatalakeanalytics.net"); - put("azureLogAnalyticsResourceId", "https://api.loganalytics.io/"); - put("azureApplicationInsightsResourceId", "https://api.applicationinsights.io/"); - }}); - - /** - * Provides the settings for authentication with Azure China. - */ - public static final AzureEnvironment AZURE_CHINA = new AzureEnvironment(new HashMap() {{ - put("portalUrl", "http://go.microsoft.com/fwlink/?LinkId=301902"); - put("publishingProfileUrl", "http://go.microsoft.com/fwlink/?LinkID=301774"); - put("managementEndpointUrl", "https://management.core.chinacloudapi.cn/"); - put("resourceManagerEndpointUrl", "https://management.chinacloudapi.cn/"); - put("sqlManagementEndpointUrl", "https://management.core.chinacloudapi.cn:8443/"); - put("sqlServerHostnameSuffix", ".database.chinacloudapi.cn"); - put("galleryEndpointUrl", "https://gallery.chinacloudapi.cn/"); - put("activeDirectoryEndpointUrl", "https://login.chinacloudapi.cn/"); - put("activeDirectoryResourceId", "https://management.core.chinacloudapi.cn/"); - put("activeDirectoryGraphResourceId", "https://graph.chinacloudapi.cn/"); - // TODO: add resource id for the china cloud for datalake once it is defined. - put("dataLakeEndpointResourceId", "N/A"); - put("activeDirectoryGraphApiVersion", "2013-04-05"); - put("storageEndpointSuffix", ".core.chinacloudapi.cn"); - put("keyVaultDnsSuffix", ".vault.azure.cn"); - // TODO: add dns suffixes for the china cloud for datalake store and datalake analytics once they are defined. - put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A"); - put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A"); - put("azureLogAnalyticsResourceId", "N/A"); - put("azureApplicationInsightsResourceId", "N/A"); - }}); - - /** - * Provides the settings for authentication with Azure US Government. - */ - public static final AzureEnvironment AZURE_US_GOVERNMENT = new AzureEnvironment(new HashMap() {{ - put("portalUrl", "https://manage.windowsazure.us"); - put("publishingProfileUrl", "https://manage.windowsazure.us/publishsettings/index"); - put("managementEndpointUrl", "https://management.core.usgovcloudapi.net/"); - put("resourceManagerEndpointUrl", "https://management.usgovcloudapi.net/"); - put("sqlManagementEndpointUrl", "https://management.core.usgovcloudapi.net:8443/"); - put("sqlServerHostnameSuffix", ".database.usgovcloudapi.net"); - put("galleryEndpointUrl", "https://gallery.usgovcloudapi.net/"); - put("activeDirectoryEndpointUrl", "https://login.microsoftonline.us/"); - put("activeDirectoryResourceId", "https://management.core.usgovcloudapi.net/"); - put("activeDirectoryGraphResourceId", "https://graph.windows.net/"); - // TODO: add resource id for the US government for datalake once it is defined. - put("dataLakeEndpointResourceId", "N/A"); - put("activeDirectoryGraphApiVersion", "2013-04-05"); - put("storageEndpointSuffix", ".core.usgovcloudapi.net"); - put("keyVaultDnsSuffix", ".vault.usgovcloudapi.net"); - // TODO: add dns suffixes for the US government for datalake store and datalake analytics once they are defined. - put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A"); - put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A"); - put("azureLogAnalyticsResourceId", "https://api.loganalytics.us/"); - put("azureApplicationInsightsResourceId", "N/A"); - }}); - - /** - * Provides the settings for authentication with Azure Germany. - */ - public static final AzureEnvironment AZURE_GERMANY = new AzureEnvironment(new HashMap() {{ - put("portalUrl", "http://portal.microsoftazure.de/"); - put("publishingProfileUrl", "https://manage.microsoftazure.de/publishsettings/index"); - put("managementEndpointUrl", "https://management.core.cloudapi.de/"); - put("resourceManagerEndpointUrl", "https://management.microsoftazure.de/"); - put("sqlManagementEndpointUrl", "https://management.core.cloudapi.de:8443/"); - put("sqlServerHostnameSuffix", ".database.cloudapi.de"); - put("galleryEndpointUrl", "https://gallery.cloudapi.de/"); - put("activeDirectoryEndpointUrl", "https://login.microsoftonline.de/"); - put("activeDirectoryResourceId", "https://management.core.cloudapi.de/"); - put("activeDirectoryGraphResourceId", "https://graph.cloudapi.de/"); - // TODO: add resource id for the germany cloud for datalake once it is defined. - put("dataLakeEndpointResourceId", "N/A"); - put("activeDirectoryGraphApiVersion", "2013-04-05"); - put("storageEndpointSuffix", ".core.cloudapi.de"); - put("keyVaultDnsSuffix", ".vault.microsoftazure.de"); - // TODO: add dns suffixes for the germany cloud for datalake store and datalake analytics once they are defined. - put("azureDataLakeStoreFileSystemEndpointSuffix", "N/A"); - put("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix", "N/A"); - put("azureLogAnalyticsResourceId", "N/A"); - put("azureApplicationInsightsResourceId", "N/A"); - }}); - - /** - * @return the entirety of the endpoints associated with the current environment. - */ - public Map endpoints() { - return endpoints; - } - - /** - * @return the array of known environments to Azure SDK. - */ - public static AzureEnvironment[] knownEnvironments() { - List environments = Arrays.asList(AZURE, AZURE_CHINA, AZURE_GERMANY, AZURE_US_GOVERNMENT); - return environments.toArray(new AzureEnvironment[environments.size()]); - } - - /** - * @return the management portal URL. - */ - public String portal() { - return endpoints.get("portalUrl"); - } - - /** - * @return the publish settings file URL. - */ - public String publishingProfile() { - return endpoints.get("publishingProfileUrl"); - } - - /** - * @return the management service endpoint. - */ - public String managementEndpoint() { - return endpoints.get("managementEndpointUrl"); - } - - /** - * @return the resource management endpoint. - */ - public String resourceManagerEndpoint() { - return endpoints.get("resourceManagerEndpointUrl"); - } - - /** - * @return the sql server management endpoint for mobile commands. - */ - public String sqlManagementEndpoint() { - return endpoints.get("sqlManagementEndpointUrl"); - } - - /** - * @return the dns suffix for sql servers. - */ - public String sqlServerHostnameSuffix() { - return endpoints.get("sqlServerHostnameSuffix"); - } - - /** - * @return the Active Directory login endpoint. - */ - public String activeDirectoryEndpoint() { - return endpoints.get("activeDirectoryEndpointUrl").replaceAll("/$", "") + "/"; - } - - /** - * @return The resource ID to obtain AD tokens for. - */ - public String activeDirectoryResourceId() { - return endpoints.get("activeDirectoryResourceId"); - } - - /** - * @return the template gallery endpoint. - */ - public String galleryEndpoint() { - return endpoints.get("galleryEndpointUrl"); - } - - /** - * @return the Active Directory resource ID. - */ - public String graphEndpoint() { - return endpoints.get("activeDirectoryGraphResourceId"); - } - - /** - * @return the Data Lake resource ID. - */ - public String dataLakeEndpointResourceId() { - return endpoints.get("dataLakeEndpointResourceId"); - } - - /** - * @return the Active Directory api version. - */ - public String activeDirectoryGraphApiVersion() { - return endpoints.get("activeDirectoryGraphApiVersion"); - } - - /** - * @return the endpoint suffix for storage accounts. - */ - public String storageEndpointSuffix() { - return endpoints.get("storageEndpointSuffix"); - } - - /** - * @return the keyvault service dns suffix. - */ - public String keyVaultDnsSuffix() { - return endpoints.get("keyVaultDnsSuffix"); - } - - /** - * @return the data lake store filesystem service dns suffix. - */ - public String azureDataLakeStoreFileSystemEndpointSuffix() { - return endpoints.get("azureDataLakeStoreFileSystemEndpointSuffix"); - } - - /** - * @return the data lake analytics job and catalog service dns suffix. - */ - public String azureDataLakeAnalyticsCatalogAndJobEndpointSuffix() { - return endpoints.get("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix"); - } - - /** - * @return the log analytics endpoint. - */ - public String logAnalyticsEndpoint() { - return endpoints.get("azureLogAnalyticsResourceId"); - } - - /** - * @return the log analytics endpoint. - */ - public String applicationInsightsEndpoint() { - return endpoints.get("azureApplicationInsightsResourceId"); - } - - - /** - * The enum representing available endpoints in an environment. - */ - public enum Endpoint implements Environment.Endpoint { - /** Azure management endpoint. */ - MANAGEMENT("managementEndpointUrl"), - /** Azure Resource Manager endpoint. */ - RESOURCE_MANAGER("resourceManagerEndpointUrl"), - /** Azure SQL endpoint. */ - SQL("sqlManagementEndpointUrl"), - /** Azure Gallery endpoint. */ - GALLERY("galleryEndpointUrl"), - /** Active Directory authentication endpoint. */ - ACTIVE_DIRECTORY("activeDirectoryEndpointUrl"), - /** Azure Active Directory Graph APIs endpoint. */ - GRAPH("activeDirectoryGraphResourceId"), - /** Key Vault DNS suffix. */ - KEYVAULT("keyVaultDnsSuffix"), - /** Azure Data Lake Store DNS suffix. */ - DATA_LAKE_STORE("azureDataLakeStoreFileSystemEndpointSuffix"), - /** Azure Data Lake Analytics DNS suffix. */ - DATA_LAKE_ANALYTICS("azureDataLakeAnalyticsCatalogAndJobEndpointSuffix"), - /** Azure Log Analytics endpoint. */ - LOG_ANALYTICS("azureLogAnalyticsResourceId"), - /** Azure Application Insights. */ - APPLICATION_INSIGHTS("azureApplicationInsightsResourceId"); - - private String field; - - Endpoint(String value) { - this.field = value; - } - - @Override - public String identifier() { - return field; - } - - @Override - public String toString() { - return field; - } - } - - /** - * Get the endpoint URL for the current environment. - * - * @param endpoint the endpoint - * @return the URL - */ - public String url(Environment.Endpoint endpoint) { - return endpoints.get(endpoint.identifier()); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureResponseBuilder.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureResponseBuilder.java deleted file mode 100644 index 0fd1d3399..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureResponseBuilder.java +++ /dev/null @@ -1,113 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.google.common.reflect.TypeToken; -import com.microsoft.bot.rest.RestException; -import com.microsoft.bot.rest.ServiceResponse; -import com.microsoft.bot.rest.ServiceResponseBuilder; -import com.microsoft.bot.rest.ServiceResponseWithHeaders; -import com.microsoft.bot.rest.protocol.ResponseBuilder; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import okhttp3.ResponseBody; -import retrofit2.Response; - -import java.io.IOException; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Type; - -/** - * The builder for building a {@link ServiceResponse} customized for Azure. - * - * @param the return type from caller. - * @param the exception to throw in case of error. - */ -public final class AzureResponseBuilder implements ResponseBuilder { - /** The base response builder for handling most scenarios. */ - private ServiceResponseBuilder baseBuilder; - - /** - * Create a ServiceResponseBuilder instance. - * - * @param serializer the serialization utils to use for deserialization operations - */ - private AzureResponseBuilder(SerializerAdapter serializer) { - baseBuilder = new ServiceResponseBuilder.Factory().newInstance(serializer); - } - - @Override - public ResponseBuilder register(int statusCode, Type type) { - baseBuilder.register(statusCode, type); - return this; - } - - @Override - public ResponseBuilder registerError(Class type) { - baseBuilder.registerError(type); - return this; - } - - @Override - public ServiceResponse build(Response response) throws IOException { - return baseBuilder.build(response); - } - - @SuppressWarnings("unchecked") - @Override - public ServiceResponse buildEmpty(Response response) throws E, IOException { - int statusCode = response.code(); - if (baseBuilder.isSuccessful(statusCode)) { - if (new TypeToken(getClass()) { }.getRawType().isAssignableFrom(Boolean.class)) { - return new ServiceResponse(response).withBody((T) (Object) (statusCode / 100 == 2)); - } else { - return new ServiceResponse<>(response); - } - } else { - try { - throw baseBuilder.exceptionType().getConstructor(String.class, Response.class) - .newInstance("Status code " + statusCode, response); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new IOException("Invalid status code " + statusCode + ", but an instance of " + baseBuilder.exceptionType().getCanonicalName() - + " cannot be created.", e); - } - } - } - - @Override - public ServiceResponseWithHeaders buildWithHeaders(Response response, Class headerType) throws IOException { - return baseBuilder.buildWithHeaders(response, headerType); - } - - @Override - public ServiceResponseWithHeaders buildEmptyWithHeaders(Response response, Class headerType) throws IOException { - ServiceResponse bodyResponse = buildEmpty(response); - ServiceResponseWithHeaders baseResponse = baseBuilder.buildEmptyWithHeaders(response, headerType); - ServiceResponseWithHeaders serviceResponse = new ServiceResponseWithHeaders<>(baseResponse.headers(), bodyResponse.headResponse()); - serviceResponse.withBody(bodyResponse.body()); - return serviceResponse; - } - - /** - * Specifies whether to throw on 404 responses from a GET call. - * @param throwOnGet404 true if to throw; false to simply return null. Default is false. - * @return the response builder itself - */ - public AzureResponseBuilder withThrowOnGet404(boolean throwOnGet404) { - baseBuilder.withThrowOnGet404(throwOnGet404); - return this; - } - - /** - * A factory to create an Azure response builder. - */ - public static final class Factory implements ResponseBuilder.Factory { - @Override - public AzureResponseBuilder newInstance(final SerializerAdapter serializerAdapter) { - return new AzureResponseBuilder(serializerAdapter); - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceClient.java deleted file mode 100644 index d188000ac..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceClient.java +++ /dev/null @@ -1,92 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.google.common.hash.Hashing; -import com.microsoft.bot.azure.serializer.AzureJacksonAdapter; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.ServiceClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; - -import java.net.NetworkInterface; -import java.util.Enumeration; - -/** - * ServiceClient is the abstraction for accessing REST operations and their payload data types. - */ -public abstract class AzureServiceClient extends ServiceClient { - protected AzureServiceClient(String baseUrl, ServiceClientCredentials credentials) { - this(baseUrl, credentials, new OkHttpClient.Builder(), new Retrofit.Builder()); - } - - /** - * Initializes a new instance of the ServiceClient class. - * - * @param baseUrl the service base uri - * @param credentials the credentials - * @param clientBuilder the http client builder - * @param restBuilder the retrofit rest client builder - */ - protected AzureServiceClient(String baseUrl, ServiceClientCredentials credentials, OkHttpClient.Builder clientBuilder, Retrofit.Builder restBuilder) { - this(new RestClient.Builder(clientBuilder, restBuilder) - .withBaseUrl(baseUrl) - .withCredentials(credentials) - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()) - .build()); - } - - /** - * Initializes a new instance of the ServiceClient class. - * - * @param restClient the REST client - */ - protected AzureServiceClient(RestClient restClient) { - super(restClient); - } - - /** - * The default User-Agent header. Override this method to override the user agent. - * - * @return the user agent string. - */ - public String userAgent() { - return String.format("Azure-SDK-For-Java/%s OS:%s MacAddressHash:%s Java:%s", - getClass().getPackage().getImplementationVersion(), - OS, - MAC_ADDRESS_HASH, - JAVA_VERSION); - } - - private static final String MAC_ADDRESS_HASH; - private static final String OS; - private static final String JAVA_VERSION; - - static { - OS = System.getProperty("os.name") + "/" + System.getProperty("os.version"); - String macAddress = "Unknown"; - try { - Enumeration networks = NetworkInterface.getNetworkInterfaces(); - while (networks.hasMoreElements()) { - NetworkInterface network = networks.nextElement(); - byte[] mac = network.getHardwareAddress(); - - if (mac != null) { - macAddress = Hashing.sha256().hashBytes(mac).toString(); - break; - } - } - } catch (Throwable ignore) { - // It's okay ignore mac address hash telemetry - } - MAC_ADDRESS_HASH = macAddress; - String version = System.getProperty("java.version"); - JAVA_VERSION = version != null ? version : "Unknown"; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceFuture.java.dep b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceFuture.java.dep deleted file mode 100644 index 9cd9f80e8..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/AzureServiceFuture.java.dep +++ /dev/null @@ -1,124 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.azure; - -import com.microsoft.rest.ServiceFuture; -import com.microsoft.rest.ServiceResponse; -import com.microsoft.rest.ServiceResponseWithHeaders; -import rx.Observable; -import rx.Subscriber; -import rx.functions.Func1; - -import java.util.List; - -/** - * An instance of this class provides access to the underlying REST call invocation. - * This class wraps around the Retrofit Call object and allows updates to it in the - * progress of a long running operation or a paging operation. - * - * @param the type of the returning object - */ -public final class AzureServiceFuture extends ServiceFuture { - private AzureServiceFuture() { - } - - /** - * Creates a ServiceCall from a paging operation. - * - * @param first the observable to the first page - * @param next the observable to poll subsequent pages - * @param callback the client-side callback - * @param the element type - * @return the future based ServiceCall - */ - public static ServiceFuture> fromPageResponse(Observable>> first, final Func1>>> next, final ListOperationCallback callback) { - final AzureServiceFuture> serviceCall = new AzureServiceFuture<>(); - final PagingSubscriber subscriber = new PagingSubscriber<>(serviceCall, next, callback); - serviceCall.setSubscription(first - .single() - .subscribe(subscriber)); - return serviceCall; - } - - /** - * Creates a ServiceCall from a paging operation that returns a header response. - * - * @param first the observable to the first page - * @param next the observable to poll subsequent pages - * @param callback the client-side callback - * @param the element type - * @param the header object type - * @return the future based ServiceCall - */ - public static ServiceFuture> fromHeaderPageResponse(Observable, V>> first, final Func1, V>>> next, final ListOperationCallback callback) { - final AzureServiceFuture> serviceCall = new AzureServiceFuture<>(); - final PagingSubscriber subscriber = new PagingSubscriber<>(serviceCall, new Func1>>>() { - @Override - public Observable>> call(String s) { - return next.call(s) - .map(new Func1, V>, ServiceResponse>>() { - @Override - public ServiceResponse> call(ServiceResponseWithHeaders, V> pageVServiceResponseWithHeaders) { - return pageVServiceResponseWithHeaders; - } - }); - } - }, callback); - serviceCall.setSubscription(first - .single() - .subscribe(subscriber)); - return serviceCall; - } - - /** - * The subscriber that handles user callback and automatically subscribes to the next page. - * - * @param the element type - */ - private static final class PagingSubscriber extends Subscriber>> { - private AzureServiceFuture> serviceCall; - private Func1>>> next; - private ListOperationCallback callback; - private ServiceResponse> lastResponse; - - PagingSubscriber(final AzureServiceFuture> serviceCall, final Func1>>> next, final ListOperationCallback callback) { - this.serviceCall = serviceCall; - this.next = next; - this.callback = callback; - } - - @Override - public void onCompleted() { - // do nothing - } - - @Override - public void onError(Throwable e) { - serviceCall.setException(e); - if (callback != null) { - callback.failure(e); - } - } - - @Override - public void onNext(ServiceResponse> serviceResponse) { - lastResponse = serviceResponse; - ListOperationCallback.PagingBehavior behavior = ListOperationCallback.PagingBehavior.CONTINUE; - if (callback != null) { - behavior = callback.progress(serviceResponse.body().items()); - if (behavior == ListOperationCallback.PagingBehavior.STOP || serviceResponse.body().nextPageLink() == null) { - callback.success(); - } - } - if (behavior == ListOperationCallback.PagingBehavior.STOP || serviceResponse.body().nextPageLink() == null) { - serviceCall.set(lastResponse.body().items()); - } else { - serviceCall.setSubscription(next.call(serviceResponse.body().nextPageLink()).single().subscribe(this)); - } - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudError.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudError.java deleted file mode 100644 index 627be615d..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudError.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import java.util.ArrayList; -import java.util.List; - -import com.fasterxml.jackson.databind.annotation.JsonDeserialize; -import com.microsoft.bot.azure.serializer.TypedErrorInfoDeserializer; - -/** - * An instance of this class provides additional information about an http error response. - */ -public final class CloudError { - /** - * The error code parsed from the body of the http error response. - */ - private String code; - - /** - * The error message parsed from the body of the http error response. - */ - private String message; - - /** - * The target of the error. - */ - private String target; - - /** - * Details for the error. - */ - private List details; - - /** - * Additional error information. - */ - @JsonDeserialize(contentUsing = TypedErrorInfoDeserializer.class) - private List additionalInfo; - - /** - * Initializes a new instance of CloudError. - */ - public CloudError() { - this.details = new ArrayList(); - } - - /** - * @return the error code parsed from the body of the http error response - */ - public String code() { - return code; - } - - /** - * Sets the error code parsed from the body of the http error response. - * - * @param code the error code - * @return the CloudError object itself - */ - public CloudError withCode(String code) { - this.code = code; - return this; - } - - /** - * @return the error message - */ - public String message() { - return message; - } - - /** - * Sets the error message parsed from the body of the http error response. - * - * @param message the error message - * @return the CloudError object itself - */ - public CloudError withMessage(String message) { - this.message = message; - return this; - } - - /** - * @return the target of the error - */ - public String target() { - return target; - } - - /** - * Sets the target of the error. - * - * @param target the target of the error - * @return the CloudError object itself - */ - public CloudError withTarget(String target) { - this.target = target; - return this; - } - - /** - * @return the details for the error - */ - public List details() { - return details; - } - - /** - * @return the additional error information - */ - public List additionalInfo() { - return additionalInfo; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudException.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudException.java deleted file mode 100644 index 8f869161b..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/CloudException.java +++ /dev/null @@ -1,51 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.rest.RestException; -import okhttp3.ResponseBody; -import retrofit2.Response; - -/** - * Exception thrown for an invalid response with custom error information. - */ -public final class CloudException extends RestException { - /** - * Initializes a new instance of the CloudException class. - * - * @param message the exception message or the response content if a message is not available - * @param response the HTTP response - */ - public CloudException(final String message, final Response response) { - super(message, response); - } - - /** - * Initializes a new instance of the CloudException class. - * - * @param message the exception message or the response content if a message is not available - * @param response the HTTP response - * @param body the deserialized response body - */ - public CloudException(final String message, Response response, CloudError body) { - super(message, response, body); - } - - @Override - public CloudError body() { - return (CloudError) super.body(); - } - - @Override - public String toString() { - String message = super.toString(); - if (body() != null && body().message() != null) { - message = message + ": " + body().message(); - } - return message; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ListOperationCallback.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ListOperationCallback.java deleted file mode 100644 index 79a81855c..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ListOperationCallback.java +++ /dev/null @@ -1,103 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.rest.ServiceCallback; - -import java.util.List; - -/** - * The callback used for client side asynchronous list operations. - * - * @param the item type - */ -public abstract class ListOperationCallback implements ServiceCallback> { - /** - * A list result that stores the accumulated resources loaded from server. - */ - private List result; - - /** - * Number of loaded pages. - */ - private int pageCount; - - /** - * Creates an instance of ListOperationCallback. - */ - public ListOperationCallback() { - this.pageCount = 0; - } - - /** - * Override this method to handle progressive results. - * The user is responsible for returning a {@link PagingBehavior} Enum to indicate - * whether the client should continue loading or stop. - * - * @param partial the list of resources from the current request. - * @return CONTINUE if you want to go on loading, STOP otherwise. - * - */ - public abstract PagingBehavior progress(List partial); - - /** - * Get the list result that stores the accumulated resources loaded from server. - * - * @return the list of resources. - */ - public List get() { - return result; - } - - /** - * This method is called by the client to load the most recent list of resources. - * This method should only be called by the service client. - * - * @param result the most recent list of resources. - */ - public void load(List result) { - ++pageCount; - if (this.result == null || this.result.isEmpty()) { - this.result = result; - } else { - this.result.addAll(result); - } - } - - @Override - public void success(List result) { - success(); - } - - /** - * Override this method to handle successful REST call results. - */ - public abstract void success(); - - /** - * Get the number of loaded pages. - * - * @return the number of pages. - */ - public int pageCount() { - return pageCount; - } - - /** - * An enum to indicate whether the client should continue loading or stop. - */ - public enum PagingBehavior { - /** - * Indicates that the client should continue loading. - */ - CONTINUE, - /** - * Indicates that the client should stop loading. - */ - STOP - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningFinalState.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningFinalState.java deleted file mode 100644 index fc1453d5a..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningFinalState.java +++ /dev/null @@ -1,29 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -/** - * Describes how to retrieve the final state of a long running operation. - */ -public enum LongRunningFinalState { - /** - * Indicate that no specific action required to retrieve the final state. - */ - DEFAULT, - /** - * Indicate that use azure async operation uri to retrieve the final state. - */ - AZURE_ASYNC_OPERATION, - /** - * Indicate that use location uri to retrieve the final state. - */ - LOCATION, - /** - * Indicate that use original uri to retrieve the final state. - */ - ORIGINAL_URI -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningOperationOptions.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningOperationOptions.java deleted file mode 100644 index 4e18ebdf9..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/LongRunningOperationOptions.java +++ /dev/null @@ -1,40 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -/** - * Type representing LRO meta-data present in the x-ms-long-running-operation-options autorest extension. - */ -public final class LongRunningOperationOptions { - /** - * Default instance of this type. - */ - public static final LongRunningOperationOptions DEFAULT = new LongRunningOperationOptions().withFinalStateVia(LongRunningFinalState.DEFAULT); - - /** - * Describes how to retrieve the final state of the LRO. - */ - private LongRunningFinalState finalStateVia; - - /** - * @return indicates how to retrieve the final state of LRO. - */ - public LongRunningFinalState finalStateVia() { - return this.finalStateVia; - } - - /** - * Sets LongRunningFinalState value. - * - * @param finalStateVia indicates how to retrieve the final state of LRO. - * @return LongRunningOperationOptions - */ - public LongRunningOperationOptions withFinalStateVia(LongRunningFinalState finalStateVia) { - this.finalStateVia = finalStateVia; - return this; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Page.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Page.java deleted file mode 100644 index 8849825c9..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Page.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import java.util.List; - -/** - * Defines a page interface in Azure responses. - * - * @param the element type. - */ -public interface Page { - /** - * Gets the link to the next page. - * - * @return the link. - */ - String nextPageLink(); - - /** - * Gets the list of items. - * - * @return the list of items. - */ - List items(); -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PagedList.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PagedList.java deleted file mode 100644 index b2eef2e2a..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PagedList.java +++ /dev/null @@ -1,413 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.rest.RestException; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.ConcurrentModificationException; -import java.util.Iterator; -import java.util.List; -import java.util.ListIterator; -import java.util.NoSuchElementException; - -/** - * Defines a list response from a paging operation. The pages are - * lazy initialized when an instance of this class is iterated. - * - * @param the element type. - */ -public abstract class PagedList implements List { - /** The actual items in the list. */ - private List items; - /** Stores the latest page fetched. */ - private Page currentPage; - /** Cached page right after the current one. */ - private Page cachedPage; - - /** - * Creates an instance of Pagedlist. - */ - public PagedList() { - items = new ArrayList<>(); - } - - /** - * Creates an instance of PagedList from a {@link Page} response. - * - * @param page the {@link Page} object. - */ - public PagedList(Page page) { - this(); - if (page == null) { - return; - } - List retrievedItems = page.items(); - if (retrievedItems != null) { - items.addAll(retrievedItems); - } - currentPage = page; - cachePage(page.nextPageLink()); - } - - private void cachePage(String nextPageLink) { - try { - while (nextPageLink != null && nextPageLink != "") { - cachedPage = nextPage(nextPageLink); - if (cachedPage == null) { - break; - } - nextPageLink = cachedPage.nextPageLink(); - if (hasNextPage()) { - // a legit, non-empty page has been fetched, otherwise keep fetching - break; - } - } - } catch (IOException ex) { - throw new RuntimeException(ex); - } - } - - /** - * Override this method to load the next page of items from a next page link. - * - * @param nextPageLink the link to get the next page of items. - * @return the {@link Page} object storing a page of items and a link to the next page. - * @throws RestException thrown if an error is raised from Azure. - * @throws IOException thrown if there's any failure in deserialization. - */ - public abstract Page nextPage(String nextPageLink) throws RestException, IOException; - - /** - * If there are more pages available. - * - * @return true if there are more pages to load. False otherwise. - */ - public boolean hasNextPage() { - return this.cachedPage != null && this.cachedPage.items() != null && !this.cachedPage.items().isEmpty(); - } - - /** - * Loads a page from next page link. - * The exceptions are wrapped into Java Runtime exceptions. - */ - public void loadNextPage() { - this.currentPage = cachedPage; - cachedPage = null; - this.items.addAll(currentPage.items()); - cachePage(currentPage.nextPageLink()); - } - - /** - * Keep loading the next page from the next page link until all items are loaded. - */ - public void loadAll() { - while (hasNextPage()) { - loadNextPage(); - } - } - - /** - * Gets the latest page fetched. - * - * @return the latest page. - */ - public Page currentPage() { - return currentPage; - } - - /** - * Sets the current page. - * - * @param currentPage the current page. - */ - protected void setCurrentPage(Page currentPage) { - this.currentPage = currentPage; - List retrievedItems = currentPage.items(); - if (retrievedItems != null) { - items.addAll(retrievedItems); - } - cachePage(currentPage.nextPageLink()); - } - - /** - * The implementation of {@link ListIterator} for PagedList. - */ - private class ListItr implements ListIterator { - /** - * index of next element to return. - */ - private int nextIndex; - /** - * index of last element returned; -1 if no such action happened. - */ - private int lastRetIndex = -1; - - /** - * Creates an instance of the ListIterator. - * - * @param index the position in the list to start. - */ - ListItr(int index) { - this.nextIndex = index; - } - - @Override - public boolean hasNext() { - return this.nextIndex != items.size() || hasNextPage(); - } - - @Override - public E next() { - if (this.nextIndex >= items.size()) { - if (!hasNextPage()) { - throw new NoSuchElementException(); - } else { - loadNextPage(); - } - // Recurse until we load a page with non-zero items. - return next(); - } else { - try { - E nextItem = items.get(this.nextIndex); - this.lastRetIndex = this.nextIndex; - this.nextIndex = this.nextIndex + 1; - return nextItem; - } catch (IndexOutOfBoundsException ex) { - // The nextIndex got invalid means a different instance of iterator - // removed item from this index. - throw new ConcurrentModificationException(); - } - } - } - - @Override - public void remove() { - if (this.lastRetIndex < 0) { - throw new IllegalStateException(); - } else { - try { - items.remove(this.lastRetIndex); - this.nextIndex = this.lastRetIndex; - this.lastRetIndex = -1; - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - } - - @Override - public boolean hasPrevious() { - return this.nextIndex != 0; - } - - @Override - public E previous() { - int i = this.nextIndex - 1; - if (i < 0) { - throw new NoSuchElementException(); - } else if (i >= items.size()) { - throw new ConcurrentModificationException(); - } else { - try { - this.nextIndex = i; - this.lastRetIndex = i; - return items.get(this.lastRetIndex); - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - } - - @Override - public int nextIndex() { - return this.nextIndex; - } - - @Override - public int previousIndex() { - return this.nextIndex - 1; - } - - @Override - public void set(E e) { - if (this.lastRetIndex < 0) { - throw new IllegalStateException(); - } else { - try { - items.set(this.lastRetIndex, e); - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - } - - @Override - public void add(E e) { - try { - items.add(this.nextIndex, e); - this.nextIndex = this.nextIndex + 1; - this.lastRetIndex = -1; - } catch (IndexOutOfBoundsException ex) { - throw new ConcurrentModificationException(); - } - } - } - - @Override - public int size() { - loadAll(); - return items.size(); - } - - @Override - public boolean isEmpty() { - return items.isEmpty() && !hasNextPage(); - } - - @Override - public boolean contains(Object o) { - return indexOf(o) >= 0; - } - - @Override - public Iterator iterator() { - return new ListItr(0); - } - - @Override - public Object[] toArray() { - loadAll(); - return items.toArray(); - } - - @Override - public T[] toArray(T[] a) { - loadAll(); - return items.toArray(a); - } - - @Override - public boolean add(E e) { - return items.add(e); - } - - @Override - public boolean remove(Object o) { - return items.remove(o); - } - - @Override - public boolean containsAll(Collection c) { - for (Object e : c) { - if (!contains(e)) { - return false; - } - } - return true; - } - - @Override - public boolean addAll(Collection c) { - return items.addAll(c); - } - - @Override - public boolean addAll(int index, Collection c) { - return items.addAll(index, c); - } - - @Override - public boolean removeAll(Collection c) { - return items.removeAll(c); - } - - @Override - public boolean retainAll(Collection c) { - return items.retainAll(c); - } - - @Override - public void clear() { - items.clear(); - } - - @Override - public E get(int index) { - while (index >= items.size() && hasNextPage()) { - loadNextPage(); - } - return items.get(index); - } - - @Override - public E set(int index, E element) { - return items.set(index, element); - } - - @Override - public void add(int index, E element) { - items.add(index, element); - } - - @Override - public E remove(int index) { - return items.remove(index); - } - - @Override - public int indexOf(Object o) { - int index = 0; - if (o == null) { - for (E item : this) { - if (item == null) { - return index; - } - ++index; - } - } else { - for (E item : this) { - if (item == o) { - return index; - } - ++index; - } - } - return -1; - } - - @Override - public int lastIndexOf(Object o) { - loadAll(); - return items.lastIndexOf(o); - } - - @Override - public ListIterator listIterator() { - return new ListItr(0); - } - - @Override - public ListIterator listIterator(int index) { - while (index >= items.size() && hasNextPage()) { - loadNextPage(); - } - return new ListItr(index); - } - - @Override - public List subList(int fromIndex, int toIndex) { - while ((fromIndex >= items.size() - || toIndex >= items.size()) - && hasNextPage()) { - loadNextPage(); - } - return items.subList(fromIndex, toIndex); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolation.java deleted file mode 100644 index 97cd10a04..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolation.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import java.io.IOException; - -import com.fasterxml.jackson.core.JsonParseException; -import com.fasterxml.jackson.databind.JsonMappingException; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * An instance of this class provides Azure policy violation information. - */ -public class PolicyViolation extends TypedErrorInfo { - /** - * Policy violation error details. - */ - private PolicyViolationErrorInfo policyErrorInfo; - - /** - * Initializes a new instance of PolicyViolation. - * @param type the error type - * @param policyErrorInfo the error details - * @throws JsonParseException if the policyErrorInfo has invalid content. - * @throws JsonMappingException if the policyErrorInfo's JSON does not match the expected schema. - * @throws IOException if an IO error occurs. - */ - public PolicyViolation(String type, ObjectNode policyErrorInfo) throws JsonParseException, JsonMappingException, IOException { - super(type, policyErrorInfo); - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); - this.policyErrorInfo = objectMapper.readValue(policyErrorInfo.toString(), PolicyViolationErrorInfo.class); - } - - /** - * @return the policy violation error details. - */ - public PolicyViolationErrorInfo policyErrorInfo() { - return policyErrorInfo; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolationErrorInfo.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolationErrorInfo.java deleted file mode 100644 index 3b102c780..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PolicyViolationErrorInfo.java +++ /dev/null @@ -1,186 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import java.util.HashMap; - -import com.fasterxml.jackson.databind.JsonNode; - -/** - * An instance of this class provides Azure policy violation information. - */ -public class PolicyViolationErrorInfo { - /** - * The policy definition id. - */ - private String policyDefinitionId; - - /** - * The policy set definition id. - */ - private String policySetDefinitionId; - - /** - * The policy definition instance id inside a policy set. - */ - private String policyDefinitionReferenceId; - - /** - * The policy set definition name. - */ - private String policySetDefinitionName; - - /** - * The policy definition name. - */ - private String policyDefinitionName; - - /** - * The policy definition action. - */ - private String policyDefinitionEffect; - - /** - * The policy assignment id. - */ - private String policyAssignmentId; - - /** - * The policy assignment name. - */ - private String policyAssignmentName; - - /** - * The policy assignment display name. - */ - private String policyAssignmentDisplayName; - - /** - * The policy assignment scope. - */ - private String policyAssignmentScope; - - /** - * The policy assignment parameters. - */ - private HashMap policyAssignmentParameters; - - /** - * The policy definition display name. - */ - private String policyDefinitionDisplayName; - - /** - * The policy set definition display name. - */ - private String policySetDefinitionDisplayName; - - /** - * @return the policy definition id. - */ - public String getPolicyDefinitionId() { - return policyDefinitionId; - } - - /** - * @return the policy set definition id. - */ - public String getPolicySetDefinitionId() { - return policySetDefinitionId; - } - - /** - * @return the policy definition instance id inside a policy set. - */ - public String getPolicyDefinitionReferenceId() { - return policyDefinitionReferenceId; - } - - /** - * @return the policy set definition name. - */ - public String getPolicySetDefinitionName() { - return policySetDefinitionName; - } - - /** - * @return the policy definition name. - */ - public String getPolicyDefinitionName() { - return policyDefinitionName; - } - - /** - * @return the policy definition action. - */ - public String getPolicyDefinitionEffect() { - return policyDefinitionEffect; - } - - /** - * @return the policy assignment id. - */ - public String getPolicyAssignmentId() { - return policyAssignmentId; - } - - /** - * @return the policy assignment name. - */ - public String getPolicyAssignmentName() { - return policyAssignmentName; - } - - /** - * @return the policy assignment display name. - */ - public String getPolicyAssignmentDisplayName() { - return policyAssignmentDisplayName; - } - - /** - * @return the policy assignment scope. - */ - public String getPolicyAssignmentScope() { - return policyAssignmentScope; - } - - /** - * @return the policy assignment parameters. - */ - public HashMap getPolicyAssignmentParameters() { - return policyAssignmentParameters; - } - - /** - * @return the policy definition display name. - */ - public String getPolicyDefinitionDisplayName() { - return policyDefinitionDisplayName; - } - - /** - * @return the policy set definition display name. - */ - public String getPolicySetDefinitionDisplayName() { - return policySetDefinitionDisplayName; - } - - /** - * An instance of this class provides policy parameter value. - */ - public static class PolicyParameter { - private JsonNode value; - - /** - * @return the parameter value. - */ - public JsonNode getValue() { - return value; - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PollingState.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PollingState.java deleted file mode 100644 index 0c5a1d5fd..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/PollingState.java +++ /dev/null @@ -1,590 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.fasterxml.jackson.annotation.JsonAutoDetect; -import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.datatype.jdk8.Jdk8Module; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import com.microsoft.bot.rest.serializer.Base64UrlSerializer; -import com.microsoft.bot.rest.serializer.ByteArraySerializer; -import com.microsoft.bot.rest.serializer.HeadersSerializer; -import okhttp3.ResponseBody; -import retrofit2.Response; - -import java.io.IOException; -import java.lang.reflect.Type; - -/** - * An instance of this class defines polling status of a long running operation. - * - * @param the type of the resource the operation returns. - */ -public class PollingState { - /** The HTTP method used to initiate the long running operation. **/ - private String initialHttpMethod; - /** The polling status. */ - private String status; - /** The HTTP status code. */ - private int statusCode = DEFAULT_STATUS_CODE; - /** The link in 'Azure-AsyncOperation' header. */ - private String azureAsyncOperationHeaderLink; - /** The link in 'Location' Header. */ - private String locationHeaderLink; - /** The default timeout interval between two polling operations. */ - private int defaultRetryTimeout; - /** The timeout interval between two polling operation. **/ - private int retryTimeout; - /** The resource uri on which PUT or PATCH operation is applied. **/ - private String putOrPatchResourceUri; - /** The logging context. **/ - private String loggingContext; - /** indicate how to retrieve the final state of LRO. **/ - private LongRunningFinalState finalStateVia; - - // Non-serializable properties - // - /** The logging context header name. **/ - @JsonIgnore - private static final String LOGGING_HEADER = "x-ms-logging-context"; - /** The statusCode that is used when no statusCode has been set. */ - @JsonIgnore - private static final int DEFAULT_STATUS_CODE = 0; - /** The Retrofit response object. */ - @JsonIgnore - private Response response; - /** The response resource object. */ - @JsonIgnore - private T resource; - /** The type of the response resource object. */ - @JsonIgnore - private Type resourceType; - /** The error during the polling operations. */ - @JsonIgnore - private CloudError error; - /** The adapter for a custom serializer. */ - @JsonIgnore - private SerializerAdapter serializerAdapter; - - /** - * Default constructor. - */ - PollingState() { - } - - /** - * Creates a polling state. - * - * @param response the response from Retrofit REST call that initiate the long running operation. - * @param lroOptions long running operation options. - * @param defaultRetryTimeout the long running operation retry timeout. - * @param resourceType the type of the resource the long running operation returns - * @param serializerAdapter the adapter for the Jackson object mapper - * @param the result type - * @return the polling state - * @throws IOException thrown by deserialization - */ - public static PollingState create(Response response, LongRunningOperationOptions lroOptions, int defaultRetryTimeout, Type resourceType, SerializerAdapter serializerAdapter) throws IOException { - PollingState pollingState = new PollingState<>(); - pollingState.initialHttpMethod = response.raw().request().method(); - pollingState.defaultRetryTimeout = defaultRetryTimeout; - pollingState.withResponse(response); - pollingState.resourceType = resourceType; - pollingState.serializerAdapter = serializerAdapter; - pollingState.loggingContext = response.raw().request().header(LOGGING_HEADER); - pollingState.finalStateVia = lroOptions.finalStateVia(); - - String responseContent = null; - PollingResource resource = null; - if (response.body() != null) { - responseContent = response.body().string(); - response.body().close(); - } - if (responseContent != null && !responseContent.isEmpty()) { - pollingState.resource = serializerAdapter.deserialize(responseContent, resourceType); - resource = serializerAdapter.deserialize(responseContent, PollingResource.class); - } - final int statusCode = pollingState.response.code(); - if (resource != null && resource.properties != null - && resource.properties.provisioningState != null) { - pollingState.withStatus(resource.properties.provisioningState, statusCode); - } else { - switch (statusCode) { - case 202: - pollingState.withStatus(AzureAsyncOperation.IN_PROGRESS_STATUS, statusCode); - break; - case 204: - case 201: - case 200: - pollingState.withStatus(AzureAsyncOperation.SUCCESS_STATUS, statusCode); - break; - default: - pollingState.withStatus(AzureAsyncOperation.FAILED_STATUS, statusCode); - } - } - return pollingState; - } - - /** - * Creates PollingState from the json string. - * - * @param serializedPollingState polling state as json string - * @param the result that the poll operation produces - * @return the polling state - */ - public static PollingState createFromJSONString(String serializedPollingState) { - ObjectMapper mapper = initMapper(new ObjectMapper()); - PollingState pollingState; - try { - pollingState = mapper.readValue(serializedPollingState, PollingState.class); - } catch (IOException exception) { - throw new RuntimeException(exception); - } - return pollingState; - } - - /** - * Creates PollingState from another polling state. - * - * @param other other polling state - * @param result the final result of the LRO - * @param the result that the poll operation produces - * @return the polling state - */ - public static PollingState createFromPollingState(PollingState other, ResultT result) { - PollingState pollingState = new PollingState<>(); - pollingState.resource = result; - pollingState.initialHttpMethod = other.initialHttpMethod(); - pollingState.status = other.status(); - pollingState.statusCode = other.statusCode(); - pollingState.azureAsyncOperationHeaderLink = other.azureAsyncOperationHeaderLink(); - pollingState.locationHeaderLink = other.locationHeaderLink(); - pollingState.putOrPatchResourceUri = other.putOrPatchResourceUri(); - pollingState.defaultRetryTimeout = other.defaultRetryTimeout; - pollingState.retryTimeout = other.retryTimeout; - pollingState.loggingContext = other.loggingContext; - pollingState.finalStateVia = other.finalStateVia; - return pollingState; - } - - /** - * @return the polling state in json string format - */ - public String serialize() { - ObjectMapper mapper = initMapper(new ObjectMapper()); - try { - return mapper.writeValueAsString(this); - } catch (JsonProcessingException exception) { - throw new RuntimeException(exception); - } - } - - /** - * Gets the resource. - * - * @return the resource. - */ - public T resource() { - return resource; - } - - /** - * Gets the operation response. - * - * @return the operation response. - */ - public Response response() { - return this.response; - } - - /** - * Gets the polling status. - * - * @return the polling status. - */ - public String status() { - return status; - } - - /** - * Gets the polling HTTP status code. - * - * @return the polling HTTP status code. - */ - public int statusCode() { - return statusCode; - } - - /** - * Gets the value captured from Azure-AsyncOperation header. - * - * @return the link in the header. - */ - public String azureAsyncOperationHeaderLink() { - if (azureAsyncOperationHeaderLink != null && !azureAsyncOperationHeaderLink.isEmpty()) { - return azureAsyncOperationHeaderLink; - } - return null; - } - - /** - * Gets the value captured from Location header. - * - * @return the link in the header. - */ - public String locationHeaderLink() { - if (locationHeaderLink != null && !locationHeaderLink.isEmpty()) { - return locationHeaderLink; - } - return null; - } - - /** - * Updates the polling state from a PUT or PATCH operation. - * - * @param response the response from Retrofit REST call - * @throws CloudException thrown if the response is invalid - * @throws IOException thrown by deserialization - */ - void updateFromResponseOnPutPatch(Response response) throws CloudException, IOException { - String responseContent = null; - if (response.body() != null) { - responseContent = response.body().string(); - response.body().close(); - } - - if (responseContent == null || responseContent.isEmpty()) { - throw new CloudException("polling response does not contain a valid body", response); - } - - PollingResource resource = serializerAdapter.deserialize(responseContent, PollingResource.class); - final int statusCode = response.code(); - if (resource != null && resource.properties != null && resource.properties.provisioningState != null) { - this.withStatus(resource.properties.provisioningState, statusCode); - } else { - this.withStatus(AzureAsyncOperation.SUCCESS_STATUS, statusCode); - } - - CloudError error = new CloudError(); - this.withErrorBody(error); - error.withCode(this.status()); - error.withMessage("Long running operation failed"); - this.withResponse(response); - this.withResource(serializerAdapter.deserialize(responseContent, resourceType)); - } - - /** - * Updates the polling state from a DELETE or POST operation. - * - * @param response the response from Retrofit REST call - * @throws IOException thrown by deserialization - */ - - void updateFromResponseOnDeletePost(Response response) throws IOException { - this.withResponse(response); - String responseContent = null; - if (response.body() != null) { - responseContent = response.body().string(); - response.body().close(); - } - this.withResource(serializerAdapter.deserialize(responseContent, resourceType)); - withStatus(AzureAsyncOperation.SUCCESS_STATUS, response.code()); - } - - /** - * Gets long running operation delay in milliseconds. - * - * @return the delay in milliseconds. - */ - int delayInMilliseconds() { - if (this.retryTimeout >= 0) { - return this.retryTimeout; - } - if (this.defaultRetryTimeout >= 0) { - return this.defaultRetryTimeout * 1000; - } - return AzureAsyncOperation.DEFAULT_DELAY * 1000; - } - - /** - * @return the uri of the resource on which the LRO PUT or PATCH applied. - */ - String putOrPatchResourceUri() { - return this.putOrPatchResourceUri; - } - - /** - * @return true if the status this state hold represents terminal status. - */ - boolean isStatusTerminal() { - for (String terminalStatus : AzureAsyncOperation.terminalStatuses()) { - if (terminalStatus.equalsIgnoreCase(this.status())) { - return true; - } - } - return false; - } - - /** - * @return true if the status this state hold is represents failed status. - */ - boolean isStatusFailed() { - for (String failedStatus : AzureAsyncOperation.failedStatuses()) { - if (failedStatus.equalsIgnoreCase(this.status())) { - return true; - } - } - return false; - } - - /** - * @return true if the status this state represents is succeeded status. - */ - boolean isStatusSucceeded() { - return AzureAsyncOperation.SUCCESS_STATUS.equalsIgnoreCase(this.status()); - } - - boolean resourcePending() { - boolean resourcePending = statusCode() != 204 - && isStatusSucceeded() - && resource() == null - && resourceType() != Void.class - && locationHeaderLink() != null; - if (resourcePending) { - // Keep current behaviour for backward-compact - return true; - } else { - return this.finalStateVia() == LongRunningFinalState.LOCATION; - } - } - - /** - * Gets the logging context. - * - * @return the logging context - */ - String loggingContext() { - return loggingContext; - } - - /** - * Sets the polling status. - * - * @param status the polling status. - * @throws IllegalArgumentException thrown if status is null. - */ - PollingState withStatus(String status) throws IllegalArgumentException { - return withStatus(status, DEFAULT_STATUS_CODE); - } - - /** - * Sets the polling status. - * - * @param status the polling status. - * @param statusCode the HTTP status code - * @throws IllegalArgumentException thrown if status is null. - */ - PollingState withStatus(String status, int statusCode) throws IllegalArgumentException { - if (status == null) { - throw new IllegalArgumentException("Status is null."); - } - this.status = status; - this.statusCode = statusCode; - return this; - } - - /** - * Sets the last operation response. - * - * @param response the last operation response. - */ - PollingState withResponse(Response response) { - this.response = response; - withPollingUrlFromResponse(response); - withPollingRetryTimeoutFromResponse(response); - return this; - } - - PollingState withPollingUrlFromResponse(Response response) { - if (response != null) { - String asyncHeader = response.headers().get("Azure-AsyncOperation"); - String locationHeader = response.headers().get("Location"); - if (asyncHeader != null) { - this.azureAsyncOperationHeaderLink = asyncHeader; - } - if (locationHeader != null) { - this.locationHeaderLink = locationHeader; - } - } - return this; - } - - PollingState withPollingRetryTimeoutFromResponse(Response response) { - if (this.response != null && response.headers().get("Retry-After") != null) { - retryTimeout = Integer.parseInt(response.headers().get("Retry-After")) * 1000; - return this; - } - this.retryTimeout = -1; - return this; - } - - PollingState withPutOrPatchResourceUri(final String uri) { - this.putOrPatchResourceUri = uri; - return this; - } - - /** - * Sets the resource. - * - * @param resource the resource. - */ - PollingState withResource(T resource) { - this.resource = resource; - return this; - } - - /** - * @return the resource type - */ - Type resourceType() { - return resourceType; - } - - /** - * @return describes how to retrieve the final result of long running operation. - */ - LongRunningFinalState finalStateVia() { - // FinalStateVia is supported only for POST LRO at the moment. - if (this.initialHttpMethod().equalsIgnoreCase("POST") && resourceType() != Void.class - && this.locationHeaderLink() != null - && this.azureAsyncOperationHeaderLink() != null - && this.finalStateVia == LongRunningFinalState.LOCATION) { - - // Consider final-state-via option only if both headers are provided on the wire otherwise - // there is nothing to disambiguate. - // A POST LRO can be tracked only using Location or AsyncOperation Header. - // If AsyncOperationHeader is present then anyway polling will be performed using - // it and there is no point in making one additional call to retrieve state using - // async operation uri anyway. Hence we consider only LongRunningFinalState.LOCATION. - return LongRunningFinalState.LOCATION; - } - return LongRunningFinalState.DEFAULT; - } - - /** - * Sets resource type. - * - * param resourceType the resource type - */ - PollingState withResourceType(Type resourceType) { - this.resourceType = resourceType; - return this; - } - - /** - * Gets {@link CloudError} from current instance. - * - * @return the cloud error. - */ - CloudError errorBody() { - return error; - } - - /** - * Sets {@link CloudError} from current instance. - * - * @param error the cloud error. - */ - PollingState withErrorBody(CloudError error) { - this.error = error; - return this; - } - - /** - * Sets the serializer adapter. - * - * @param serializerAdapter the serializer adapter. - */ - PollingState withSerializerAdapter(SerializerAdapter serializerAdapter) { - this.serializerAdapter = serializerAdapter; - return this; - } - - /** - * @return the http method used to initiate the long running operation. - */ - String initialHttpMethod() { - return this.initialHttpMethod; - } - - /** - * If status is in failed state then throw CloudException. - */ - void throwCloudExceptionIfInFailedState() { - if (this.isStatusFailed()) { - if (this.errorBody() != null) { - throw new CloudException("Async operation failed with provisioning state: " + this.status(), this.response(), this.errorBody()); - } else { - throw new CloudException("Async operation failed with provisioning state: " + this.status(), this.response()); - } - } - } - - /** - * Initializes an object mapper. - * - * @param mapper the mapper to initialize - * @return the initialized mapper - */ - private static ObjectMapper initMapper(ObjectMapper mapper) { - mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) - .configure(SerializationFeature.WRITE_EMPTY_JSON_ARRAYS, true) - .configure(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT, true) - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .configure(DeserializationFeature.ACCEPT_SINGLE_VALUE_AS_ARRAY, true) - .setSerializationInclusion(JsonInclude.Include.NON_NULL) - .registerModule(new ParameterNamesModule()) - .registerModule(new Jdk8Module()) - .registerModule(new JavaTimeModule()) - .registerModule(ByteArraySerializer.getModule()) - .registerModule(Base64UrlSerializer.getModule()) - .registerModule(HeadersSerializer.getModule()); - mapper.setVisibility(mapper.getSerializationConfig().getDefaultVisibilityChecker() - .withFieldVisibility(JsonAutoDetect.Visibility.ANY) - .withSetterVisibility(JsonAutoDetect.Visibility.NONE) - .withGetterVisibility(JsonAutoDetect.Visibility.NONE) - .withIsGetterVisibility(JsonAutoDetect.Visibility.NONE)); - return mapper; - } - - /** - * An instance of this class describes the status of a long running operation - * and is returned from server each time. - */ - private static class PollingResource { - /** Inner properties object. */ - @JsonProperty(value = "properties") - private Properties properties; - - /** - * Inner properties class. - */ - private static class Properties { - /** The provisioning state of the resource. */ - @JsonProperty(value = "provisioningState") - private String provisioningState; - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ProxyResource.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ProxyResource.java deleted file mode 100644 index bbdd0e6d6..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/ProxyResource.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * The Proxy Resource model. - */ -public class ProxyResource { - /** - * Resource Id. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String id; - - /** - * Resource name. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String name; - - /** - * Resource type. - */ - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String type; - - /** - * Get the id value. - * - * @return the id value - */ - public String id() { - return this.id; - } - - /** - * Get the name value. - * - * @return the name value - */ - public String name() { - return this.name; - } - - /** - * Get the type value. - * - * @return the type value - */ - public String type() { - return this.type; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Resource.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Resource.java deleted file mode 100644 index cad012f2a..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/Resource.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import java.util.Map; - -/** - * The Resource model. - */ -public class Resource extends ProxyResource { - /** - * Resource location. - */ - @JsonProperty(required = true) - private String location; - - /** - * Resource tags. - */ - private Map tags; - - /** - * Get the location value. - * - * @return the location value - */ - public String location() { - return this.location; - } - - /** - * Set the location value. - * - * @param location the location value to set - * @return the resource itself - */ - public Resource withLocation(String location) { - this.location = location; - return this; - } - - /** - * Get the tags value. - * - * @return the tags value - */ - public Map getTags() { - return this.tags; - } - - /** - * Set the tags value. - * - * @param tags the tags value to set - * @return the resource itself - */ - public Resource withTags(Map tags) { - this.tags = tags; - return this; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/SubResource.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/SubResource.java deleted file mode 100644 index 9b9f03339..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/SubResource.java +++ /dev/null @@ -1,37 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -/** - * The SubResource model. - */ -public class SubResource { - /** - * Resource Id. - */ - private String id; - - /** - * Get the id value. - * - * @return the id value - */ - public String id() { - return this.id; - } - - /** - * Set the id value. - * - * @param id the id value to set - * @return the sub resource itself - */ - public SubResource withId(String id) { - this.id = id; - return this; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/TypedErrorInfo.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/TypedErrorInfo.java deleted file mode 100644 index 2351f3aef..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/TypedErrorInfo.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.fasterxml.jackson.databind.node.ObjectNode; - -/** - * An instance of this class provides Azure error type and information. - */ -public class TypedErrorInfo { - /** - * The error type. - */ - private String type; - - /** - * The error information. - */ - private ObjectNode info; - - /** - * Initializes a new instance of TypedErrorInfo. - * @param type the error type. - * @param info the error information. - */ - public TypedErrorInfo(String type, ObjectNode info) { - this.type = type; - this.info = info; - } - - /** - * @return the error type. - */ - public String type() { - return type; - } - - /** - * @return the error information. - */ - public ObjectNode info() { - return info; - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentials.java deleted file mode 100644 index e0a32eb1f..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentials.java +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure.credentials; - -import com.microsoft.bot.azure.AzureEnvironment; -import com.microsoft.bot.azure.AzureEnvironment.Endpoint; -import com.microsoft.bot.rest.credentials.TokenCredentials; -import okhttp3.Authenticator; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.Route; - -import javax.net.ssl.SSLSocketFactory; -import java.io.IOException; -import java.net.Proxy; -import java.util.Map; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * AzureTokenCredentials represents a credentials object with access to Azure - * Resource management. - */ -public abstract class AzureTokenCredentials extends TokenCredentials { - private final AzureEnvironment environment; - private final String domain; - private String defaultSubscription; - - private Proxy proxy; - private SSLSocketFactory sslSocketFactory; - - /** - * Initializes a new instance of the AzureTokenCredentials. - * - * @param environment the Azure environment to use - * @param domain the tenant or domain the credential is authorized to - */ - public AzureTokenCredentials(AzureEnvironment environment, String domain) { - super("Bearer", null); - this.environment = (environment == null) ? AzureEnvironment.AZURE : environment; - this.domain = domain; - } - - @Override - protected final String getToken(Request request) throws IOException { - String host = request.url().toString().toLowerCase(); - String resource = environment().managementEndpoint(); - for (Map.Entry endpoint : environment().endpoints().entrySet()) { - if (host.contains(endpoint.getValue())) { - if (endpoint.getKey().equals(Endpoint.KEYVAULT.identifier())) { - resource = String.format("https://%s/", endpoint.getValue().replaceAll("^\\.*", "")); - break; - } else if (endpoint.getKey().equals(Endpoint.GRAPH.identifier())) { - resource = environment().graphEndpoint(); - break; - } else if (endpoint.getKey().equals(Endpoint.LOG_ANALYTICS.identifier())) { - resource = environment().logAnalyticsEndpoint(); - break; - } else if (endpoint.getKey().equals(Endpoint.APPLICATION_INSIGHTS.identifier())) { - resource = environment().applicationInsightsEndpoint(); - break; - } else if (endpoint.getKey().equals(Endpoint.DATA_LAKE_STORE.identifier()) - || endpoint.getKey().equals(Endpoint.DATA_LAKE_ANALYTICS.identifier())) { - resource = environment().dataLakeEndpointResourceId(); - break; - } - } - } - return getToken(resource); - } - - /** - * Override this method to provide the mechanism to get a token. - * - * @param resource the resource the access token is for - * @return the token to access the resource - * @throws IOException exceptions from IO - */ - public abstract String getToken(String resource) throws IOException; - - /** - * Override this method to provide the domain or tenant ID the token is valid in. - * - * @return the domain or tenant ID string - */ - public String domain() { - return domain; - } - - /** - * @return the environment details the credential has access to. - */ - public AzureEnvironment environment() { - return environment; - } - - /** - * @return The default subscription ID, if any - */ - public String defaultSubscriptionId() { - return defaultSubscription; - } - - /** - * Set default subscription ID. - * - * @param subscriptionId the default subscription ID. - * @return the credentials object itself. - */ - public AzureTokenCredentials withDefaultSubscriptionId(String subscriptionId) { - this.defaultSubscription = subscriptionId; - return this; - } - - /** - * @return the proxy being used for accessing Active Directory. - */ - public Proxy proxy() { - return proxy; - } - - /** - * @return the ssl socket factory. - */ - public SSLSocketFactory sslSocketFactory() { - return sslSocketFactory; - } - - /** - * @param proxy the proxy being used for accessing Active Directory - * @return the credential itself - */ - public AzureTokenCredentials withProxy(Proxy proxy) { - this.proxy = proxy; - return this; - } - - /** - * @param sslSocketFactory the ssl socket factory - * @return the credential itself - */ - public AzureTokenCredentials withSslSocketFactory(SSLSocketFactory sslSocketFactory) { - this.sslSocketFactory = sslSocketFactory; - return this; - } - - @Override - public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { - clientBuilder.interceptors().add(new AzureTokenCredentialsInterceptor(this)); - clientBuilder.authenticator(new Authenticator() { - @Override - public Request authenticate(Route route, Response response) throws IOException { - String authenticateHeader = response.header("WWW-Authenticate"); - if (authenticateHeader != null && !authenticateHeader.isEmpty()) { - Pattern pattern = Pattern.compile("resource=\"([a-zA-Z0-9.:/-_]+)\""); - Matcher matcher = pattern.matcher(authenticateHeader); - if (matcher.find()) { - String resource = matcher.group(1); - return response.request().newBuilder() - .header("Authorization", "Bearer " + getToken(resource)) - .build(); - } - } - // Otherwise cannot satisfy the challenge - return null; - } - }); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentialsInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentialsInterceptor.java deleted file mode 100644 index 8e11fa31c..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/AzureTokenCredentialsInterceptor.java +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure.credentials; - -import okhttp3.Interceptor; -import okhttp3.Request; -import okhttp3.Response; - -import java.io.IOException; - -/** - * Token credentials filter for placing a token credential into request headers. - */ -public final class AzureTokenCredentialsInterceptor implements Interceptor { - /** - * The credentials instance to apply to the HTTP client pipeline. - */ - private AzureTokenCredentials credentials; - - /** - * Initialize a TokenCredentialsFilter class with a - * TokenCredentials credential. - * - * @param credentials a TokenCredentials instance - */ - AzureTokenCredentialsInterceptor(AzureTokenCredentials credentials) { - this.credentials = credentials; - } - - @Override - public Response intercept(Chain chain) throws IOException { - String token = credentials.getToken(chain.request()); - Request newRequest = chain.request().newBuilder() - .header("Authorization", "Bearer " + token) - .build(); - return chain.proceed(newRequest); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/package-info.java deleted file mode 100644 index aa6d63128..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/credentials/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * The package contains the credentials classes required for AutoRest generated - * Azure clients to compile and function. To learn more about AutoRest generator, - * see https://github.com/azure/autorest. - */ -package com.microsoft.bot.azure.credentials; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/package-info.java deleted file mode 100644 index 7e6fe3a95..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/package-info.java +++ /dev/null @@ -1,6 +0,0 @@ -/** - * The package contains the runtime classes required for AutoRest generated - * Azure clients to compile and function. To learn more about AutoRest generator, - * see https://github.com/azure/autorest. - */ -package com.microsoft.bot.azure; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/AzureJacksonAdapter.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/AzureJacksonAdapter.java deleted file mode 100644 index 5c9d9adc8..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/AzureJacksonAdapter.java +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure.serializer; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import com.microsoft.bot.rest.serializer.JacksonAdapter; - -/** - * A serialization helper class overriding {@link JacksonAdapter} with extra - * functionality useful for Azure operations. - */ -public final class AzureJacksonAdapter extends JacksonAdapter implements SerializerAdapter { - /** - * Creates an instance of the Azure flavored Jackson adapter. - */ - public AzureJacksonAdapter() { - super(); - serializer().registerModule(CloudErrorDeserializer.getModule(simpleMapper())); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/CloudErrorDeserializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/CloudErrorDeserializer.java deleted file mode 100644 index 386beb43d..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/CloudErrorDeserializer.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure.serializer; - -import com.fasterxml.jackson.core.JsonFactory; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.MapperFeature; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.microsoft.bot.azure.CloudError; - -import java.io.IOException; - -/** - * Custom serializer for serializing {@link CloudError} objects. - */ -final class CloudErrorDeserializer extends JsonDeserializer { - /** Object mapper for default deserializations. */ - private ObjectMapper mapper; - - /** - * Creates an instance of CloudErrorDeserializer. - * - * @param mapper the object mapper for default deserializations. - */ - private CloudErrorDeserializer(ObjectMapper mapper) { - mapper.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); - this.mapper = mapper; - } - - /** - * Gets a module wrapping this serializer as an adapter for the Jackson - * ObjectMapper. - * - * @param mapper the object mapper for default deserializations. - * @return a simple module to be plugged onto Jackson ObjectMapper. - */ - static SimpleModule getModule(ObjectMapper mapper) { - SimpleModule module = new SimpleModule(); - module.addDeserializer(CloudError.class, new CloudErrorDeserializer(mapper)); - return module; - } - - @Override - public CloudError deserialize(JsonParser p, DeserializationContext ctxt) throws IOException { - p.setCodec(mapper); - JsonNode errorNode = p.readValueAsTree(); - - if (errorNode == null) { - return null; - } - if (errorNode.get("error") != null) { - errorNode = errorNode.get("error"); - } - - JsonParser parser = new JsonFactory().createParser(errorNode.toString()); - parser.setCodec(mapper); - return parser.readValueAs(CloudError.class); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/TypedErrorInfoDeserializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/TypedErrorInfoDeserializer.java deleted file mode 100644 index efcb60459..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/TypedErrorInfoDeserializer.java +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure.serializer; - -import java.io.IOException; -import java.util.Iterator; - -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.DeserializationContext; -import com.fasterxml.jackson.databind.JsonDeserializer; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.microsoft.bot.azure.PolicyViolation; -import com.microsoft.bot.azure.TypedErrorInfo; - -/** - * Custom serializer for serializing {@link TypedErrorInfo} objects. - */ -public class TypedErrorInfoDeserializer extends JsonDeserializer { - private static final String TYPE_FIELD_NAME = "type"; - private static final String INFO_FIELD_NAME = "info"; - - @Override - public TypedErrorInfo deserialize(JsonParser p, DeserializationContext ctxt) - throws IOException, JsonProcessingException { - JsonNode errorInfoNode = p.readValueAsTree(); - if (errorInfoNode == null) { - return null; - } - - JsonNode typeNode = errorInfoNode.get(TYPE_FIELD_NAME); - JsonNode infoNode = errorInfoNode.get(INFO_FIELD_NAME); - if (typeNode == null || infoNode == null) { - Iterator fieldNames = errorInfoNode.fieldNames(); - while (fieldNames.hasNext()) { - String fieldName = fieldNames.next(); - if (typeNode == null && TYPE_FIELD_NAME.equalsIgnoreCase(fieldName)) { - typeNode = errorInfoNode.get(fieldName); - - } - - if (infoNode == null && INFO_FIELD_NAME.equalsIgnoreCase(fieldName)) { - infoNode = errorInfoNode.get(fieldName); - - } - } - } - - if (typeNode == null || infoNode == null || !(infoNode instanceof ObjectNode)) { - return null; - } - - // deserialize to any strongly typed error defined - switch (typeNode.asText()) { - case "PolicyViolation": - return new PolicyViolation(typeNode.asText(), (ObjectNode) infoNode); - - default: - return new TypedErrorInfo(typeNode.asText(), (ObjectNode) infoNode); - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/package-info.java deleted file mode 100644 index 0e35c7905..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/azure/serializer/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The package contains classes that handle serialization and deserialization - * for the REST call payloads in Azure. - */ -package com.microsoft.bot.azure.serializer; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ConnectorClient.java index 8d38e6a09..168439e3b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ConnectorClient.java @@ -10,8 +10,8 @@ package com.microsoft.bot.connector; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; /** * The interface for ConnectorClient class. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java index fb2b43e7a..bd45a8675 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java @@ -210,7 +210,7 @@ default CompletableFuture replyToActivity(Activity activity) { * @param conversationId Conversation ID * @param activityId activityId to delete * @throws IllegalArgumentException thrown if parameters fail the validation - * @return the {@link com.microsoft.bot.rest.ServiceResponse} object if + * @return the {@link com.microsoft.bot.restclient.ServiceResponse} object if * successful. */ CompletableFuture deleteActivity(String conversationId, String activityId); @@ -244,7 +244,7 @@ default CompletableFuture replyToActivity(Activity activity) { * @param conversationId Conversation ID * @param memberId ID of the member to delete from this conversation * @throws IllegalArgumentException thrown if parameters fail the validation - * @return the {@link com.microsoft.bot.rest.ServiceResponse} object if + * @return the {@link com.microsoft.bot.restclient.ServiceResponse} object if * successful. */ CompletableFuture deleteConversationMember(String conversationId, String memberId); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java index 4f0fcd8c3..cf638239c 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java @@ -4,7 +4,7 @@ package com.microsoft.bot.connector.authentication; import com.microsoft.aad.msal4j.IAuthenticationResult; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; import okhttp3.OkHttpClient; import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/ErrorResponseException.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/ErrorResponseException.java index 247e4893a..ce40b6f09 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/ErrorResponseException.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/ErrorResponseException.java @@ -6,7 +6,7 @@ package com.microsoft.bot.connector.rest; -import com.microsoft.bot.rest.RestException; +import com.microsoft.bot.restclient.RestException; import com.microsoft.bot.schema.ErrorResponse; import okhttp3.ResponseBody; import retrofit2.Response; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java index f714b526e..ed713763e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java @@ -10,7 +10,7 @@ import com.microsoft.bot.connector.Attachments; import com.google.common.reflect.TypeToken; import com.microsoft.bot.schema.AttachmentInfo; -import com.microsoft.bot.rest.ServiceResponse; +import com.microsoft.bot.restclient.ServiceResponse; import java.io.InputStream; import java.io.IOException; import java.net.HttpURLConnection; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java index 47bb35564..9628de4e9 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java @@ -8,7 +8,7 @@ import retrofit2.Retrofit; import com.microsoft.bot.connector.BotSignIn; -import com.microsoft.bot.rest.ServiceResponse; +import com.microsoft.bot.restclient.ServiceResponse; import java.util.concurrent.CompletableFuture; import okhttp3.ResponseBody; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConnectorClient.java index 2e934e1d4..831e126f7 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConnectorClient.java @@ -6,16 +6,16 @@ package com.microsoft.bot.connector.rest; -import com.microsoft.bot.azure.AzureResponseBuilder; -import com.microsoft.bot.azure.AzureServiceClient; -import com.microsoft.bot.azure.serializer.AzureJacksonAdapter; import com.microsoft.bot.connector.Attachments; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; import com.microsoft.bot.connector.UserAgent; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.retry.RetryStrategy; +import com.microsoft.bot.restclient.ServiceClient; +import com.microsoft.bot.restclient.ServiceResponseBuilder; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.retry.RetryStrategy; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import okhttp3.OkHttpClient; import retrofit2.Retrofit; @@ -35,7 +35,7 @@ * accomplished with JWT Bearer tokens, and is described in detail in the * [Connector Authentication](/en-us/restapi/authentication) document. */ -public class RestConnectorClient extends AzureServiceClient implements ConnectorClient { +public class RestConnectorClient extends ServiceClient implements ConnectorClient { private static final int RETRY_TIMEOUT = 30; /** @@ -281,8 +281,8 @@ public static RestClient.Builder getDefaultRestClientBuilder( return new RestClient.Builder(new OkHttpClient.Builder(), new Retrofit.Builder()) .withBaseUrl(baseUrl) .withCredentials(credentials) - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()); + .withSerializerAdapter(new JacksonAdapter()) + .withResponseBuilderFactory(new ServiceResponseBuilder.Factory()); } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java index 44a18a432..ce39d99c3 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java @@ -6,7 +6,7 @@ package com.microsoft.bot.connector.rest; -import com.microsoft.bot.azure.AzureResponseBuilder; +import com.microsoft.bot.restclient.ServiceResponseBuilder; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.AttachmentData; import com.microsoft.bot.schema.ChannelAccount; @@ -19,8 +19,8 @@ import retrofit2.Retrofit; import com.microsoft.bot.connector.Conversations; import com.google.common.reflect.TypeToken; -import com.microsoft.bot.rest.ServiceResponse; -import com.microsoft.bot.rest.Validator; +import com.microsoft.bot.restclient.ServiceResponse; +import com.microsoft.bot.restclient.Validator; import java.io.IOException; import java.net.HttpURLConnection; @@ -607,7 +607,7 @@ private ServiceResponse getConversationMemberDelegate( Response response ) throws ErrorResponseException, IOException, IllegalArgumentException { - return ((AzureResponseBuilder) client.restClient() + return ((ServiceResponseBuilder) client.restClient() .responseBuilderFactory() .newInstance(client.serializerAdapter()) .register(HttpURLConnection.HTTP_OK, new TypeToken() { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java index a1119b190..01b11d6e7 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java @@ -1,16 +1,16 @@ package com.microsoft.bot.connector.rest; -import com.microsoft.bot.azure.AzureServiceClient; import com.microsoft.bot.connector.BotSignIn; import com.microsoft.bot.connector.OAuthClient; import com.microsoft.bot.connector.UserToken; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.ServiceClient; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; /** * Rest OAuth client. */ -public class RestOAuthClient extends AzureServiceClient implements OAuthClient { +public class RestOAuthClient extends ServiceClient implements OAuthClient { /** * The BotSignIns object to access its operations. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsConnectorClient.java index 0fb043f45..040ada972 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsConnectorClient.java @@ -6,15 +6,15 @@ package com.microsoft.bot.connector.rest; -import com.microsoft.bot.azure.AzureResponseBuilder; -import com.microsoft.bot.azure.AzureServiceClient; -import com.microsoft.bot.azure.serializer.AzureJacksonAdapter; import com.microsoft.bot.connector.UserAgent; import com.microsoft.bot.connector.teams.TeamsConnectorClient; import com.microsoft.bot.connector.teams.TeamsOperations; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import com.microsoft.bot.rest.retry.RetryStrategy; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.ServiceClient; +import com.microsoft.bot.restclient.ServiceResponseBuilder; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.retry.RetryStrategy; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import okhttp3.OkHttpClient; import retrofit2.Retrofit; @@ -34,7 +34,7 @@ * accomplished with JWT Bearer tokens, and is described in detail in the * [Connector Authentication](/en-us/restapi/authentication) document. */ -public class RestTeamsConnectorClient extends AzureServiceClient implements TeamsConnectorClient { +public class RestTeamsConnectorClient extends ServiceClient implements TeamsConnectorClient { private static final int RETRY_TIMEOUT = 30; /** Gets or sets the preferred language for the response. */ @@ -259,8 +259,8 @@ public static RestClient.Builder getDefaultRestClientBuilder( return new RestClient.Builder(new OkHttpClient.Builder(), new Retrofit.Builder()) .withBaseUrl(baseUrl) .withCredentials(credentials) - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()); + .withSerializerAdapter(new JacksonAdapter()) + .withResponseBuilderFactory(new ServiceResponseBuilder.Factory()); } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java index 9f5210fd9..b3a96346a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java @@ -8,7 +8,7 @@ import com.google.common.reflect.TypeToken; import com.microsoft.bot.connector.teams.TeamsOperations; -import com.microsoft.bot.rest.ServiceResponse; +import com.microsoft.bot.restclient.ServiceResponse; import com.microsoft.bot.schema.teams.ConversationList; import com.microsoft.bot.schema.teams.TeamDetails; import com.microsoft.bot.schema.teams.TeamsMeetingParticipant; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java index 8ea25134c..b79504d65 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java @@ -12,8 +12,8 @@ import com.microsoft.bot.schema.AadResourceUrls; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; -import com.microsoft.bot.rest.ServiceResponse; -import com.microsoft.bot.rest.Validator; +import com.microsoft.bot.restclient.ServiceResponse; +import com.microsoft.bot.restclient.Validator; import java.io.IOException; import java.net.HttpURLConnection; import java.util.List; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java index 9b3af4342..97e135c97 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java @@ -1,7 +1,7 @@ package com.microsoft.bot.connector.teams; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; /** * Teams operations. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/DateTimeRfc1123.java.dep b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/DateTimeRfc1123.java.dep deleted file mode 100644 index 4201472f6..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/DateTimeRfc1123.java.dep +++ /dev/null @@ -1,79 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.rest; - -import org.joda.time.DateTime; -import org.joda.time.format.DateTimeFormat; -import org.joda.time.format.DateTimeFormatter; -import java.util.Locale; - -/** - * Simple wrapper over joda.time.DateTime used for specifying RFC1123 format during serialization/deserialization. - */ -public final class DateTimeRfc1123 { - /** - * The pattern of the datetime used for RFC1123 datetime format. - */ - private static final DateTimeFormatter RFC1123_DATE_TIME_FORMATTER = - DateTimeFormat.forPattern("EEE, dd MMM yyyy HH:mm:ss 'GMT'").withZoneUTC().withLocale(Locale.US); - - /** - * The actual datetime object. - */ - private final DateTime dateTime; - - /** - * Creates a new DateTimeRfc1123 object with the specified DateTime. - * @param dateTime The DateTime object to wrap. - */ - public DateTimeRfc1123(DateTime dateTime) { - this.dateTime = dateTime; - } - - /** - * Creates a new DateTimeRfc1123 object with the specified DateTime. - * @param formattedString The datetime string in RFC1123 format - */ - public DateTimeRfc1123(String formattedString) { - this.dateTime = DateTime.parse(formattedString, RFC1123_DATE_TIME_FORMATTER); - } - - /** - * Returns the underlying DateTime. - * @return The underlying DateTime. - */ - public DateTime dateTime() { - if (this.dateTime == null) { - return null; - } - return this.dateTime; - } - - @Override - public String toString() { - return RFC1123_DATE_TIME_FORMATTER.print(this.dateTime); - } - - @Override - public int hashCode() { - return this.dateTime.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) { - return false; - } - - if (!(obj instanceof DateTimeRfc1123)) { - return false; - } - - DateTimeRfc1123 rhs = (DateTimeRfc1123) obj; - return this.dateTime.equals(rhs.dateTime()); - } -} \ No newline at end of file diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceClient.java deleted file mode 100644 index d93efb43a..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceClient.java +++ /dev/null @@ -1,83 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.rest; - -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import com.microsoft.bot.rest.serializer.JacksonAdapter; -import okhttp3.OkHttpClient; -import retrofit2.Retrofit; - -/** - * ServiceClient is the abstraction for accessing REST operations and their payload data types. - */ -public abstract class ServiceClient { - /** - * The RestClient instance storing all information needed for making REST calls. - */ - private RestClient restClient; - - /** - * Initializes a new instance of the ServiceClient class. - * - * @param baseUrl the service endpoint - */ - protected ServiceClient(String baseUrl) { - this(baseUrl, new OkHttpClient.Builder(), new Retrofit.Builder()); - } - - /** - * Initializes a new instance of the ServiceClient class. - * - * @param baseUrl the service base uri - * @param clientBuilder the http client builder - * @param restBuilder the retrofit rest client builder - */ - protected ServiceClient(String baseUrl, OkHttpClient.Builder clientBuilder, Retrofit.Builder restBuilder) { - this(new RestClient.Builder(clientBuilder, restBuilder) - .withBaseUrl(baseUrl) - .withResponseBuilderFactory(new ServiceResponseBuilder.Factory()) - .withSerializerAdapter(new JacksonAdapter()) - .build()); - } - - /** - * Initializes a new instance of the ServiceClient class. - * - * @param restClient the REST client - */ - protected ServiceClient(RestClient restClient) { - this.restClient = restClient; - } - - /** - * @return the {@link RestClient} instance. - */ - public RestClient restClient() { - return restClient; - } - - /** - * @return the Retrofit instance. - */ - public Retrofit retrofit() { - return restClient.retrofit(); - } - - /** - * @return the HTTP client. - */ - public OkHttpClient httpClient() { - return this.restClient.httpClient(); - } - - /** - * @return the adapter to a Jackson {@link com.fasterxml.jackson.databind.ObjectMapper}. - */ - public SerializerAdapter serializerAdapter() { - return this.restClient.serializerAdapter(); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceFuture.java.dep b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceFuture.java.dep deleted file mode 100644 index 50fc8f564..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceFuture.java.dep +++ /dev/null @@ -1,216 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.rest; - -import com.google.common.util.concurrent.AbstractFuture; -import rx.Completable; -import rx.Observable; -import rx.Subscription; -import rx.functions.Action0; -import rx.functions.Action1; - -/** - * An instance of this class provides access to the underlying REST call invocation. - * This class wraps around the Retrofit Call object and allows updates to it in the - * progress of a long running operation or a paging operation. - * - * @param the type of the returning object - */ -public class ServiceFuture extends AbstractFuture { - /** - * The Retrofit method invocation. - */ - private Subscription subscription; - - protected ServiceFuture() { - } - - /** - * Creates a ServiceCall from an observable object. - * - * @param observable the observable to create from - * @param the type of the response - * @return the created ServiceCall - */ - public static ServiceFuture fromResponse(final Observable> observable) { - final ServiceFuture serviceFuture = new ServiceFuture<>(); - serviceFuture.subscription = observable - .last() - .subscribe(new Action1>() { - @Override - public void call(ServiceResponse t) { - serviceFuture.set(t.body()); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - serviceFuture.setException(throwable); - } - }); - return serviceFuture; - } - - /** - * Creates a ServiceCall from an observable object and a callback. - * - * @param observable the observable to create from - * @param callback the callback to call when events happen - * @param the type of the response - * @return the created ServiceCall - */ - public static ServiceFuture fromResponse(final Observable> observable, final ServiceCallback callback) { - final ServiceFuture serviceFuture = new ServiceFuture<>(); - serviceFuture.subscription = observable - .last() - .subscribe(new Action1>() { - @Override - public void call(ServiceResponse t) { - if (callback != null) { - callback.success(t.body()); - } - serviceFuture.set(t.body()); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - if (callback != null) { - callback.failure(throwable); - } - serviceFuture.setException(throwable); - } - }); - return serviceFuture; - } - - /** - * Creates a ServiceFuture from an observable object and a callback. - * - * @param observable the observable to create from - * @param callback the callback to call when events happen - * @param the type of the response - * @return the created ServiceFuture - */ - public static ServiceFuture fromBody(final Observable observable, final ServiceCallback callback) { - final ServiceFuture serviceFuture = new ServiceFuture<>(); - serviceFuture.subscription = observable - .last() - .subscribe(new Action1() { - @Override - public void call(T t) { - if (callback != null) { - callback.success(t); - } - serviceFuture.set(t); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - if (callback != null) { - callback.failure(throwable); - } - serviceFuture.setException(throwable); - } - }); - return serviceFuture; - } - - /** - * Creates a ServiceFuture from an Completable object and a callback. - * - * @param completable the completable to create from - * @param callback the callback to call when event happen - * @return the created ServiceFuture - */ - public static ServiceFuture fromBody(final Completable completable, final ServiceCallback callback) { - final ServiceFuture serviceFuture = new ServiceFuture<>(); - completable.subscribe(new Action0() { - Void value = null; - @Override - public void call() { - if (callback != null) { - callback.success(value); - } - serviceFuture.set(value); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - if (callback != null) { - callback.failure(throwable); - } - serviceFuture.setException(throwable); - } - }); - return serviceFuture; - }; - - /** - * Creates a ServiceCall from an observable and a callback for a header response. - * - * @param observable the observable of a REST call that returns JSON in a header - * @param callback the callback to call when events happen - * @param the type of the response body - * @param the type of the response header - * @return the created ServiceCall - */ - public static ServiceFuture fromHeaderResponse(final Observable> observable, final ServiceCallback callback) { - final ServiceFuture serviceFuture = new ServiceFuture<>(); - serviceFuture.subscription = observable - .last() - .subscribe(new Action1>() { - @Override - public void call(ServiceResponse t) { - if (callback != null) { - callback.success(t.body()); - } - serviceFuture.set(t.body()); - } - }, new Action1() { - @Override - public void call(Throwable throwable) { - if (callback != null) { - callback.failure(throwable); - } - serviceFuture.setException(throwable); - } - }); - return serviceFuture; - } - - /** - * @return the current Rx subscription associated with the ServiceCall. - */ - public Subscription getSubscription() { - return subscription; - } - - protected void setSubscription(Subscription subscription) { - this.subscription = subscription; - } - - /** - * Invoke this method to report completed, allowing - * {@link AbstractFuture#get()} to be unblocked. - * - * @param result the service response returned. - * @return true if successfully reported; false otherwise. - */ - public boolean success(T result) { - return set(result); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - subscription.unsubscribe(); - return super.cancel(mayInterruptIfRunning); - } - - @Override - public boolean isCancelled() { - return subscription.isUnsubscribed(); - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/package-info.java deleted file mode 100644 index ae9a87a91..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/package-info.java +++ /dev/null @@ -1,5 +0,0 @@ -/** - * The package provides 2 basic credential classes that work with AutoRest - * generated clients for authentication purposes. - */ -package com.microsoft.bot.rest.credentials; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeRfc1123Serializer.java.dep b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeRfc1123Serializer.java.dep deleted file mode 100644 index 6d02e97f6..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeRfc1123Serializer.java.dep +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.rest.serializer; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import com.microsoft.rest.DateTimeRfc1123; - -import java.io.IOException; - -/** - * Custom serializer for serializing {@link DateTimeRfc1123} object into RFC1123 formats. - */ -public final class DateTimeRfc1123Serializer extends JsonSerializer { - /** - * Gets a module wrapping this serializer as an adapter for the Jackson - * ObjectMapper. - * - * @return a simple module to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - SimpleModule module = new SimpleModule(); - module.addSerializer(DateTimeRfc1123.class, new DateTimeRfc1123Serializer()); - return module; - } - - @Override - public void serialize(DateTimeRfc1123 value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { - jgen.writeNumber(value.dateTime().getMillis()); - } else { - jgen.writeString(value.toString()); //Use the default toString as it is RFC1123. - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeSerializer.java.dep b/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeSerializer.java.dep deleted file mode 100644 index 940720c9d..000000000 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/DateTimeSerializer.java.dep +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.rest.serializer; - -import com.fasterxml.jackson.core.JsonGenerator; -import com.fasterxml.jackson.databind.JsonSerializer; -import com.fasterxml.jackson.databind.SerializationFeature; -import com.fasterxml.jackson.databind.SerializerProvider; -import com.fasterxml.jackson.databind.module.SimpleModule; -import org.joda.time.DateTime; -import org.joda.time.DateTimeZone; -import org.joda.time.format.ISODateTimeFormat; - -import java.io.IOException; - -/** - * Custom serializer for serializing {@link DateTime} object into ISO8601 formats. - */ -public final class DateTimeSerializer extends JsonSerializer { - /** - * Gets a module wrapping this serializer as an adapter for the Jackson - * ObjectMapper. - * - * @return a simple module to be plugged onto Jackson ObjectMapper. - */ - public static SimpleModule getModule() { - SimpleModule module = new SimpleModule(); - module.addSerializer(DateTime.class, new DateTimeSerializer()); - return module; - } - - @Override - public void serialize(DateTime value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - if (provider.isEnabled(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS)) { - jgen.writeNumber(value.getMillis()); - } else { - value = value.withZone(DateTimeZone.UTC); - jgen.writeString(value.toString(ISODateTimeFormat.dateTime())); - } - } -} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Base64Url.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Base64Url.java similarity index 90% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Base64Url.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Base64Url.java index fa05c1054..01e0f0ce2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Base64Url.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Base64Url.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.google.common.io.BaseEncoding; @@ -14,6 +11,7 @@ * Simple wrapper over Base64Url encoded byte array used during serialization/deserialization. */ public final class Base64Url { + /** * The Base64Url encoded bytes. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/CollectionFormat.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/CollectionFormat.java similarity index 85% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/CollectionFormat.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/CollectionFormat.java index 13b602dae..1b8438897 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/CollectionFormat.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/CollectionFormat.java @@ -1,11 +1,9 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; +@SuppressWarnings("checkstyle:linelength") /** * Swagger collection format to use for joining {@link java.util.List} parameters in * paths, queries, and headers. @@ -42,7 +40,7 @@ public enum CollectionFormat { /** * The delimiter separating the values. */ - private String delimiter; + private final String delimiter; /** * Creates an instance of the enum. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ExpandableStringEnum.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ExpandableStringEnum.java similarity index 91% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ExpandableStringEnum.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ExpandableStringEnum.java index 67f80298e..5b5f8fc19 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ExpandableStringEnum.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ExpandableStringEnum.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import java.util.ArrayList; import java.util.Collection; @@ -18,6 +15,7 @@ * @param a specific expandable enum type */ public abstract class ExpandableStringEnum> { + private static ConcurrentMap> valuesByName = null; private String name; @@ -66,9 +64,10 @@ protected static > T fromString(String name, C @SuppressWarnings("unchecked") protected static > Collection values(Class clazz) { // Make a copy of all values - Collection> values = new ArrayList<>(valuesByName.values()); + Collection> values = new ArrayList<>( + valuesByName.values()); - Collection list = new HashSet(); + Collection list = new HashSet<>(); for (ExpandableStringEnum value : values) { if (value.getClass().isAssignableFrom(clazz)) { list.add((T) value); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/LogLevel.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/LogLevel.java similarity index 85% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/LogLevel.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/LogLevel.java index 4bef98f67..be2971b7f 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/LogLevel.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/LogLevel.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; /** * Describes the level of HTTP traffic to log. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestClient.java similarity index 95% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestClient.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestClient.java index e32543a0a..f44e26026 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestClient.java @@ -1,24 +1,21 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.microsoft.azure.management.apigeneration.Beta; import com.microsoft.azure.management.apigeneration.Beta.SinceVersion; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import com.microsoft.bot.rest.interceptors.BaseUrlHandler; -import com.microsoft.bot.rest.interceptors.CustomHeadersInterceptor; -import com.microsoft.bot.rest.interceptors.LoggingInterceptor; -import com.microsoft.bot.rest.interceptors.RequestIdHeaderInterceptor; -import com.microsoft.bot.rest.interceptors.UserAgentInterceptor; -import com.microsoft.bot.rest.protocol.Environment; -import com.microsoft.bot.rest.protocol.ResponseBuilder; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import com.microsoft.bot.rest.retry.RetryHandler; -import com.microsoft.bot.rest.retry.RetryStrategy; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.interceptors.BaseUrlHandler; +import com.microsoft.bot.restclient.interceptors.CustomHeadersInterceptor; +import com.microsoft.bot.restclient.interceptors.LoggingInterceptor; +import com.microsoft.bot.restclient.interceptors.RequestIdHeaderInterceptor; +import com.microsoft.bot.restclient.interceptors.UserAgentInterceptor; +import com.microsoft.bot.restclient.protocol.Environment; +import com.microsoft.bot.restclient.protocol.ResponseBuilder; +import com.microsoft.bot.restclient.protocol.SerializerAdapter; +import com.microsoft.bot.restclient.retry.RetryHandler; +import com.microsoft.bot.restclient.retry.RetryStrategy; import okhttp3.Authenticator; import okhttp3.ConnectionPool; import okhttp3.Dispatcher; @@ -157,7 +154,7 @@ public static class Builder { /** The builder to build an {@link OkHttpClient}. */ private OkHttpClient.Builder httpClientBuilder; /** The builder to build a {@link Retrofit}. */ - private Retrofit.Builder retrofitBuilder; + private final Retrofit.Builder retrofitBuilder; /** The credentials to authenticate. */ private ServiceClientCredentials credentials; /** The credentials interceptor. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestException.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestException.java similarity index 85% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestException.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestException.java index edf6929c4..936c97e8e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/RestException.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/RestException.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import okhttp3.ResponseBody; import retrofit2.Response; @@ -16,7 +13,7 @@ public class RestException extends RuntimeException { /** * Information about the associated HTTP response. */ - private Response response; + private final Response response; /** * The HTTP response body. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceCallback.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceCallback.java similarity index 70% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceCallback.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceCallback.java index 5f266df1b..3418769ff 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceCallback.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceCallback.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; /** * The callback used for client side asynchronous operations. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceClient.java new file mode 100644 index 000000000..e182c4320 --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceClient.java @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.restclient; + +import com.google.common.hash.Hashing; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.protocol.SerializerAdapter; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import java.net.NetworkInterface; +import java.util.Enumeration; +import okhttp3.OkHttpClient; +import retrofit2.Retrofit; + +/** + * ServiceClient is the abstraction for accessing REST operations and their payload data types. + */ +public abstract class ServiceClient { + /** + * The RestClient instance storing all information needed for making REST calls. + */ + private final RestClient restClient; + + /** + * Initializes a new instance of the ServiceClient class. + * + * @param baseUrl the service endpoint + */ + protected ServiceClient(String baseUrl) { + this(baseUrl, new OkHttpClient.Builder(), new Retrofit.Builder()); + } + + /** + * Initializes a new instance of the ServiceClient class. + * + * @param baseUrl the service base uri + * @param clientBuilder the http client builder + * @param restBuilder the retrofit rest client builder + */ + protected ServiceClient(String baseUrl, OkHttpClient.Builder clientBuilder, Retrofit.Builder restBuilder) { + this(new RestClient.Builder(clientBuilder, restBuilder) + .withBaseUrl(baseUrl) + .withResponseBuilderFactory(new ServiceResponseBuilder.Factory()) + .withSerializerAdapter(new JacksonAdapter()) + .build()); + } + + protected ServiceClient(String baseUrl, ServiceClientCredentials credentials) { + this(baseUrl, credentials, new OkHttpClient.Builder(), new Retrofit.Builder()); + } + + /** + * Initializes a new instance of the ServiceClient class. + * + * @param baseUrl the service base uri + * @param credentials the credentials + * @param clientBuilder the http client builder + * @param restBuilder the retrofit rest client builder + */ + protected ServiceClient(String baseUrl, ServiceClientCredentials credentials, OkHttpClient.Builder clientBuilder, Retrofit.Builder restBuilder) { + this(new RestClient.Builder(clientBuilder, restBuilder) + .withBaseUrl(baseUrl) + .withCredentials(credentials) + .withSerializerAdapter(new JacksonAdapter()) + .withResponseBuilderFactory(new ServiceResponseBuilder.Factory()) + .build()); + } + + /** + * Initializes a new instance of the ServiceClient class. + * + * @param restClient the REST client + */ + protected ServiceClient(RestClient restClient) { + this.restClient = restClient; + } + + /** + * @return the {@link RestClient} instance. + */ + public RestClient restClient() { + return restClient; + } + + /** + * @return the Retrofit instance. + */ + public Retrofit retrofit() { + return restClient.retrofit(); + } + + /** + * @return the HTTP client. + */ + public OkHttpClient httpClient() { + return this.restClient.httpClient(); + } + + /** + * @return the adapter to a Jackson {@link com.fasterxml.jackson.databind.ObjectMapper}. + */ + public SerializerAdapter serializerAdapter() { + return this.restClient.serializerAdapter(); + } + + /** + * The default User-Agent header. Override this method to override the user agent. + * + * @return the user agent string. + */ + public String userAgent() { + return String.format("Azure-SDK-For-Java/%s OS:%s MacAddressHash:%s Java:%s", + getClass().getPackage().getImplementationVersion(), + OS, + MAC_ADDRESS_HASH, + JAVA_VERSION); + } + + private static final String MAC_ADDRESS_HASH; + private static final String OS; + private static final String JAVA_VERSION; + + static { + OS = System.getProperty("os.name") + "/" + System.getProperty("os.version"); + String macAddress = "Unknown"; + + try { + Enumeration networks = NetworkInterface.getNetworkInterfaces(); + while (networks.hasMoreElements()) { + NetworkInterface network = networks.nextElement(); + byte[] mac = network.getHardwareAddress(); + + if (mac != null) { + macAddress = Hashing.sha256().hashBytes(mac).toString(); + break; + } + } + } catch (Throwable ignore) { //NOPMD + // It's okay ignore mac address hash telemetry + } + MAC_ADDRESS_HASH = macAddress; + String version = System.getProperty("java.version"); + JAVA_VERSION = version != null ? version : "Unknown"; + } +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponse.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponse.java similarity index 90% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponse.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponse.java index 95af3d33f..60791aeec 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponse.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponse.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import okhttp3.ResponseBody; import retrofit2.Response; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseBuilder.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseBuilder.java similarity index 90% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseBuilder.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseBuilder.java index 5f3c03566..c94f7bbd6 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseBuilder.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseBuilder.java @@ -1,16 +1,12 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.core.type.TypeReference; -import com.google.common.base.Function; import com.google.common.collect.Maps; -import com.microsoft.bot.rest.protocol.ResponseBuilder; -import com.microsoft.bot.rest.protocol.SerializerAdapter; +import com.microsoft.bot.restclient.protocol.ResponseBuilder; +import com.microsoft.bot.restclient.protocol.SerializerAdapter; import okhttp3.ResponseBody; import retrofit2.Response; @@ -150,12 +146,9 @@ public ServiceResponse buildEmpty(Response response) throws IOException public ServiceResponseWithHeaders buildWithHeaders(final Response response, Class headerType) throws IOException { ServiceResponse bodyResponse = build(response); THeader headers = serializerAdapter.deserialize( - serializerAdapter.serialize(Maps.asMap(response.headers().names(), new Function() { - @Override - public String apply(String s) { - return response.headers().get(s); - } - })), + serializerAdapter.serialize(Maps.asMap(response.headers().names(), + s -> response.headers().get(s) + )), headerType); return new ServiceResponseWithHeaders<>(bodyResponse.body(), headers, bodyResponse.response()); } @@ -164,12 +157,9 @@ public String apply(String s) { public ServiceResponseWithHeaders buildEmptyWithHeaders(final Response response, Class headerType) throws IOException { ServiceResponse bodyResponse = buildEmpty(response); THeader headers = serializerAdapter.deserialize( - serializerAdapter.serialize(Maps.asMap(response.headers().names(), new Function() { - @Override - public String apply(String s) { - return response.headers().get(s); - } - })), + serializerAdapter.serialize(Maps.asMap(response.headers().names(), + s -> response.headers().get(s) + )), headerType); ServiceResponseWithHeaders serviceResponse = new ServiceResponseWithHeaders<>(headers, bodyResponse.headResponse()); serviceResponse.withBody(bodyResponse.body()); @@ -240,7 +230,7 @@ public boolean isSuccessful(int statusCode) { * @param throwOnGet404 true if to throw; false to simply return null. Default is false. * @return the response builder itself */ - public ServiceResponseBuilder withThrowOnGet404(boolean throwOnGet404) { + public ServiceResponseBuilder withThrowOnGet404(boolean throwOnGet404) { this.throwOnGet404 = throwOnGet404; return this; } @@ -251,7 +241,7 @@ public ServiceResponseBuilder withThrowOnGet404(boolean throwOnGet404) { public static final class Factory implements ResponseBuilder.Factory { @Override public ServiceResponseBuilder newInstance(final SerializerAdapter serializerAdapter) { - return new ServiceResponseBuilder(serializerAdapter); + return new ServiceResponseBuilder<>(serializerAdapter); } } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseWithHeaders.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseWithHeaders.java similarity index 85% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseWithHeaders.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseWithHeaders.java index f13a3842c..58e69f7e0 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/ServiceResponseWithHeaders.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/ServiceResponseWithHeaders.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import okhttp3.ResponseBody; import retrofit2.Response; @@ -19,7 +16,7 @@ public final class ServiceResponseWithHeaders extends ServiceRes /** * The response headers object. */ - private THeader headers; + private final THeader headers; /** * Instantiate a ServiceResponse instance with a response object and a raw REST response. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/SkipParentValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/SkipParentValidation.java similarity index 67% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/SkipParentValidation.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/SkipParentValidation.java index e4e034a31..9eb40ae53 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/SkipParentValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/SkipParentValidation.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Validator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Validator.java similarity index 94% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Validator.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Validator.java index 218f7115b..ef95f8653 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/Validator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/Validator.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.primitives.Primitives; @@ -41,7 +38,7 @@ public static void validate(Object parameter) { return; } - Class parameterType = parameter.getClass(); + Class parameterType = parameter.getClass(); TypeToken parameterToken = TypeToken.of(parameterType); if (Primitives.isWrapperType(parameterType)) { parameterToken = parameterToken.unwrap(); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentials.java similarity index 56% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentials.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentials.java index a27ad1b06..14ffbfa50 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentials.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.credentials; +package com.microsoft.bot.restclient.credentials; import okhttp3.OkHttpClient; @@ -16,22 +13,22 @@ public class BasicAuthenticationCredentials implements ServiceClientCredentials /** * Basic auth UserName. */ - private String userName; + private final String userName; /** * Basic auth password. */ - private String password; + private final String password; /** * Instantiates a new basic authentication credential. * - * @param userName basic auth user name - * @param password basic auth password + * @param withUserName basic auth user name + * @param withPassword basic auth password */ - public BasicAuthenticationCredentials(String userName, String password) { - this.userName = userName; - this.password = password; + public BasicAuthenticationCredentials(String withUserName, String withPassword) { + this.userName = withUserName; + this.password = withPassword; } /** @@ -48,6 +45,11 @@ protected String getPassword() { return password; } + /** + * Apply the credentials to the HTTP client builder. + * + * @param clientBuilder the builder for building up an {@link OkHttpClient} + */ @Override public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { clientBuilder.interceptors().add(new BasicAuthenticationCredentialsInterceptor(this)); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentialsInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentialsInterceptor.java similarity index 60% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentialsInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentialsInterceptor.java index e1568be39..d0069ea0d 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/BasicAuthenticationCredentialsInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/BasicAuthenticationCredentialsInterceptor.java @@ -1,12 +1,10 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.credentials; +package com.microsoft.bot.restclient.credentials; import com.google.common.io.BaseEncoding; +import java.nio.charset.StandardCharsets; import okhttp3.Interceptor; import okhttp3.Request; import okhttp3.Response; @@ -20,22 +18,28 @@ final class BasicAuthenticationCredentialsInterceptor implements Interceptor { /** * The credentials instance to apply to the HTTP client pipeline. */ - private BasicAuthenticationCredentials credentials; + private final BasicAuthenticationCredentials credentials; /** * Initialize a BasicAuthenticationCredentialsFilter class with a * BasicAuthenticationCredentials credential. * - * @param credentials a BasicAuthenticationCredentials instance + * @param withCredentials a BasicAuthenticationCredentials instance */ - BasicAuthenticationCredentialsInterceptor(BasicAuthenticationCredentials credentials) { - this.credentials = credentials; + BasicAuthenticationCredentialsInterceptor(BasicAuthenticationCredentials withCredentials) { + this.credentials = withCredentials; } + /** + * Handle OKHttp intercept. + * @param chain okhttp3 Chain + * @return okhttp3 Response + * @throws IOException IOException during http IO. + */ @Override public Response intercept(Chain chain) throws IOException { String auth = credentials.getUserName() + ":" + credentials.getPassword(); - auth = BaseEncoding.base64().encode(auth.getBytes("UTF8")); + auth = BaseEncoding.base64().encode(auth.getBytes(StandardCharsets.UTF_8)); Request newRequest = chain.request().newBuilder() .header("Authorization", "Basic " + auth) .build(); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/ServiceClientCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/ServiceClientCredentials.java similarity index 66% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/ServiceClientCredentials.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/ServiceClientCredentials.java index bc9af5bae..d98bdb991 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/ServiceClientCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/ServiceClientCredentials.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.credentials; +package com.microsoft.bot.restclient.credentials; import okhttp3.OkHttpClient; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentials.java similarity index 57% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentials.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentials.java index 63c5ad271..1774b8fb2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentials.java @@ -1,38 +1,33 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.credentials; +package com.microsoft.bot.restclient.credentials; import okhttp3.OkHttpClient; import okhttp3.Request; -import java.io.IOException; - /** * Token based credentials for use with a REST Service Client. */ public class TokenCredentials implements ServiceClientCredentials { /** The authentication scheme. */ - private String scheme; + private final String scheme; /** The secure token. */ - private String token; + private final String token; /** * Initializes a new instance of the TokenCredentials. * - * @param scheme scheme to use. If null, defaults to Bearer - * @param token valid token + * @param withScheme scheme to use. If null, defaults to Bearer + * @param withToken valid token */ - public TokenCredentials(String scheme, String token) { - if (scheme == null) { - scheme = "Bearer"; + public TokenCredentials(String withScheme, String withToken) { + if (withScheme == null) { + withScheme = "Bearer"; } - this.scheme = scheme; - this.token = token; + this.scheme = withScheme; + this.token = withToken; } /** @@ -41,9 +36,8 @@ public TokenCredentials(String scheme, String token) { * * @param request the context of the HTTP request * @return the secure token. - * @throws IOException exception thrown from token acquisition operations. */ - protected String getToken(Request request) throws IOException { + protected String getToken(Request request) { return token; } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentialsInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentialsInterceptor.java similarity index 78% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentialsInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentialsInterceptor.java index c1251e2c3..2ce4abb49 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/credentials/TokenCredentialsInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/TokenCredentialsInterceptor.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.credentials; +package com.microsoft.bot.restclient.credentials; import okhttp3.Interceptor; import okhttp3.Request; @@ -19,7 +16,7 @@ final class TokenCredentialsInterceptor implements Interceptor { /** * The credentials instance to apply to the HTTP client pipeline. */ - private TokenCredentials credentials; + private final TokenCredentials credentials; /** * Initialize a TokenCredentialsFilter class with a diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/package-info.java new file mode 100644 index 000000000..17ac009b9 --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/credentials/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +/** + * The package provides 2 basic credential classes that work with AutoRest + * generated clients for authentication purposes. + */ +package com.microsoft.bot.restclient.credentials; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/BaseUrlHandler.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/BaseUrlHandler.java similarity index 89% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/BaseUrlHandler.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/BaseUrlHandler.java index 36cb6db7a..099f70f18 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/BaseUrlHandler.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/BaseUrlHandler.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; import okhttp3.HttpUrl; import okhttp3.Interceptor; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/CustomHeadersInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/CustomHeadersInterceptor.java similarity index 89% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/CustomHeadersInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/CustomHeadersInterceptor.java index bd7b257a0..2eb4c6cb8 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/CustomHeadersInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/CustomHeadersInterceptor.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; import okhttp3.Headers; import okhttp3.Interceptor; @@ -33,13 +30,13 @@ public Map> headers() { /** * A mapping of custom headers. */ - private Map> headers; + private final Map> headers; /** * Initialize an instance of {@link CustomHeadersInterceptor} class. */ public CustomHeadersInterceptor() { - headers = new HashMap>(); + headers = new HashMap<>(); } /** @@ -62,7 +59,7 @@ public CustomHeadersInterceptor(String key, String value) { * @return the interceptor instance itself. */ public CustomHeadersInterceptor replaceHeader(String name, String value) { - this.headers.put(name, new ArrayList()); + this.headers.put(name, new ArrayList<>()); this.headers.get(name).add(value); return this; } @@ -77,7 +74,7 @@ public CustomHeadersInterceptor replaceHeader(String name, String value) { */ public CustomHeadersInterceptor addHeader(String name, String value) { if (!this.headers.containsKey(name)) { - this.headers.put(name, new ArrayList()); + this.headers.put(name, new ArrayList<>()); } this.headers.get(name).add(value); return this; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/LoggingInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/LoggingInterceptor.java similarity index 93% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/LoggingInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/LoggingInterceptor.java index 1d6945e12..7b11988df 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/LoggingInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/LoggingInterceptor.java @@ -1,16 +1,14 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.base.Joiner; import com.google.common.io.CharStreams; -import com.microsoft.bot.rest.LogLevel; +import com.microsoft.bot.restclient.LogLevel; +import java.nio.charset.StandardCharsets; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.Request; @@ -89,7 +87,7 @@ public Response intercept(Chain chain) throws IOException { Buffer buffer = new Buffer(); request.body().writeTo(buffer); - Charset charset = Charset.forName("UTF8"); + Charset charset = StandardCharsets.UTF_8; MediaType contentType = request.body().contentType(); if (contentType != null) { charset = contentType.charset(charset); @@ -101,7 +99,7 @@ public Response intercept(Chain chain) throws IOException { try { content = MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString( MAPPER.readValue(content, JsonNode.class)); - } catch (Exception ignore) { + } catch (Exception ignore) { //NOPMD // swallow, keep original content } } @@ -151,7 +149,7 @@ public Response intercept(Chain chain) throws IOException { source.request(Long.MAX_VALUE); // Buffer the entire body. Buffer buffer = source.buffer(); - Charset charset = Charset.forName("UTF8"); + Charset charset = StandardCharsets.UTF_8; MediaType contentType = responseBody.contentType(); if (contentType != null) { try { @@ -182,7 +180,7 @@ public Response intercept(Chain chain) throws IOException { try { content = MAPPER.writerWithDefaultPrettyPrinter() .writeValueAsString(MAPPER.readValue(content, JsonNode.class)); - } catch (Exception ignore) { + } catch (Exception ignore) { //NOPMD // swallow, keep original content } } @@ -209,7 +207,7 @@ public LoggingInterceptor withLogLevel(LogLevel logLevel) { return this; } - private static boolean isPlaintext(Buffer buffer) throws EOFException { + private static boolean isPlaintext(Buffer buffer) { try { Buffer prefix = new Buffer(); long byteCount = buffer.size() < 64 ? buffer.size() : 64; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/RequestIdHeaderInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/RequestIdHeaderInterceptor.java similarity index 77% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/RequestIdHeaderInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/RequestIdHeaderInterceptor.java index b89f119be..7c0549039 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/RequestIdHeaderInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/RequestIdHeaderInterceptor.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; import okhttp3.Interceptor; import okhttp3.Request; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/UserAgentInterceptor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/UserAgentInterceptor.java similarity index 90% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/UserAgentInterceptor.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/UserAgentInterceptor.java index 4399b66d8..6dcc7a2dc 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/UserAgentInterceptor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/UserAgentInterceptor.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; import okhttp3.Interceptor; import okhttp3.Request; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java similarity index 60% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/package-info.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java index 9d7089306..76526b810 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/interceptors/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java @@ -1,4 +1,4 @@ /** * The package contains default interceptors for making HTTP requests. */ -package com.microsoft.bot.rest.interceptors; +package com.microsoft.bot.restclient.interceptors; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java similarity index 86% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/package-info.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java index 6e3503479..b6b410a6d 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java @@ -3,4 +3,4 @@ * clients to compile and function. To learn more about AutoRest generator, * see https://github.com/azure/autorest. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/Environment.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/Environment.java similarity index 72% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/Environment.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/Environment.java index 2670c9eac..6e8f64c21 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/Environment.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/Environment.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.protocol; +package com.microsoft.bot.restclient.protocol; /** * An collection of endpoints in a region or a cloud. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/ResponseBuilder.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/ResponseBuilder.java similarity index 93% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/ResponseBuilder.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/ResponseBuilder.java index 2a5abe120..9492f6f81 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/ResponseBuilder.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/ResponseBuilder.java @@ -1,14 +1,11 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.protocol; +package com.microsoft.bot.restclient.protocol; -import com.microsoft.bot.rest.RestException; -import com.microsoft.bot.rest.ServiceResponse; -import com.microsoft.bot.rest.ServiceResponseWithHeaders; +import com.microsoft.bot.restclient.RestException; +import com.microsoft.bot.restclient.ServiceResponse; +import com.microsoft.bot.restclient.ServiceResponseWithHeaders; import okhttp3.ResponseBody; import retrofit2.Response; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/SerializerAdapter.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/SerializerAdapter.java similarity index 88% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/SerializerAdapter.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/SerializerAdapter.java index 27391b3aa..dc28358d6 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/SerializerAdapter.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/SerializerAdapter.java @@ -1,12 +1,9 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.protocol; +package com.microsoft.bot.restclient.protocol; -import com.microsoft.bot.rest.CollectionFormat; +import com.microsoft.bot.restclient.CollectionFormat; import retrofit2.Converter; import java.io.IOException; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java similarity index 73% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/package-info.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java index ea3be602c..0c47c4a00 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/protocol/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java @@ -2,4 +2,4 @@ * The package contains classes that interfaces defining the behaviors * of the necessary components of a Rest Client. */ -package com.microsoft.bot.rest.protocol; +package com.microsoft.bot.restclient.protocol; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/ExponentialBackoffRetryStrategy.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/ExponentialBackoffRetryStrategy.java similarity index 92% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/ExponentialBackoffRetryStrategy.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/ExponentialBackoffRetryStrategy.java index 5a8edc131..f04bf6b18 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/ExponentialBackoffRetryStrategy.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/ExponentialBackoffRetryStrategy.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.retry; +package com.microsoft.bot.restclient.retry; import okhttp3.Response; @@ -32,15 +29,15 @@ public final class ExponentialBackoffRetryStrategy extends RetryStrategy { * The value that will be used to calculate a random delta in the exponential delay * between retries. */ - private final int deltaBackoff; + private final int deltaBackoff; //NOPMD /** * The maximum backoff time. */ - private final int maxBackoff; + private final int maxBackoff; //NOPMD /** * The minimum backoff time. */ - private final int minBackoff; + private final int minBackoff; //NOPMD /** * The maximum number of retry attempts. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryHandler.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryHandler.java similarity index 90% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryHandler.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryHandler.java index 5739d0426..feeda4d58 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryHandler.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryHandler.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.retry; +package com.microsoft.bot.restclient.retry; import okhttp3.Interceptor; import okhttp3.Request; @@ -37,7 +34,7 @@ public final class RetryHandler implements Interceptor { /** * The retry strategy to use. */ - private RetryStrategy retryStrategy; + private final RetryStrategy retryStrategy; /** * @return the strategy used by this handler diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryStrategy.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryStrategy.java similarity index 89% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryStrategy.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryStrategy.java index 60df99339..5a8d3aaa6 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/RetryStrategy.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/RetryStrategy.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.retry; +package com.microsoft.bot.restclient.retry; import okhttp3.Response; @@ -31,13 +28,13 @@ public abstract class RetryStrategy { /** * The name of the retry strategy. */ - private String name; + private final String name; /** * The value indicating whether the first retry attempt will be made immediately, * whereas subsequent retries will remain subject to the retry interval. */ - private boolean fastFirstRetry; + private final boolean fastFirstRetry; /** * Initializes a new instance of the {@link RetryStrategy} class. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java similarity index 72% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/package-info.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java index 7e80702a5..65d0fe5aa 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/retry/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java @@ -2,4 +2,4 @@ * The package contains classes that define the retry behaviors when an error * occurs during a REST call. */ -package com.microsoft.bot.rest.retry; +package com.microsoft.bot.restclient.retry; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesDeserializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java similarity index 94% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesDeserializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java index e0b0ddc53..ce3021a55 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesDeserializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonFactory; @@ -106,7 +103,7 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE } // put into additional properties - root.put("additionalProperties", copy); + root.set("additionalProperties", copy); JsonParser parser = new JsonFactory().createParser(root.toString()); parser.nextToken(); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesSerializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesSerializer.java similarity index 95% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesSerializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesSerializer.java index 8f87f52ac..a34089836 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/AdditionalPropertiesSerializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesSerializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.core.JsonGenerator; @@ -113,7 +110,7 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi fields = extraProperties.fields(); while (fields.hasNext()) { Entry field = fields.next(); - root.put(field.getKey(), field.getValue()); + root.set(field.getKey(), field.getValue()); } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/Base64UrlSerializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/Base64UrlSerializer.java similarity index 79% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/Base64UrlSerializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/Base64UrlSerializer.java index 4c0a8035a..8a3f39657 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/Base64UrlSerializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/Base64UrlSerializer.java @@ -1,16 +1,13 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.SerializerProvider; import com.fasterxml.jackson.databind.module.SimpleModule; -import com.microsoft.bot.rest.Base64Url; +import com.microsoft.bot.restclient.Base64Url; import java.io.IOException; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/ByteArraySerializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/ByteArraySerializer.java similarity index 83% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/ByteArraySerializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/ByteArraySerializer.java index bb68694e5..37efe77e2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/ByteArraySerializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/ByteArraySerializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningDeserializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningDeserializer.java similarity index 93% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningDeserializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningDeserializer.java index 9b6b0cd10..26206dac0 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningDeserializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningDeserializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -91,9 +88,7 @@ public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, Typ JsonNode currentJsonNode = mapper.readTree(jp); final Class tClass = this.defaultDeserializer.handledType(); for (Class c : TypeToken.of(tClass).getTypes().classes().rawTypes()) { - if (c.isAssignableFrom(Object.class)) { - continue; - } else { + if (!c.isAssignableFrom(Object.class)) { final JsonTypeInfo typeInfo = c.getAnnotation(JsonTypeInfo.class); if (typeInfo != null) { String typeId = typeInfo.property(); @@ -101,7 +96,7 @@ public Object deserializeWithType(JsonParser jp, DeserializationContext cxt, Typ final String typeIdOnWire = unescapeEscapedDots(typeId); JsonNode typeIdValue = ((ObjectNode) currentJsonNode).remove(typeIdOnWire); if (typeIdValue != null) { - ((ObjectNode) currentJsonNode).put(typeId, typeIdValue); + ((ObjectNode) currentJsonNode).set(typeId, typeIdValue); } } } @@ -123,9 +118,7 @@ public Object deserialize(JsonParser jp, DeserializationContext cxt) throws IOEx } final Class tClass = this.defaultDeserializer.handledType(); for (Class c : TypeToken.of(tClass).getTypes().classes().rawTypes()) { - if (c.isAssignableFrom(Object.class)) { - continue; - } else { + if (!c.isAssignableFrom(Object.class)) { for (Field classField : c.getDeclaredFields()) { handleFlatteningForField(classField, currentJsonNode); } @@ -154,7 +147,7 @@ private static void handleFlatteningForField(Field classField, JsonNode jsonNode final String jsonPropValue = jsonProperty.value(); if (containsFlatteningDots(jsonPropValue)) { JsonNode childJsonNode = findNestedNode(jsonNode, jsonPropValue); - ((ObjectNode) jsonNode).put(jsonPropValue, childJsonNode); + ((ObjectNode) jsonNode).set(jsonPropValue, childJsonNode); } } } @@ -220,7 +213,7 @@ private static String unescapeEscapedDots(String key) { * @return true if at least one dot found */ private static boolean containsDot(String str) { - return str != null && str != "" && str.contains("."); + return str != null && str.length() > 0 && str.contains("."); } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningSerializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningSerializer.java similarity index 94% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningSerializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningSerializer.java index 7ce216591..13bfc5b9e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/FlatteningSerializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/FlatteningSerializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.BeanDescription; @@ -89,7 +86,7 @@ public JsonSerializer modifySerializer(SerializationConfig config, BeanDescri } private List getAllDeclaredFields(Class clazz) { - List fields = new ArrayList(); + List fields = new ArrayList<>(); while (clazz != null && !clazz.equals(Object.class)) { for (Field f : clazz.getDeclaredFields()) { int mod = f.getModifiers(); @@ -163,8 +160,8 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi // BFS for all collapsed properties ObjectNode root = mapper.valueToTree(value); ObjectNode res = root.deepCopy(); - Queue source = new LinkedBlockingQueue(); - Queue target = new LinkedBlockingQueue(); + Queue source = new LinkedBlockingQueue<>(); + Queue target = new LinkedBlockingQueue<>(); source.add(root); target.add(res); while (!source.isEmpty()) { @@ -190,7 +187,7 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi node = (ObjectNode) node.get(val); } else { ObjectNode child = new ObjectNode(JsonNodeFactory.instance); - node.put(val, child); + node.set(val, child); node = child; } } @@ -202,7 +199,7 @@ public void serialize(Object value, JsonGenerator jgen, SerializerProvider provi // String originalKey = key.replaceAll("\\\\.", "."); resCurrent.remove(key); - resCurrent.put(originalKey, outNode); + resCurrent.set(originalKey, outNode); } if (field.getValue() instanceof ObjectNode) { source.add((ObjectNode) field.getValue()); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/HeadersSerializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/HeadersSerializer.java similarity index 83% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/HeadersSerializer.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/HeadersSerializer.java index fcb9a7a8a..a92a0058e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/HeadersSerializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/HeadersSerializer.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.core.JsonGenerator; import com.fasterxml.jackson.databind.JsonSerializer; @@ -35,7 +32,7 @@ public static SimpleModule getModule() { @Override public void serialize(Headers value, JsonGenerator jgen, SerializerProvider provider) throws IOException { - Map headers = new HashMap(); + Map headers = new HashMap<>(); for (Map.Entry> entry : value.toMultimap().entrySet()) { if (entry.getValue() != null && entry.getValue().size() == 1) { headers.put(entry.getKey(), entry.getValue().get(0)); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonAdapter.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java similarity index 94% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonAdapter.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java index 6bd7f788a..a8a7f4048 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonAdapter.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonAdapter.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonInclude; @@ -18,8 +15,8 @@ import com.fasterxml.jackson.module.paramnames.ParameterNamesModule; import com.google.common.base.CharMatcher; import com.google.common.base.Joiner; -import com.microsoft.bot.rest.CollectionFormat; -import com.microsoft.bot.rest.protocol.SerializerAdapter; +import com.microsoft.bot.restclient.CollectionFormat; +import com.microsoft.bot.restclient.protocol.SerializerAdapter; import java.io.IOException; import java.io.StringWriter; @@ -131,7 +128,7 @@ public T deserialize(String value, final Type type) throws IOException { if (value == null || value.isEmpty()) { return null; } - return (T) serializer().readValue(value, constructJavaType(type)); + return serializer().readValue(value, constructJavaType(type)); } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonConverterFactory.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonConverterFactory.java similarity index 80% rename from libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonConverterFactory.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonConverterFactory.java index fd65df61a..45eea1321 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/rest/serializer/JacksonConverterFactory.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/JacksonConverterFactory.java @@ -1,10 +1,7 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. -package com.microsoft.bot.rest.serializer; +package com.microsoft.bot.restclient.serializer; import com.fasterxml.jackson.databind.JavaType; import com.fasterxml.jackson.databind.ObjectMapper; @@ -50,7 +47,7 @@ private JacksonConverterFactory(ObjectMapper mapper) { @Override public Converter responseBodyConverter(Type type, Annotation[] annotations, Retrofit retrofit) { JavaType javaType = mapper.getTypeFactory().constructType(type); - ObjectReader reader = mapper.reader(javaType); + ObjectReader reader = mapper.readerFor(javaType); return new JacksonResponseBodyConverter<>(reader); } @@ -66,7 +63,7 @@ public Converter requestBodyConverter(Type type, * * @param type of request object */ - final class JacksonRequestBodyConverter implements Converter { + static final class JacksonRequestBodyConverter implements Converter { /** Jackson object writer. */ private final ObjectWriter adapter; @@ -85,7 +82,7 @@ final class JacksonRequestBodyConverter implements Converter * * @param the expected object type to convert to */ - final class JacksonResponseBodyConverter implements Converter { + static final class JacksonResponseBodyConverter implements Converter { /** Jackson object reader. */ private final ObjectReader adapter; @@ -94,16 +91,8 @@ final class JacksonResponseBodyConverter implements Converter serializerAdapter = new AzureJacksonAdapter(); - - String bodyString = - "{" + - " \"name\":\"1431219a-acad-4d70-9a17-f8b7a5a143cb\",\"status\":\"InProgress\"" + - "}"; - - AzureAsyncOperation asyncOperation = serializerAdapter.deserialize(bodyString, AzureAsyncOperation.class); - - Assert.assertEquals("InProgress", asyncOperation.status()); - Assert.assertEquals(null, asyncOperation.getError()); - - Exception e = null; - asyncOperation = null; - bodyString = - "{" + - " \"name\":\"1431219a-acad-4d70-9a17-f8b7a5a143cb\",\"status\":\"InProgress\"," + - " \"error\":{" + - " }" + - "}"; - try { - asyncOperation = serializerAdapter.deserialize(bodyString, AzureAsyncOperation.class); - } catch (Exception ex) { - e = ex; - } - - Assert.assertNull(e); - Assert.assertEquals("InProgress", asyncOperation.status()); - CloudError error = asyncOperation.getError(); - Assert.assertNotNull(error); - Assert.assertNull(error.message()); - Assert.assertNull(error.code()); - - asyncOperation = null; - bodyString = - "{" + - " \"name\":\"1431219a-acad-4d70-9a17-f8b7a5a143cb\",\"status\":\"InProgress\"," + - " \"error\":{" + - " \"code\":\"None\",\"message\":null,\"target\":\"e1a19fd1-8110-470a-a82f-9f789c2b2917\"" + - " }" + - "}"; - - asyncOperation = serializerAdapter.deserialize(bodyString, AzureAsyncOperation.class); - Assert.assertEquals("InProgress", asyncOperation.status()); - error = asyncOperation.getError(); - Assert.assertNotNull(error); - Assert.assertEquals("None", error.code()); - Assert.assertNull(error.message()); - Assert.assertEquals("e1a19fd1-8110-470a-a82f-9f789c2b2917", error.target()); - } -} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/CloudErrorDeserializerTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/CloudErrorDeserializerTests.java deleted file mode 100644 index 34ef6d194..000000000 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/CloudErrorDeserializerTests.java +++ /dev/null @@ -1,176 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.microsoft.bot.azure.PolicyViolation; -import com.microsoft.bot.azure.serializer.AzureJacksonAdapter; -import com.microsoft.bot.azure.CloudError; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import org.junit.Assert; -import org.junit.Test; - -public class CloudErrorDeserializerTests { - @Test - public void cloudErrorDeserialization() throws Exception { - SerializerAdapter serializerAdapter = new AzureJacksonAdapter(); - String bodyString = - "{" + - " \"error\": {" + - " \"code\": \"BadArgument\"," + - " \"message\": \"The provided database ‘foo’ has an invalid username.\"," + - " \"target\": \"query\"," + - " \"details\": [" + - " {" + - " \"code\": \"301\"," + - " \"target\": \"$search\"," + - " \"message\": \"$search query option not supported\"" + - " }" + - " ]," + - " \"additionalInfo\": [" + - " {" + - " \"type\": \"SomeErrorType\"," + - " \"info\": {" + - " \"someProperty\": \"SomeValue\"" + - " }" + - " }" + - " ]" + - " }" + - "}"; - - CloudError cloudError = serializerAdapter.deserialize(bodyString, CloudError.class); - - Assert.assertEquals("BadArgument", cloudError.code()); - Assert.assertEquals("The provided database ‘foo’ has an invalid username.", cloudError.message()); - Assert.assertEquals("query", cloudError.target()); - Assert.assertEquals(1, cloudError.details().size()); - Assert.assertEquals("301", cloudError.details().get(0).code()); - Assert.assertEquals(1, cloudError.additionalInfo().size()); - Assert.assertEquals("SomeErrorType", cloudError.additionalInfo().get(0).type()); - Assert.assertEquals("SomeValue", cloudError.additionalInfo().get(0).info().get("someProperty").asText()); - } - - @Test - public void cloudErrorWithPolicyViolationDeserialization() throws Exception { - SerializerAdapter serializerAdapter = new AzureJacksonAdapter(); - String bodyString = - "{" + - " \"error\": {" + - " \"code\": \"BadArgument\"," + - " \"message\": \"The provided database ‘foo’ has an invalid username.\"," + - " \"target\": \"query\"," + - " \"details\": [" + - " {" + - " \"code\": \"301\"," + - " \"target\": \"$search\"," + - " \"message\": \"$search query option not supported\"," + - " \"additionalInfo\": [" + - " {" + - " \"type\": \"PolicyViolation\"," + - " \"info\": {" + - " \"policyDefinitionDisplayName\": \"Allowed locations\"," + - " \"policyDefinitionId\": \"testDefinitionId\"," + - " \"policyDefinitionName\": \"testDefinitionName\"," + - " \"policyDefinitionEffect\": \"deny\"," + - " \"policyAssignmentId\": \"testAssignmentId\"," + - " \"policyAssignmentName\": \"testAssignmentName\"," + - " \"policyAssignmentDisplayName\": \"test assignment\"," + - " \"policyAssignmentScope\": \"/subscriptions/testSubId/resourceGroups/jilimpolicytest2\"," + - " \"policyAssignmentParameters\": {" + - " \"listOfAllowedLocations\": {" + - " \"value\": [" + - " \"westus\"" + - " ]" + - " }" + - " }" + - " }" + - " }" + - " ]" + - " }" + - " ]," + - " \"additionalInfo\": [" + - " {" + - " \"type\": \"SomeErrorType\"," + - " \"info\": {" + - " \"someProperty\": \"SomeValue\"" + - " }" + - " }" + - " ]" + - " }" + - "}"; - - CloudError cloudError = serializerAdapter.deserialize(bodyString, CloudError.class); - - Assert.assertEquals("BadArgument", cloudError.code()); - Assert.assertEquals("The provided database ‘foo’ has an invalid username.", cloudError.message()); - Assert.assertEquals("query", cloudError.target()); - Assert.assertEquals(1, cloudError.details().size()); - Assert.assertEquals("301", cloudError.details().get(0).code()); - Assert.assertEquals(1, cloudError.additionalInfo().size()); - Assert.assertEquals("SomeErrorType", cloudError.additionalInfo().get(0).type()); - Assert.assertEquals("SomeValue", cloudError.additionalInfo().get(0).info().get("someProperty").asText()); - Assert.assertEquals(1, cloudError.details().get(0).additionalInfo().size()); - Assert.assertTrue(cloudError.details().get(0).additionalInfo().get(0) instanceof PolicyViolation); - - PolicyViolation policyViolation = (PolicyViolation)cloudError.details().get(0).additionalInfo().get(0); - - Assert.assertEquals("PolicyViolation", policyViolation.type()); - Assert.assertEquals("Allowed locations", policyViolation.policyErrorInfo().getPolicyDefinitionDisplayName()); - Assert.assertEquals("westus", policyViolation.policyErrorInfo().getPolicyAssignmentParameters().get("listOfAllowedLocations").getValue().elements().next().asText()); - } - - @Test - public void cloudErrorWitDifferentCasing() throws Exception { - SerializerAdapter serializerAdapter = new AzureJacksonAdapter(); - String bodyString = - "{" + - " \"error\": {" + - " \"Code\": \"BadArgument\"," + - " \"Message\": \"The provided database ‘foo’ has an invalid username.\"," + - " \"Target\": \"query\"," + - " \"Details\": [" + - " {" + - " \"Code\": \"301\"," + - " \"Target\": \"$search\"," + - " \"Message\": \"$search query option not supported\"," + - " \"AdditionalInfo\": [" + - " {" + - " \"Type\": \"PolicyViolation\"," + - " \"Info\": {" + - " \"PolicyDefinitionDisplayName\": \"Allowed locations\"," + - " \"PolicyAssignmentParameters\": {" + - " \"listOfAllowedLocations\": {" + - " \"Value\": [" + - " \"westus\"" + - " ]" + - " }" + - " }" + - " }" + - " }" + - " ]" + - " }" + - " ]" + - " }" + - "}"; - - CloudError cloudError = serializerAdapter.deserialize(bodyString, CloudError.class); - - Assert.assertEquals("BadArgument", cloudError.code()); - Assert.assertEquals("The provided database ‘foo’ has an invalid username.", cloudError.message()); - Assert.assertEquals("query", cloudError.target()); - Assert.assertEquals(1, cloudError.details().size()); - Assert.assertEquals("301", cloudError.details().get(0).code()); - Assert.assertEquals(1, cloudError.details().get(0).additionalInfo().size()); - Assert.assertTrue(cloudError.details().get(0).additionalInfo().get(0) instanceof PolicyViolation); - - PolicyViolation policyViolation = (PolicyViolation)cloudError.details().get(0).additionalInfo().get(0); - - Assert.assertEquals("PolicyViolation", policyViolation.type()); - Assert.assertEquals("Allowed locations", policyViolation.policyErrorInfo().getPolicyDefinitionDisplayName()); - Assert.assertEquals("westus", policyViolation.policyErrorInfo().getPolicyAssignmentParameters().get("listOfAllowedLocations").getValue().elements().next().asText()); - } -} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/PagedListTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/PagedListTests.java deleted file mode 100644 index c5ecd8f6e..000000000 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/PagedListTests.java +++ /dev/null @@ -1,298 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.azure.Page; -import com.microsoft.bot.azure.PagedList; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.ListIterator; - -public class PagedListTests { - private PagedList list; - - @Before - public void setupList() { - list = new PagedList(new TestPage(0, 21)) { - @Override - public Page nextPage(String nextPageLink) { - int pageNum = Integer.parseInt(nextPageLink); - return new TestPage(pageNum, 21); - } - }; - } - - @Test - public void sizeTest() { - Assert.assertEquals(20, list.size()); - } - - @Test - public void getTest() { - Assert.assertEquals(15, (int) list.get(15)); - } - - @Test - public void iterateTest() { - int j = 0; - for (int i : list) { - Assert.assertEquals(i, j++); - } - } - - @Test - public void removeTest() { - Integer i = list.get(10); - list.remove(10); - Assert.assertEquals(19, list.size()); - Assert.assertEquals(19, (int) list.get(18)); - } - - @Test - public void addTest() { - Integer i = list.get(10); - list.add(100); - Assert.assertEquals(21, list.size()); - Assert.assertEquals(100, (int) list.get(11)); - Assert.assertEquals(19, (int) list.get(20)); - } - - @Test - public void containsTest() { - Assert.assertTrue(list.contains(0)); - Assert.assertTrue(list.contains(3)); - Assert.assertTrue(list.contains(19)); - Assert.assertFalse(list.contains(20)); - } - - @Test - public void containsAllTest() { - List subList = new ArrayList<>(); - subList.addAll(Arrays.asList(0, 3, 19)); - Assert.assertTrue(list.containsAll(subList)); - subList.add(20); - Assert.assertFalse(list.containsAll(subList)); - } - - @Test - public void subListTest() { - List subList = list.subList(5, 15); - Assert.assertEquals(10, subList.size()); - Assert.assertTrue(list.containsAll(subList)); - Assert.assertEquals(7, (int) subList.get(2)); - } - - @Test - public void testIndexOf() { - Assert.assertEquals(15, list.indexOf(15)); - } - - @Test - public void testLastIndexOf() { - Assert.assertEquals(15, list.lastIndexOf(15)); - } - - - @Test - public void testIteratorWithListSizeInvocation() { - ListIterator itr = list.listIterator(); - list.size(); - int j = 0; - while (itr.hasNext()) { - Assert.assertEquals(j++, (long) itr.next()); - } - } - - @Test - public void testIteratorPartsWithSizeInvocation() { - ListIterator itr = list.listIterator(); - int j = 0; - while (j < 5) { - Assert.assertTrue(itr.hasNext()); - Assert.assertEquals(j++, (long) itr.next()); - } - list.size(); - while (j < 10) { - Assert.assertTrue(itr.hasNext()); - Assert.assertEquals(j++, (long) itr.next()); - } - } - - @Test - public void testIteratorWithLoadNextPageInvocation() { - ListIterator itr = list.listIterator(); - int j = 0; - while (j < 5) { - Assert.assertTrue(itr.hasNext()); - Assert.assertEquals(j++, (long) itr.next()); - } - list.loadNextPage(); - while (j < 10) { - Assert.assertTrue(itr.hasNext()); - Assert.assertEquals(j++, (long) itr.next()); - } - list.loadNextPage(); - while (itr.hasNext()) { - Assert.assertEquals(j++, (long) itr.next()); - } - Assert.assertEquals(20, j); - } - - @Test - public void testIteratorOperations() { - ListIterator itr1 = list.listIterator(); - IllegalStateException expectedException = null; - try { - itr1.remove(); - } catch (IllegalStateException ex) { - expectedException = ex; - } - Assert.assertNotNull(expectedException); - - ListIterator itr2 = list.listIterator(); - Assert.assertTrue(itr2.hasNext()); - Assert.assertEquals(0, (long) itr2.next()); - itr2.remove(); - Assert.assertTrue(itr2.hasNext()); - Assert.assertEquals(1, (long) itr2.next()); - - itr2.set(100); - Assert.assertTrue(itr2.hasPrevious()); - Assert.assertEquals(100, (long) itr2.previous()); - Assert.assertTrue(itr2.hasNext()); - Assert.assertEquals(100, (long) itr2.next()); - } - - @Test - public void testAddViaIteratorWhileIterating() { - ListIterator itr1 = list.listIterator(); - while (itr1.hasNext()) { - Integer val = itr1.next(); - if (val < 10) { - itr1.add(99); - } - } - Assert.assertEquals(30, list.size()); - } - - @Test - public void testRemoveViaIteratorWhileIterating() { - ListIterator itr1 = list.listIterator(); - while (itr1.hasNext()) { - itr1.next(); - itr1.remove(); - } - Assert.assertEquals(0, list.size()); - } - - @Test - public void canHandleIntermediateEmptyPage() { - List pagedList = new PagedList(new Page() { - @Override - public String nextPageLink() { - return "A"; - } - - @Override - public List items() { - List list = new ArrayList<>(); - list.add(1); - list.add(2); - return list; - } - }) { - @Override - public Page nextPage(String nextPageLink) { - if (nextPageLink == "A") { - return new Page() { - @Override - public String nextPageLink() { - return "B"; - } - - @Override - public List items() { - return new ArrayList<>(); // EMPTY PAGE - } - }; - } else if (nextPageLink == "B") { - return new Page() { - @Override - public String nextPageLink() { - return "C"; - } - - @Override - public List items() { - List list = new ArrayList<>(); - list.add(3); - list.add(4); - return list; - } - }; - } else if (nextPageLink == "C") { - return new Page() { - @Override - public String nextPageLink() { - return null; - } - - @Override - public List items() { - List list = new ArrayList<>(); - list.add(5); - list.add(6); - return list; - } - }; - } - throw new RuntimeException("nextPage should not be called after a page with next link as null"); - } - }; - ListIterator itr = pagedList.listIterator(); - int c = 1; - while (itr.hasNext()) { - Assert.assertEquals(c, (int) itr.next()); - c++; - } - Assert.assertEquals(7, c); - } - - public static class TestPage implements Page { - private int page; - private int max; - - public TestPage(int page, int max) { - this.page = page; - this.max = max; - } - - @Override - public String nextPageLink() { - if (page + 1 == max) { - return null; - } - return Integer.toString(page + 1); - } - - @Override - public List items() { - if (page + 1 != max) { - List items = new ArrayList<>(); - items.add(page); - return items; - } else { - return new ArrayList<>(); - } - } - } -} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/RequestIdHeaderInterceptorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/RequestIdHeaderInterceptorTests.java deleted file mode 100644 index 52529c584..000000000 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/azure/RequestIdHeaderInterceptorTests.java +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for - * license information. - */ - -package com.microsoft.bot.azure; - -import com.microsoft.bot.azure.serializer.AzureJacksonAdapter; -import com.microsoft.bot.azure.AzureResponseBuilder; -import com.microsoft.bot.azure.AzureServiceClient; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.interceptors.RequestIdHeaderInterceptor; -import com.microsoft.bot.rest.retry.RetryHandler; -import okhttp3.Interceptor; -import okhttp3.MediaType; -import okhttp3.Protocol; -import okhttp3.Request; -import okhttp3.Response; -import okhttp3.ResponseBody; -import org.junit.Assert; -import org.junit.Test; - -import java.io.IOException; - -public class RequestIdHeaderInterceptorTests { - private static final String REQUEST_ID_HEADER = "x-ms-client-request-id"; - - @Test - public void newRequestIdForEachCall() throws Exception { - RestClient restClient = new RestClient.Builder() - .withBaseUrl("http://localhost") - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()) - .withInterceptor(new RequestIdHeaderInterceptor()) - .withInterceptor(new Interceptor() { - private String firstRequestId = null; - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - if (request.header(REQUEST_ID_HEADER) != null) { - if (firstRequestId == null) { - firstRequestId = request.header(REQUEST_ID_HEADER); - return new Response.Builder() - .code(200) - .request(request) - .message("OK") - .protocol(Protocol.HTTP_1_1) - .body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks")) - .build(); - } else if (!request.header(REQUEST_ID_HEADER).equals(firstRequestId)) { - return new Response.Builder() - .code(200) - .request(request) - .message("OK") - .protocol(Protocol.HTTP_1_1) - .body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks")) - .build(); - } - } - return new Response.Builder().code(400).request(request) - .protocol(Protocol.HTTP_1_1).build(); - } - }) - .build(); - AzureServiceClient serviceClient = new AzureServiceClient(restClient) { }; - Response response = serviceClient.restClient().httpClient() - .newCall(new Request.Builder().get().url("http://localhost").build()).execute(); - Assert.assertEquals(200, response.code()); - response = serviceClient.restClient().httpClient() - .newCall(new Request.Builder().get().url("http://localhost").build()).execute(); - Assert.assertEquals(200, response.code()); - } - - @Test - public void sameRequestIdForRetry() throws Exception { - RestClient restClient = new RestClient.Builder() - .withBaseUrl("http://localhost") - .withSerializerAdapter(new AzureJacksonAdapter()) - .withResponseBuilderFactory(new AzureResponseBuilder.Factory()) - .withInterceptor(new RequestIdHeaderInterceptor()) - .withInterceptor(new RetryHandler()) - .withInterceptor(new Interceptor() { - private String firstRequestId = null; - - @Override - public Response intercept(Chain chain) throws IOException { - Request request = chain.request(); - if (request.header(REQUEST_ID_HEADER) != null) { - if (firstRequestId == null) { - firstRequestId = request.header(REQUEST_ID_HEADER); - return new Response.Builder() - .code(500) - .request(request) - .message("Error") - .protocol(Protocol.HTTP_1_1) - .body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks")) - .build(); - } else if (request.header(REQUEST_ID_HEADER).equals(firstRequestId)) { - return new Response.Builder() - .code(200) - .request(request) - .message("OK") - .protocol(Protocol.HTTP_1_1) - .body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks")) - .build(); - } - } - return new Response.Builder().code(400).request(request) - .protocol(Protocol.HTTP_1_1).build(); - } - }) - .build(); - AzureServiceClient serviceClient = new AzureServiceClient(restClient) { }; - Response response = serviceClient.restClient().httpClient() - .newCall(new Request.Builder().get().url("http://localhost").build()).execute(); - Assert.assertEquals(200, response.code()); - } -} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java index 9c54f382c..38f013ca3 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java @@ -1,6 +1,6 @@ package com.microsoft.bot.connector; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; import okhttp3.OkHttpClient; public class BotAccessTokenStub implements ServiceClientCredentials { diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java index ad6168090..9fa4f1ec7 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java @@ -3,7 +3,7 @@ import com.microsoft.bot.connector.base.TestBase; import com.microsoft.bot.connector.rest.RestConnectorClient; import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.rest.RestClient; +import com.microsoft.bot.restclient.RestClient; public class BotConnectorTestBase extends TestBase { protected ConnectorClient connector; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java index 30ce00d54..0af43a281 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java @@ -6,7 +6,7 @@ import com.microsoft.bot.connector.rest.RestConnectorClient; import com.microsoft.bot.connector.rest.RestOAuthClient; import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.rest.RestClient; +import com.microsoft.bot.restclient.RestClient; import java.io.IOException; import java.net.URISyntaxException; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java index 81a4dc9fb..6f42a4e21 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java @@ -1,13 +1,13 @@ package com.microsoft.bot.connector.base; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; -import com.microsoft.bot.rest.LogLevel; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.ServiceResponseBuilder; -import com.microsoft.bot.rest.credentials.ServiceClientCredentials; -import com.microsoft.bot.rest.credentials.TokenCredentials; -import com.microsoft.bot.rest.interceptors.LoggingInterceptor; -import com.microsoft.bot.rest.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.LogLevel; +import com.microsoft.bot.restclient.RestClient; +import com.microsoft.bot.restclient.ServiceResponseBuilder; +import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; +import com.microsoft.bot.restclient.credentials.TokenCredentials; +import com.microsoft.bot.restclient.interceptors.LoggingInterceptor; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import org.junit.*; import org.junit.rules.TestName; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AdditionalPropertiesSerializerTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AdditionalPropertiesSerializerTests.java similarity index 95% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AdditionalPropertiesSerializerTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AdditionalPropertiesSerializerTests.java index b10453c09..660b73b03 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AdditionalPropertiesSerializerTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AdditionalPropertiesSerializerTests.java @@ -4,11 +4,11 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.serializer.JacksonAdapter; -import com.microsoft.bot.rest.util.Foo; -import com.microsoft.bot.rest.util.FooChild; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.util.Foo; +import com.microsoft.bot.restclient.util.FooChild; import org.junit.Assert; import org.junit.Test; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalShelter.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java similarity index 88% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalShelter.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java index b8a0d9b5d..1d0a801f9 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalShelter.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java @@ -1,8 +1,8 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; -import com.microsoft.bot.rest.serializer.JsonFlatten; +import com.microsoft.bot.restclient.serializer.JsonFlatten; import java.util.List; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java similarity index 95% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java index 76cbb8199..f8039d710 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/AnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CatWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java similarity index 95% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CatWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java index 351e4370d..052d35162 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CatWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ComposeTurtles.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java similarity index 98% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ComposeTurtles.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java index 94ad33dd0..749bc8e84 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ComposeTurtles.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ConnectionPoolTests.java.dep b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ConnectionPoolTests.java.dep similarity index 100% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ConnectionPoolTests.java.dep rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ConnectionPoolTests.java.dep diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CredentialsTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CredentialsTests.java similarity index 93% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CredentialsTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CredentialsTests.java index 41c2ac06d..92ed4a4f4 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/CredentialsTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CredentialsTests.java @@ -4,11 +4,10 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.ServiceClient; -import com.microsoft.bot.rest.credentials.BasicAuthenticationCredentials; -import com.microsoft.bot.rest.credentials.TokenCredentials; +import com.microsoft.bot.restclient.credentials.BasicAuthenticationCredentials; +import com.microsoft.bot.restclient.credentials.TokenCredentials; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/DogWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java similarity index 96% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/DogWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java index 3c34c7908..463ccdbef 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/DogWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlattenableAnimalInfo.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java similarity index 94% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlattenableAnimalInfo.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java index bd46ac942..8fa997cbf 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlattenableAnimalInfo.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlatteningSerializerTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlatteningSerializerTests.java similarity index 99% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlatteningSerializerTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlatteningSerializerTests.java index 8f17ee141..f7aa67333 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/FlatteningSerializerTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlatteningSerializerTests.java @@ -4,13 +4,13 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.common.collect.ImmutableList; -import com.microsoft.bot.rest.serializer.JacksonAdapter; -import com.microsoft.bot.rest.serializer.JsonFlatten; -import com.microsoft.bot.rest.util.Foo; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.serializer.JsonFlatten; +import com.microsoft.bot.restclient.util.Foo; import org.junit.Assert; import org.junit.Test; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/NonEmptyAnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java similarity index 95% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/NonEmptyAnimalWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java index 01839de53..e4fded082 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/NonEmptyAnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RabbitWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java similarity index 96% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RabbitWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java index 1f7f66b86..4e05f6760 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RabbitWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RestClientTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RestClientTests.java similarity index 92% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RestClientTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RestClientTests.java index 7fb6addbd..d9f849618 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RestClientTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RestClientTests.java @@ -5,19 +5,14 @@ * */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.CollectionFormat; -import com.microsoft.bot.rest.LogLevel; -import com.microsoft.bot.rest.RestClient; -import com.microsoft.bot.rest.RestException; -import com.microsoft.bot.rest.ServiceResponseBuilder; -import com.microsoft.bot.rest.credentials.BasicAuthenticationCredentials; -import com.microsoft.bot.rest.credentials.TokenCredentials; -import com.microsoft.bot.rest.interceptors.UserAgentInterceptor; -import com.microsoft.bot.rest.protocol.ResponseBuilder; -import com.microsoft.bot.rest.protocol.SerializerAdapter; -import com.microsoft.bot.rest.serializer.JacksonAdapter; +import com.microsoft.bot.restclient.credentials.BasicAuthenticationCredentials; +import com.microsoft.bot.restclient.credentials.TokenCredentials; +import com.microsoft.bot.restclient.interceptors.UserAgentInterceptor; +import com.microsoft.bot.restclient.protocol.ResponseBuilder; +import com.microsoft.bot.restclient.protocol.SerializerAdapter; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; import okhttp3.Interceptor; import okhttp3.Response; import org.junit.Assert; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RetryHandlerTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RetryHandlerTests.java similarity index 96% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RetryHandlerTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RetryHandlerTests.java index 65d543ce5..73521b8e6 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/RetryHandlerTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RetryHandlerTests.java @@ -4,10 +4,9 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.ServiceClient; -import com.microsoft.bot.rest.retry.RetryHandler; +import com.microsoft.bot.restclient.retry.RetryHandler; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ServiceClientTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ServiceClientTests.java similarity index 96% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ServiceClientTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ServiceClientTests.java index 058129d25..c2330f8a2 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ServiceClientTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ServiceClientTests.java @@ -4,9 +4,8 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.ServiceClient; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/TurtleWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java similarity index 94% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/TurtleWithTypeIdContainingDot.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java index a04155635..ba4cdf40f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/TurtleWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java @@ -1,4 +1,4 @@ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonTypeInfo; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/UserAgentTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/UserAgentTests.java similarity index 95% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/UserAgentTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/UserAgentTests.java index 9325ddf1c..91fcc1cc7 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/UserAgentTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/UserAgentTests.java @@ -4,10 +4,9 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; -import com.microsoft.bot.rest.ServiceClient; -import com.microsoft.bot.rest.interceptors.UserAgentInterceptor; +import com.microsoft.bot.restclient.interceptors.UserAgentInterceptor; import okhttp3.Interceptor; import okhttp3.MediaType; import okhttp3.OkHttpClient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ValidatorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ValidatorTests.java similarity index 98% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ValidatorTests.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ValidatorTests.java index b074dfa80..d4ce8f47c 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/ValidatorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ValidatorTests.java @@ -4,13 +4,11 @@ * license information. */ -package com.microsoft.bot.rest; +package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.node.TextNode; -import com.microsoft.bot.rest.SkipParentValidation; -import com.microsoft.bot.rest.Validator; import org.junit.Assert; import org.junit.Test; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/Foo.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/Foo.java similarity index 91% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/Foo.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/Foo.java index f79a478e1..6c06f0c12 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/Foo.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/Foo.java @@ -4,13 +4,13 @@ * license information. */ -package com.microsoft.bot.rest.util; +package com.microsoft.bot.restclient.util; import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.microsoft.bot.rest.serializer.JsonFlatten; +import com.microsoft.bot.restclient.serializer.JsonFlatten; import java.util.List; import java.util.Map; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/FooChild.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/FooChild.java similarity index 83% rename from libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/FooChild.java rename to libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/FooChild.java index f12dcb9bb..f45a66cf0 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/rest/util/FooChild.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/util/FooChild.java @@ -4,11 +4,11 @@ * license information. */ -package com.microsoft.bot.rest.util; +package com.microsoft.bot.restclient.util; import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeName; -import com.microsoft.bot.rest.serializer.JsonFlatten; +import com.microsoft.bot.restclient.serializer.JsonFlatten; /** * Class for testing serialization. From 3dec9a9578f27abc6d41c08000adf271ec96273a Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Wed, 13 Jan 2021 15:37:43 -0300 Subject: [PATCH 039/221] [Samples] Add 17.multilingual-bot sample (#898) * Add deploymentTemplates folder * Add webapp folder * Add resources folder * Add translation folder * Add Startup and Bot files * Add welcomeCard * Add dependencies and documentation * Add test structure * Apply minor fixes due to parity * Apply internal feedback * Fix test errors during validation * Fix test errors * Fix error in Middleware * Apply feedback * Add missing this * Update sendWelcomeMessage method to send the first non-target member * Fix issues during testing of the sample * Fix translation error * Add component notation in MultiLingualBot * Add notation to ignore unknown properties * Fix error when trasnlationKey is empty Co-authored-by: Franco Alvarez <51216149+fran893@users.noreply.github.com> Co-authored-by: Victor Grycuk --- samples/17.multilingual-bot/LICENSE | 21 + samples/17.multilingual-bot/README.md | 85 ++++ .../new-rg-parameters.json | 42 ++ .../preexisting-rg-parameters.json | 39 ++ .../template-with-new-rg.json | 183 ++++++++ .../template-with-preexisting-rg.json | 154 +++++++ samples/17.multilingual-bot/pom.xml | 238 ++++++++++ .../bot/sample/multilingual/Application.java | 84 ++++ .../sample/multilingual/MultiLingualBot.java | 187 ++++++++ .../translation/MicrosoftTranslator.java | 96 ++++ .../translation/TranslationMiddleware.java | 126 ++++++ .../translation/TranslationSettings.java | 11 + .../translation/model/TranslatorResponse.java | 34 ++ .../translation/model/TranslatorResult.java | 49 ++ .../src/main/resources/application.properties | 4 + .../src/main/resources/cards/welcomeCard.json | 46 ++ .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/multilingual/ApplicationTest.java | 19 + 21 files changed, 1869 insertions(+) create mode 100644 samples/17.multilingual-bot/LICENSE create mode 100644 samples/17.multilingual-bot/README.md create mode 100644 samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json create mode 100644 samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/17.multilingual-bot/pom.xml create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java create mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java create mode 100644 samples/17.multilingual-bot/src/main/resources/application.properties create mode 100644 samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json create mode 100644 samples/17.multilingual-bot/src/main/resources/log4j2.json create mode 100644 samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/17.multilingual-bot/src/main/webapp/index.html create mode 100644 samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java diff --git a/samples/17.multilingual-bot/LICENSE b/samples/17.multilingual-bot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/17.multilingual-bot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/17.multilingual-bot/README.md b/samples/17.multilingual-bot/README.md new file mode 100644 index 000000000..16ddd7477 --- /dev/null +++ b/samples/17.multilingual-bot/README.md @@ -0,0 +1,85 @@ +# Multilingual Bot + +Bot Framework v4 multilingual bot sample + +This sample will present the user with a set of cards to pick their choice of language. The user can either change language by invoking the option cards, or by entering the language code (_en_/_es_). The bot will then acknowledge the selection. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to translate incoming and outgoing text using a custom middleware and the [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/). + +## Concepts introduced in this sample + +Translation Middleware: We create a translation middleware that can translate text from bot to user and from user to bot, allowing the creation of multi-lingual bots. + +The middleware is driven by user state. This means that users can specify their language preference, and the middleware automatically will intercept messages back and forth and present them to the user in their preferred language. + +Users can change their language preference anytime, and since this gets written to the user state, the middleware will read this state and instantly modify its behavior to honor the newly selected preferred language. + +The [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/), Microsoft Translator Text API is a cloud-based machine translation service. With this API you can translate text in near real-time from any app or service through a simple REST API call. +The API uses the most modern neural machine translation technology, as well as offering statistical machine translation technology. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. +- [Microsoft Translator Text API key](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup) + + To consume the Microsoft Translator Text API, first obtain a key following the instructions in the [Microsoft Translator Text API documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup). + + Paste the key in the `TranslatorKey` setting in the `application.properties` file. + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-multilingual-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +### Creating a custom middleware + +Translation Middleware: We create a translation middleware than can translate text from bot to user and from user to bot, allowing the creation of multilingual bots. +Users can specify their language preference, which is stored in the user state. The translation middleware translates to and from the user's preferred language. + +### Microsoft Translator Text API + +The [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/), Microsoft Translator Text API is a cloud-based machine translation service. With this API you can translate text in near real-time from any app or service through a simple REST API call. +The API uses the most modern neural machine translation technology, as well as offering statistical machine translation technology. + +## Deploy this bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +### Add `TranslatorKey` to Application Settings + +If you used the `application.properties` file to store your `TranslatorKey` then you'll need to add this key and its value to the Application Settings for your deployed bot. + +- Log into the [Azure portal](https://portal.azure.com) +- In the left nav, click on `Bot Services` +- Click the `` Name to display the bots Web App Settings +- Click the `Application Settings` +- Scroll to the `Application settings` section +- Click `+ Add new setting` +- Add the key `TranslatorKey` with a value of the Translator Text API `Authentication key` created from the steps above + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Bot State](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-storage-concept?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json b/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..ead339093 --- /dev/null +++ b/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json b/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..b6f5114fc --- /dev/null +++ b/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..3a0e81219 --- /dev/null +++ b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,183 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('appServicePlanName')]" + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..34a026819 --- /dev/null +++ b/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,154 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "properties": { + "name": "[variables('servicePlanName')]" + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "siteConfig": { + "appSettings": [ + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "10.14.1" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/17.multilingual-bot/pom.xml b/samples/17.multilingual-bot/pom.xml new file mode 100644 index 000000000..137e34808 --- /dev/null +++ b/samples/17.multilingual-bot/pom.xml @@ -0,0 +1,238 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-multilingual + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Multi-Lingual Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.multilingual.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.multilingual.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java new file mode 100644 index 000000000..c1052cd4b --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.multilingual.translation.MicrosoftTranslator; +import com.microsoft.bot.sample.multilingual.translation.TranslationMiddleware; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see MultiLingualBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + Storage storage = this.getStorage(); + ConversationState conversationState = this.getConversationState(storage); + + BotFrameworkHttpAdapter adapter = new AdapterWithErrorHandler(configuration, conversationState); + TranslationMiddleware translationMiddleware = this.getTranslationMiddleware(configuration); + adapter.use(translationMiddleware); + return adapter; + } + + /** + * Create the Microsoft Translator responsible for making calls to the Cognitive Services translation service. + * @param configuration The Configuration object to use. + * @return MicrosoftTranslator + */ + @Bean + public MicrosoftTranslator getMicrosoftTranslator(Configuration configuration) { + return new MicrosoftTranslator(configuration); + } + + /** + * Create the Translation Middleware that will be added to the middleware pipeline in the AdapterWithErrorHandler. + * @param configuration The Configuration object to use. + * @return TranslationMiddleware + */ + @Bean + public TranslationMiddleware getTranslationMiddleware(Configuration configuration) { + Storage storage = this.getStorage(); + UserState userState = this.getUserState(storage); + MicrosoftTranslator microsoftTranslator = this.getMicrosoftTranslator(configuration); + return new TranslationMiddleware(microsoftTranslator, userState); + } +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java new file mode 100644 index 000000000..d549c3f91 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java @@ -0,0 +1,187 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.google.common.base.Strings; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.SuggestedActions; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.Serialization; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +/** + * This bot demonstrates how to use Microsoft Translator. + * More information can be found + * here https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-info-overview. + */ +@Component +public class MultiLingualBot extends ActivityHandler { + private static final String WELCOME_TEXT = + new StringBuilder("This bot will introduce you to translation middleware. ") + .append("Say 'hi' to get started.").toString(); + + private static final String ENGLISH_ENGLISH = "en"; + private static final String ENGLISH_SPANISH = "es"; + private static final String SPANISH_ENGLISH = "in"; + private static final String SPANISH_SPANISH = "it"; + + private UserState userState; + private StatePropertyAccessor languagePreference; + + /** + * Creates a Multilingual bot. + * @param withUserState User state object. + */ + public MultiLingualBot(UserState withUserState) { + if (withUserState == null) { + throw new IllegalArgumentException("userState"); + } + this.userState = withUserState; + + this.languagePreference = userState.createProperty("LanguagePreference"); + } + + /** + * This method is executed when a user is joining to the conversation. + * @param membersAdded A list of all the members added to the conversation, + * as described by the conversation update activity. + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onMembersAdded(List membersAdded, + TurnContext turnContext) { + return MultiLingualBot.sendWelcomeMessage(turnContext); + } + + /** + * This method is executed when the turnContext receives a message activity. + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + if (MultiLingualBot.isLanguageChangeRequested(turnContext.getActivity().getText())) { + String currentLang = turnContext.getActivity().getText().toLowerCase(); + String lang = currentLang.equals(ENGLISH_ENGLISH) || currentLang.equals(SPANISH_ENGLISH) + ? ENGLISH_ENGLISH : ENGLISH_SPANISH; + + // If the user requested a language change through the suggested actions with values "es" or "en", + // simply change the user's language preference in the user state. + // The translation middleware will catch this setting and translate both ways to the user's + // selected language. + // If Spanish was selected by the user, the reply below will actually be shown in spanish to the user. + return languagePreference.set(turnContext, lang) + .thenCompose(task -> { + Activity reply = MessageFactory.text(String.format("Your current language code is: %s", lang)); + return turnContext.sendActivity(reply); + }) + // Save the user profile updates into the user state. + .thenCompose(task -> userState.saveChanges(turnContext, false)); + } else { + // Show the user the possible options for language. If the user chooses a different language + // than the default, then the translation middleware will pick it up from the user state and + // translate messages both ways, i.e. user to bot and bot to user. + Activity reply = MessageFactory.text("Choose your language:"); + CardAction esAction = new CardAction() { + { + setTitle("Español"); + setType(ActionTypes.POST_BACK); + setValue(ENGLISH_SPANISH); + } + }; + CardAction enAction = new CardAction() { + { + setTitle("English"); + setType(ActionTypes.POST_BACK); + setValue(ENGLISH_ENGLISH); + } + }; + List actions = new ArrayList<>(Arrays.asList(esAction, enAction)); + SuggestedActions suggestedActions = new SuggestedActions() { + { + setActions(actions); + } + }; + reply.setSuggestedActions(suggestedActions); + return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); + } + } + + private static CompletableFuture sendWelcomeMessage(TurnContext turnContext) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + Attachment welcomeCard = MultiLingualBot.createAdaptiveCardAttachment(); + Activity response = MessageFactory.attachment(welcomeCard); + return turnContext.sendActivity(response) + .thenCompose(task -> turnContext.sendActivity(MessageFactory.text(WELCOME_TEXT))); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + /** + * Load attachment from file. + * @return the welcome adaptive card + */ + private static Attachment createAdaptiveCardAttachment() { + // combine path for cross platform support + try ( + InputStream input = Thread.currentThread().getContextClassLoader() + .getResourceAsStream("cards/welcomeCard.json") + ) { + String adaptiveCardJson = IOUtils.toString(input, StandardCharsets.UTF_8.toString()); + + return new Attachment() {{ + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(Serialization.jsonToTree(adaptiveCardJson)); + }}; + } catch (IOException e) { + e.printStackTrace(); + return new Attachment(); + } + } + + /** + * Checks whether the utterance from the user is requesting a language change. + * In a production bot, we would use the Microsoft Text Translation API language + * detection feature, along with detecting language names. + * For the purpose of the sample, we just assume that the user requests language + * changes by responding with the language code through the suggested action presented + * above or by typing it. + * @param utterance utterance the current turn utterance. + * @return the utterance. + */ + private static Boolean isLanguageChangeRequested(String utterance) { + if (Strings.isNullOrEmpty(utterance)) { + return false; + } + + utterance = utterance.toLowerCase().trim(); + return utterance.equals(ENGLISH_SPANISH) || utterance.equals(ENGLISH_ENGLISH) + || utterance.equals(SPANISH_SPANISH) || utterance.equals(SPANISH_ENGLISH); + } +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java new file mode 100644 index 000000000..f4a2b347a --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual.translation; + +import java.io.Reader; +import java.io.StringReader; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.sample.multilingual.translation.model.TranslatorResponse; + +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.base.Strings; + +import org.slf4j.LoggerFactory; + +/** + * A helper class wrapper for the Microsoft Translator API. + */ +public class MicrosoftTranslator { + private static final String HOST = "https://api.cognitive.microsofttranslator.com"; + private static final String PATH = "/translate?api-version=3.0"; + private static final String URI_PARAMS = "&to="; + + private static String key; + + /** + * @param configuration The configuration class with the translator key stored. + */ + public MicrosoftTranslator(Configuration configuration) { + String translatorKey = configuration.getProperty("TranslatorKey"); + + if (translatorKey == null) { + throw new IllegalArgumentException("key"); + } + + MicrosoftTranslator.key = translatorKey; + } + + /** + * Helper method to translate text to a specified language. + * @param text Text that will be translated. + * @param targetLocale targetLocale Two character language code, e.g. "en", "es". + * @return The first translation result + */ + public CompletableFuture translate(String text, String targetLocale) { + return CompletableFuture.supplyAsync(() -> { + // From Cognitive Services translation documentation: + // https://docs.microsoft.com/en-us/azure/cognitive-services/Translator/quickstart-translator?tabs=java + String body = String.format("[{ \"Text\": \"%s\" }]", text); + + String uri = new StringBuilder(MicrosoftTranslator.HOST) + .append(MicrosoftTranslator.PATH) + .append(MicrosoftTranslator.URI_PARAMS) + .append(targetLocale).toString(); + + RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), body); + + OkHttpClient client = new OkHttpClient(); + Request request = new Request.Builder() + .url(uri) + .header("Ocp-Apim-Subscription-Key", MicrosoftTranslator.key) + .post(requestBody) + .build(); + + try { + Response response = client.newCall(request).execute(); + + if (!response.isSuccessful()) { + String message = new StringBuilder("The call to the translation service returned HTTP status code ") + .append(response.code()) + .append(".").toString(); + throw new Exception(message); + } + + ObjectMapper objectMapper = new ObjectMapper(); + Reader reader = new StringReader(response.body().string()); + TranslatorResponse[] result = objectMapper.readValue(reader, TranslatorResponse[].class); + + return result[0].getTranslations().get(0).getText(); + + } catch (Exception e) { + LoggerFactory.getLogger(MicrosoftTranslator.class).error("findPackages", e); + throw new CompletionException(e); + } + }); + } +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java new file mode 100644 index 000000000..b36653cdb --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual.translation; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import com.google.common.base.Strings; +import com.microsoft.bot.builder.Middleware; +import com.microsoft.bot.builder.NextDelegate; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +/** + * Middleware for translating text between the user and bot. + * Uses the Microsoft Translator Text API. + */ +public class TranslationMiddleware implements Middleware { + private MicrosoftTranslator translator; + private StatePropertyAccessor languageStateProperty; + + /** + * Initializes a new instance of the {@link TranslationMiddleware} class. + * @param withTranslator Translator implementation to be used for text translation. + * @param userState State property for current language. + */ + public TranslationMiddleware(MicrosoftTranslator withTranslator, UserState userState) { + if (withTranslator == null) { + throw new IllegalArgumentException("withTranslator"); + } + this.translator = withTranslator; + if (userState == null) { + throw new IllegalArgumentException("userState"); + } + + this.languageStateProperty = userState.createProperty("LanguagePreference"); + } + + /** + * Processes an incoming activity. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param next The delegate to call to continue the bot middleware pipeline. + * @return A Task representing the asynchronous operation. + */ + public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext"); + } + + return this.shouldTranslate(turnContext).thenCompose(translate -> { + if (translate) { + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + return this.translator.translate( + turnContext.getActivity().getText(), + TranslationSettings.DEFAULT_LANGUAGE) + .thenApply(text -> { + turnContext.getActivity().setText(text); + return CompletableFuture.completedFuture(null); + }); + } + } + return CompletableFuture.completedFuture(null); + }).thenCompose(task -> { + turnContext.onSendActivities((newContext, activities, nextSend) -> { + return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenCompose(userLanguage -> { + Boolean shouldTranslate = !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); + + // Translate messages sent to the user to user language + if (shouldTranslate) { + ArrayList> tasks = new ArrayList>(); + for (Activity activity : activities.stream().filter(a -> a.getType() == ActivityTypes.MESSAGE).collect(Collectors.toList())) { + tasks.add(this.translateMessageActivity(activity, userLanguage)); + } + + if (!Arrays.asList(tasks).isEmpty()) { + CompletableFuture.allOf(tasks.toArray(new CompletableFuture[tasks.size()])).join(); + } + } + + return nextSend.get(); + }); + }); + + turnContext.onUpdateActivity((newContext, activity, nextUpdate) -> { + return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenCompose(userLanguage -> { + Boolean shouldTranslate = !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); + + // Translate messages sent to the user to user language + if (activity.getType() == ActivityTypes.MESSAGE) { + if (shouldTranslate) { + this.translateMessageActivity(activity, userLanguage); + } + } + + return nextUpdate.get(); + }); + }); + + return next.next(); + }); + } + + private CompletableFuture translateMessageActivity(Activity activity, String targetLocale) { + if (activity.getType() == ActivityTypes.MESSAGE) { + return this.translator.translate(activity.getText(), targetLocale).thenAccept(text -> { + activity.setText(text); + }); + } + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture shouldTranslate(TurnContext turnContext) { + return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenApply(userLanguage -> { + if (Strings.isNullOrEmpty(userLanguage)) { + userLanguage = TranslationSettings.DEFAULT_LANGUAGE; + } + return !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); + }); + } +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java new file mode 100644 index 000000000..9f216d220 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java @@ -0,0 +1,11 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual.translation; + +/** + * General translation settings and constants. + */ +public class TranslationSettings { + public static final String DEFAULT_LANGUAGE = "en"; +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java new file mode 100644 index 000000000..c4f056eb8 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual.translation.model; + +import java.util.List; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Array of translated results from Translator API v3. + */ +@JsonIgnoreProperties(ignoreUnknown = true) +public class TranslatorResponse { + @JsonProperty("translations") + private List translations; + + /** + * Gets the translation results. + * @return A list of {@link TranslatorResult} + */ + public List getTranslations() { + return this.translations; + } + + /** + * Sets the translation results. + * @param withTranslations A list of {@link TranslatorResult} + */ + public void setTranslations(List withTranslations) { + this.translations = withTranslations; + } +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java new file mode 100644 index 000000000..6021ee88c --- /dev/null +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual.translation.model; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Translation result from Translator API v3. + */ +public class TranslatorResult { + @JsonProperty("text") + private String text; + + @JsonProperty("to") + private String to; + + /** + * Gets the translation result text. + * @return Translation result. + */ + public String getText() { + return this.text; + } + + /** + * Sets the translation result text. + * @param withText Translation result. + */ + public void setText(String withText) { + this.text = withText; + } + + /** + * Gets the target language locale. + * @return Locale. + */ + public String getTo() { + return this.to; + } + + /** + * Sets the target language locale. + * @param withTo Target locale. + */ + public void setTo(String withTo) { + this.to = withTo; + } +} diff --git a/samples/17.multilingual-bot/src/main/resources/application.properties b/samples/17.multilingual-bot/src/main/resources/application.properties new file mode 100644 index 000000000..bbbe15889 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/resources/application.properties @@ -0,0 +1,4 @@ +MicrosoftAppId= +MicrosoftAppPassword= +TranslatorKey= +server.port=3978 diff --git a/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json b/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json new file mode 100644 index 000000000..47e5614df --- /dev/null +++ b/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "Welcome to Bot Framework!", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": true, + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} \ No newline at end of file diff --git a/samples/17.multilingual-bot/src/main/resources/log4j2.json b/samples/17.multilingual-bot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml b/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/17.multilingual-bot/src/main/webapp/index.html b/samples/17.multilingual-bot/src/main/webapp/index.html new file mode 100644 index 000000000..c46330130 --- /dev/null +++ b/samples/17.multilingual-bot/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + Multi-lingual Bot Sample + + + + + +
+
+
+
Multi-lingual Bot Sample
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java b/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java new file mode 100644 index 000000000..18d372838 --- /dev/null +++ b/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multilingual; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From b9ba6fe1da4490ef2653672d3de3d1f7f9a512f4 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 14 Jan 2021 12:18:16 -0600 Subject: [PATCH 040/221] Now throwing on certificate decoding errors (#904) --- .../authentication/JwtTokenExtractor.java | 25 ++++++++----------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java index fcd2eedc9..5e564d324 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenExtractor.java @@ -10,6 +10,7 @@ import com.auth0.jwt.interfaces.Verification; import com.microsoft.bot.connector.ExecutorFactory; import java.io.ByteArrayInputStream; +import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Base64; @@ -156,12 +157,8 @@ private CompletableFuture validateToken( && key.certificateChain != null && key.certificateChain.size() > 0 ) { - // Note that decodeCertificate will return null if the cert could not - // be decoded. This would likely be the case if it were in an unexpected - // encoding. Going to err on the side of ignoring this check. - // May want to reconsider this and throw on null cert. X509Certificate cert = decodeCertificate(key.certificateChain.get(0)); - if (cert != null && !isCertValid(cert)) { + if (!isCertValid(cert)) { throw new JWTVerificationException("Signing certificate is not valid"); } } @@ -209,24 +206,24 @@ private CompletableFuture validateToken( } return new ClaimsIdentity(decodedJWT); - } catch (JWTVerificationException ex) { + } catch (JWTVerificationException | CertificateException ex) { LOGGER.warn(ex.getMessage()); throw new AuthenticationException(ex); } }, ExecutorFactory.getExecutor()); } - private X509Certificate decodeCertificate(String certStr) { - try { - byte[] decoded = Base64.getDecoder().decode(certStr); - return (X509Certificate) CertificateFactory - .getInstance("X.509").generateCertificate(new ByteArrayInputStream(decoded)); - } catch (Throwable t) { - return null; - } + private X509Certificate decodeCertificate(String certStr) throws CertificateException { + byte[] decoded = Base64.getDecoder().decode(certStr); + return (X509Certificate) CertificateFactory + .getInstance("X.509").generateCertificate(new ByteArrayInputStream(decoded)); } private boolean isCertValid(X509Certificate cert) { + if (cert == null) { + return false; + } + long now = new Date().getTime(); long clockskew = tokenValidationParameters.clockSkew.toMillis(); long startValid = cert.getNotBefore().getTime() - clockskew; From e4baf6f3abb9f7470d84cbcbda80497ce24a0eba Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 15 Jan 2021 09:07:18 -0600 Subject: [PATCH 041/221] Fixes Unauthorized error when calling ContinueConversation (#905) --- .../bot/builder/BotFrameworkAdapter.java | 40 +++++++++++-------- 1 file changed, 23 insertions(+), 17 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 9274bb97a..012d67fc7 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -286,10 +286,6 @@ public CompletableFuture continueConversation( ConversationReference reference, BotCallbackHandler callback ) { - if (StringUtils.isEmpty(botAppId)) { - throw new IllegalArgumentException("botAppId"); - } - if (reference == null) { throw new IllegalArgumentException("reference"); } @@ -298,14 +294,14 @@ public CompletableFuture continueConversation( throw new IllegalArgumentException("callback"); } + botAppId = botAppId == null ? "" : botAppId; + // Hand craft Claims Identity. - HashMap claims = new HashMap() { - { - // Adding claims for both Emulator and Channel. - put(AuthenticationConstants.AUDIENCE_CLAIM, botAppId); - put(AuthenticationConstants.APPID_CLAIM, botAppId); - } - }; + // Adding claims for both Emulator and Channel. + HashMap claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, botAppId); + claims.put(AuthenticationConstants.APPID_CLAIM, botAppId); + ClaimsIdentity claimsIdentity = new ClaimsIdentity("ExternalBearer", claims); String audience = getBotFrameworkOAuthScope(); @@ -382,12 +378,22 @@ public CompletableFuture continueConversation( context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); context.getTurnState().add(OAUTH_SCOPE_KEY, audience); - pipelineResult = createConnectorClient( - reference.getServiceUrl(), claimsIdentity, audience - ).thenCompose(connectorClient -> { - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - return runPipeline(context, callback); - }); + String appIdFromClaims = JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims()); + return credentialProvider.isValidAppId(appIdFromClaims) + .thenCompose(isValidAppId -> { + // If we receive a valid app id in the incoming token claims, add the + // channel service URL to the trusted services list so we can send messages back. + if (!StringUtils.isEmpty(appIdFromClaims) && isValidAppId) { + AppCredentials.trustServiceUrl(reference.getServiceUrl()); + } + + return createConnectorClient( + reference.getServiceUrl(), claimsIdentity, audience + ).thenCompose(connectorClient -> { + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + return runPipeline(context, callback); + }); + }); } catch (Exception e) { pipelineResult.completeExceptionally(e); } From 34aed86ad99138b2b029e60561b3d662630893d4 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 15 Jan 2021 11:01:53 -0600 Subject: [PATCH 042/221] Add state to AppBasedLinkQuery for OAuth flow (#906) --- .../bot/schema/teams/AppBasedLinkQuery.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/AppBasedLinkQuery.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/AppBasedLinkQuery.java index 665f36dd3..ad1b01427 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/AppBasedLinkQuery.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/AppBasedLinkQuery.java @@ -12,6 +12,9 @@ public class AppBasedLinkQuery { @JsonProperty(value = "url") private String url; + @JsonProperty(value = "state") + private String state; + /** * Initializes a new empty instance of the AppBasedLinkQuery class. */ @@ -45,4 +48,22 @@ public String getUrl() { public void setUrl(String withUrl) { url = withUrl; } + + /** + * Gets the magic code for OAuth Flow. + * + * @return The state + */ + public String getState() { + return state; + } + + /** + * Sets the magic code for OAuth Flow. + * + * @param withState The state. + */ + public void setState(String withState) { + state = withState; + } } From 0890c421b4f3070a4738c922e592201d0c750b59 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 21 Jan 2021 09:13:59 -0600 Subject: [PATCH 043/221] Bump jackson-databind from 2.9.10.5 to 2.9.10.7 (#911) Bumps [jackson-databind](https://github.com/FasterXML/jackson) from 2.9.10.5 to 2.9.10.7. - [Release notes](https://github.com/FasterXML/jackson/releases) - [Commits](https://github.com/FasterXML/jackson/commits) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index c36db0cb2..c1d6d2b91 100644 --- a/pom.xml +++ b/pom.xml @@ -264,7 +264,7 @@ com.fasterxml.jackson.core jackson-databind - 2.9.10.5 + 2.9.10.7 From 41b3ff7ea8ec6b54b49114baa43de04197dbf273 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 25 Jan 2021 13:07:15 -0600 Subject: [PATCH 044/221] Refactored Retry to be CompletableFuture friendly (#913) * Refactored Retry to be CompletableFuture friendly * Corrected CheckStyle warnings. --- .../bot/connector/authentication/Retry.java | 52 +++++++++++-------- .../connector/authentication/RetryParams.java | 10 ++-- .../microsoft/bot/connector/RetryTests.java | 15 ++++-- 3 files changed, 44 insertions(+), 33 deletions(-) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/Retry.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/Retry.java index 12332dbb2..064dec5c3 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/Retry.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/Retry.java @@ -4,11 +4,10 @@ package com.microsoft.bot.connector.authentication; -import com.microsoft.bot.connector.ExecutorFactory; - import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; import java.util.function.BiFunction; import java.util.function.Supplier; @@ -24,7 +23,7 @@ private Retry() { /** * Runs a task with retry. - * + * * @param task The task to run. * @param retryExceptionHandler Called when an exception happens. * @param The type of the result. @@ -36,41 +35,50 @@ public static CompletableFuture run( Supplier> task, BiFunction retryExceptionHandler ) { + return runInternal(task, retryExceptionHandler, 1, new ArrayList<>()); + } - CompletableFuture result = new CompletableFuture<>(); + private static CompletableFuture runInternal( + Supplier> task, + BiFunction retryExceptionHandler, + final Integer retryCount, + final List exceptions + ) { + AtomicReference retry = new AtomicReference<>(); - ExecutorFactory.getExecutor().execute(() -> { - RetryParams retry = RetryParams.stopRetrying(); - List exceptions = new ArrayList<>(); - int currentRetryCount = 0; + return task.get() + .exceptionally((t) -> { + exceptions.add(t); + retry.set(retryExceptionHandler.apply(new RetryException(t), retryCount)); + return null; + }) + .thenCompose(taskResult -> { + CompletableFuture result = new CompletableFuture<>(); - do { - try { - result.complete(task.get().join()); - } catch (Throwable t) { - exceptions.add(t); - retry = retryExceptionHandler.apply(new RetryException(t), currentRetryCount); + if (retry.get() == null) { + result.complete(taskResult); + return result; } - if (retry.getShouldRetry()) { - currentRetryCount++; + if (retry.get().getShouldRetry()) { try { - Thread.sleep(withBackoff(retry.getRetryAfter(), currentRetryCount)); + Thread.sleep(withBackOff(retry.get().getRetryAfter(), retryCount)); } catch (InterruptedException e) { throw new RetryException(e); } + + return runInternal(task, retryExceptionHandler, retryCount + 1, exceptions); } - } while (retry.getShouldRetry()); - result.completeExceptionally(new RetryException("Exceeded retry count", exceptions)); - }); + result.completeExceptionally(new RetryException("Exceeded retry count", exceptions)); - return result; + return result; + }); } private static final double BACKOFF_MULTIPLIER = 1.1; - private static long withBackoff(long delay, int retryCount) { + private static long withBackOff(long delay, int retryCount) { double result = delay * Math.pow(BACKOFF_MULTIPLIER, retryCount - 1); return (long) Math.min(result, Long.MAX_VALUE); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java index 975132bb2..899c5225a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java @@ -10,7 +10,7 @@ * State for Retry. */ public class RetryParams { - private static final int MAX_RETRIES = 10; + public static final int MAX_RETRIES = 10; private static final Duration MAX_DELAY = Duration.ofSeconds(10); private static final Duration DEFAULT_BACKOFF_TIME = Duration.ofMillis(50); @@ -23,11 +23,9 @@ public class RetryParams { * @return A RetryParams that returns false for {@link #getShouldRetry()}. */ public static RetryParams stopRetrying() { - return new RetryParams() { - { - setShouldRetry(false); - } - }; + return new RetryParams() {{ + setShouldRetry(false); + }}; } /** diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java index a4ae0b77e..2eb8d711b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java @@ -16,7 +16,7 @@ public void Retry_NoRetryWhenTaskSucceeds() { exceptionToThrow = null; }}; - String result = Retry.run(() -> + Retry.run(() -> faultyClass.faultyTask(), ((e, integer) -> faultyClass.exceptionHandler(e, integer))) .join(); @@ -32,8 +32,8 @@ public void Retry_RetryThenSucceed() { triesUntilSuccess = 3; }}; - String result = Retry.run(() -> - faultyClass.faultyTask(), + Retry.run(() -> + faultyClass.faultyTask(), ((e, integer) -> faultyClass.exceptionHandler(e, integer))) .join(); @@ -50,11 +50,14 @@ public void Retry_RetryUntilFailure() { try { Retry.run(() -> - faultyClass.faultyTask(), + faultyClass.faultyTask(), ((e, integer) -> faultyClass.exceptionHandler(e, integer))) .join(); + Assert.fail("Should have thrown a RetryException because it exceeded max retry"); } catch (CompletionException e) { Assert.assertTrue(e.getCause() instanceof RetryException); + Assert.assertEquals(RetryParams.MAX_RETRIES, faultyClass.callCount); + Assert.assertTrue(RetryParams.MAX_RETRIES == ((RetryException) e.getCause()).getExceptions().size()); } } @@ -69,7 +72,9 @@ CompletableFuture faultyTask() { callCount++; if (callCount < triesUntilSuccess && exceptionToThrow != null) { - throw exceptionToThrow; + CompletableFuture result = new CompletableFuture<>(); + result.completeExceptionally(exceptionToThrow); + return result; } return CompletableFuture.completedFuture(null); From aa1ea57fe88b1c01f97ed009a610a27202898ef8 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 26 Jan 2021 21:07:34 -0600 Subject: [PATCH 045/221] Proper exception throwing withing CompletableFuture methods. (#916) --- .../bot/builder/ActivityHandler.java | 13 +- .../com/microsoft/bot/builder/BotAdapter.java | 5 +- .../bot/builder/BotFrameworkAdapter.java | 128 ++++++++-------- .../com/microsoft/bot/builder/BotState.java | 98 +++++++------ .../microsoft/bot/builder/MemoryStorage.java | 20 +-- .../bot/builder/MemoryTranscriptStore.java | 22 ++- .../bot/builder/PrivateConversationState.java | 2 +- .../bot/builder/RecognizerResult.java | 3 +- .../bot/builder/ShowTypingMiddleware.java | 3 +- .../builder/TelemetryLoggerMiddleware.java | 5 +- .../bot/builder/TraceTranscriptLogger.java | 6 +- .../bot/builder/TurnContextImpl.java | 31 ++-- .../builder/TurnContextStateCollection.java | 6 +- .../com/microsoft/bot/builder/UserState.java | 2 +- .../microsoft/bot/builder/BotStateTests.java | 37 ++--- .../bot/builder/TranscriptBaseTests.java | 25 ++-- .../com/microsoft/bot/connector}/Async.java | 138 +++++++++++------- .../bot/connector}/ThrowSupplier.java | 38 ++--- .../authentication/EndorsementsValidator.java | 4 +- .../EnterpriseChannelValidation.java | 5 +- .../authentication/JwtTokenValidation.java | 11 +- .../bot/connector/rest/RestAttachments.java | 14 +- .../bot/connector/rest/RestBotSignIn.java | 11 +- .../bot/connector/rest/RestConversations.java | 131 +++++++++-------- .../connector/rest/RestTeamsOperations.java | 9 +- .../bot/connector/rest/RestUserToken.java | 96 +++++++----- .../bot/connector/ConversationsTest.java | 80 +++++----- .../connector/JwtTokenValidationTests.java | 4 +- .../bot/connector/OAuthConnectorTests.java | 27 ++-- .../java/com/microsoft/bot/schema/Entity.java | 3 +- 30 files changed, 560 insertions(+), 417 deletions(-) rename libraries/{bot-integration-core/src/main/java/com/microsoft/bot/integration => bot-connector/src/main/java/com/microsoft/bot/connector}/Async.java (50%) rename libraries/{bot-integration-core/src/main/java/com/microsoft/bot/integration => bot-connector/src/main/java/com/microsoft/bot/connector}/ThrowSupplier.java (87%) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index 7946e8f7b..f44d4aee0 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.connector.Async; import java.net.HttpURLConnection; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -53,17 +54,21 @@ public class ActivityHandler implements Bot { @Override public CompletableFuture onTurn(TurnContext turnContext) { if (turnContext == null) { - throw new IllegalArgumentException("TurnContext cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } if (turnContext.getActivity() == null) { - throw new IllegalArgumentException("turnContext must have a non-null Activity."); + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext must have a non-null Activity." + )); } if (turnContext.getActivity().getType() == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "turnContext.getActivity must have a non-null Type." - ); + )); } switch (turnContext.getActivity().getType()) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index 7a4a4cf55..2d2006e6b 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.authentication.ClaimsIdentity; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ConversationReference; @@ -187,7 +188,9 @@ protected CompletableFuture runPipeline( TurnContext context, BotCallbackHandler callback ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } // Call any registered Middleware Components looking for ReceiveActivity() if (context.getActivity() != null) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 012d67fc7..dc07f0ad9 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.microsoft.bot.builder.integration.AdapterIntegration; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.Channels; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; @@ -154,7 +155,6 @@ public BotFrameworkAdapter( RetryStrategy withRetryStrategy, Middleware withMiddleware ) { - this( withCredentialProvider, new AuthenticationConfiguration(), @@ -181,7 +181,6 @@ public BotFrameworkAdapter( RetryStrategy withRetryStrategy, Middleware withMiddleware ) { - if (withCredentialProvider == null) { throw new IllegalArgumentException("CredentialProvider cannot be null"); } @@ -226,7 +225,6 @@ public BotFrameworkAdapter( RetryStrategy withRetryStrategy, Middleware withMiddleware ) { - if (withCredentials == null) { throw new IllegalArgumentException("credentials"); } @@ -287,11 +285,11 @@ public CompletableFuture continueConversation( BotCallbackHandler callback ) { if (reference == null) { - throw new IllegalArgumentException("reference"); + return Async.completeExceptionally(new IllegalArgumentException("reference")); } if (callback == null) { - throw new IllegalArgumentException("callback"); + return Async.completeExceptionally(new IllegalArgumentException("callback")); } botAppId = botAppId == null ? "" : botAppId; @@ -303,7 +301,6 @@ public CompletableFuture continueConversation( claims.put(AuthenticationConstants.APPID_CLAIM, botAppId); ClaimsIdentity claimsIdentity = new ClaimsIdentity("ExternalBearer", claims); - String audience = getBotFrameworkOAuthScope(); return continueConversation(claimsIdentity, reference, audience, callback); @@ -356,19 +353,21 @@ public CompletableFuture continueConversation( BotCallbackHandler callback ) { if (claimsIdentity == null) { - throw new IllegalArgumentException("claimsIdentity"); + return Async.completeExceptionally(new IllegalArgumentException("claimsIdentity")); } if (reference == null) { - throw new IllegalArgumentException("reference"); + return Async.completeExceptionally(new IllegalArgumentException("reference")); } if (callback == null) { - throw new IllegalArgumentException("callback"); + return Async.completeExceptionally(new IllegalArgumentException("callback")); } if (StringUtils.isEmpty(audience)) { - throw new IllegalArgumentException("audience cannot be null or empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "audience cannot be null or empty" + )); } CompletableFuture pipelineResult = new CompletableFuture<>(); @@ -434,7 +433,9 @@ public CompletableFuture processActivity( Activity activity, BotCallbackHandler callback ) { - BotAssert.activityNotNull(activity); + if (activity == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } return JwtTokenValidation.authenticateRequest( activity, authHeader, credentialProvider, channelProvider, authConfiguration @@ -460,7 +461,9 @@ public CompletableFuture processActivity( Activity activity, BotCallbackHandler callback ) { - BotAssert.activityNotNull(activity); + if (activity == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } CompletableFuture pipelineResult = new CompletableFuture<>(); @@ -552,17 +555,17 @@ public CompletableFuture sendActivities( List activities ) { if (context == null) { - throw new IllegalArgumentException("context"); + return Async.completeExceptionally(new IllegalArgumentException("context")); } if (activities == null) { - throw new IllegalArgumentException("activities"); + return Async.completeExceptionally(new IllegalArgumentException("activities")); } if (activities.size() == 0) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Expecting one or more activities, but the array was empty." - ); + )); } return CompletableFuture.supplyAsync(() -> { @@ -690,15 +693,15 @@ public CompletableFuture deleteConversationMember( String memberId ) { if (context.getActivity().getConversation() == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.deleteConversationMember(): missing conversation" - ); + )); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.deleteConversationMember(): missing conversation.id" - ); + )); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -735,15 +738,15 @@ public CompletableFuture> getActivityMembers( } if (context.getActivity().getConversation() == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.GetActivityMembers(): missing conversation" - ); + )); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.GetActivityMembers(): missing conversation.id" - ); + )); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -760,15 +763,15 @@ public CompletableFuture> getActivityMembers( */ public CompletableFuture> getConversationMembers(TurnContextImpl context) { if (context.getActivity().getConversation() == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.GetActivityMembers(): missing conversation" - ); + )); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.GetActivityMembers(): missing conversation.id" - ); + )); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -823,11 +826,11 @@ public CompletableFuture getConversations( String continuationToken ) { if (StringUtils.isEmpty(serviceUrl)) { - throw new IllegalArgumentException("serviceUrl"); + return Async.completeExceptionally(new IllegalArgumentException("serviceUrl")); } if (credentials == null) { - throw new IllegalArgumentException("credentials"); + return Async.completeExceptionally(new IllegalArgumentException("credentials")); } return getOrCreateConnectorClient(serviceUrl, credentials) @@ -893,28 +896,27 @@ public CompletableFuture getUserToken( String connectionName, String magicCode ) { - BotAssert.contextNotNull(context); - + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } if ( context.getActivity().getFrom() == null || StringUtils.isEmpty(context.getActivity().getFrom().getId()) ) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "BotFrameworkAdapter.getUserToken(): missing from or from.id" - ); + )); } if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName"); + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken() - .getToken( - context.getActivity().getFrom().getId(), connectionName, - context.getActivity().getChannelId(), magicCode - ); - }); + return createOAuthClient(context, null).thenCompose(oAuthClient -> oAuthClient.getUserToken() + .getToken( + context.getActivity().getFrom().getId(), connectionName, + context.getActivity().getChannelId(), magicCode + )); } /** @@ -931,9 +933,12 @@ public CompletableFuture getOauthSignInLink( TurnContext context, String connectionName ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName"); + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } return createOAuthClient(context, null).thenCompose(oAuthClient -> { @@ -988,12 +993,14 @@ public CompletableFuture getOauthSignInLink( String userId, String finalRedirect ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName"); + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } if (StringUtils.isEmpty(userId)) { - throw new IllegalArgumentException("userId"); + return Async.completeExceptionally(new IllegalArgumentException("userId")); } return createOAuthClient(context, null).thenCompose(oAuthClient -> { @@ -1044,9 +1051,11 @@ public CompletableFuture signOutUser( String connectionName, String userId ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName"); + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } return createOAuthClient(context, null).thenCompose(oAuthClient -> { @@ -1075,9 +1084,11 @@ public CompletableFuture> getTokenStatus( String userId, String includeFilter ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } if (StringUtils.isEmpty(userId)) { - throw new IllegalArgumentException("userId"); + return Async.completeExceptionally(new IllegalArgumentException("userId")); } return createOAuthClient(context, null).thenCompose(oAuthClient -> { @@ -1107,13 +1118,14 @@ public CompletableFuture> getAadTokens( String[] resourceUrls, String userId ) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName"); + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } - if (resourceUrls == null) { - throw new IllegalArgumentException("resourceUrls"); + return Async.completeExceptionally(new IllegalArgumentException("resourceUrls")); } return createOAuthClient(context, null).thenCompose(oAuthClient -> { @@ -1348,9 +1360,9 @@ private CompletableFuture createConnectorClient( String audience ) { if (claimsIdentity == null) { - throw new UnsupportedOperationException( + return Async.completeExceptionally(new UnsupportedOperationException( "ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off." - ); + )); } // For requests from channel App Id is in Audience claim of JWT token. For @@ -1477,7 +1489,7 @@ private CompletableFuture getAppCredentials(String appId, String }); } - private String getBotAppId(TurnContext turnContext) { + private String getBotAppId(TurnContext turnContext) throws IllegalStateException { ClaimsIdentity botIdentity = turnContext.getTurnState().get(BOT_IDENTITY_KEY); if (botIdentity == null) { throw new IllegalStateException( diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java index 9552de981..e0261da7c 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.bot.connector.Async; import org.apache.commons.lang3.StringUtils; import java.util.HashMap; @@ -42,8 +43,9 @@ public abstract class BotState implements PropertyManager { * * @param withStorage The storage provider to use. * @param withContextServiceKey The key for the state cache for this BotState. + * @throws IllegalArgumentException Null Storage or empty service key arguments. */ - public BotState(Storage withStorage, String withContextServiceKey) { + public BotState(Storage withStorage, String withContextServiceKey) throws IllegalArgumentException { if (withStorage == null) { throw new IllegalArgumentException("Storage cannot be null"); } @@ -62,8 +64,9 @@ public BotState(Storage withStorage, String withContextServiceKey) { * @param name name of property. * @param type of property. * @return A {@link StatePropertyAccessor} for the property. + * @throws IllegalArgumentException Empty name */ - public StatePropertyAccessor createProperty(String name) { + public StatePropertyAccessor createProperty(String name) throws IllegalArgumentException { if (StringUtils.isEmpty(name)) { throw new IllegalArgumentException("name cannot be empty"); } @@ -92,24 +95,26 @@ public CompletableFuture load(TurnContext turnContext) { * @return A task that represents the work queued to execute. */ public CompletableFuture load(TurnContext turnContext, boolean force) { - if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); - } + return Async.tryCompletable(() -> { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null"); + } - CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); - String storageKey = getStorageKey(turnContext); - if (force || cachedState == null || cachedState.getState() == null) { - return storage.read(new String[] {storageKey}).thenApply(val -> { - turnContext.getTurnState() - .replace( - contextServiceKey, - new CachedBotState((Map) val.get(storageKey)) - ); - return null; - }); - } + CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); + String storageKey = getStorageKey(turnContext); + if (force || cachedState == null || cachedState.getState() == null) { + return storage.read(new String[]{storageKey}).thenApply(val -> { + turnContext.getTurnState() + .replace( + contextServiceKey, + new CachedBotState((Map) val.get(storageKey)) + ); + return null; + }); + } - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture(null); + }); } /** @@ -132,26 +137,28 @@ public CompletableFuture saveChanges(TurnContext turnContext) { * @return A task that represents the work queued to execute. */ public CompletableFuture saveChanges(TurnContext turnContext, boolean force) { - if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); - } + return Async.tryCompletable(() -> { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null"); + } - CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); - if (force || cachedState != null && cachedState.isChanged()) { - String storageKey = getStorageKey(turnContext); - Map changes = new HashMap() { - { - put(storageKey, cachedState.state); - } - }; - - return storage.write(changes).thenApply(val -> { - cachedState.setHash(cachedState.computeHash(cachedState.state)); - return null; - }); - } + CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); + if (force || cachedState != null && cachedState.isChanged()) { + String storageKey = getStorageKey(turnContext); + Map changes = new HashMap() { + { + put(storageKey, cachedState.state); + } + }; - return CompletableFuture.completedFuture(null); + return storage.write(changes).thenApply(val -> { + cachedState.setHash(cachedState.computeHash(cachedState.state)); + return null; + }); + } + + return CompletableFuture.completedFuture(null); + }); } /** @@ -219,8 +226,9 @@ public JsonNode get(TurnContext turnContext) { * * @param turnContext The context object for this turn. * @return The storage key. + * @throws IllegalArgumentException TurnContext doesn't contain all the required data. */ - public abstract String getStorageKey(TurnContext turnContext); + public abstract String getStorageKey(TurnContext turnContext) throws IllegalArgumentException; /** * Gets the value of a property from the state cache for this BotState. @@ -236,16 +244,22 @@ protected CompletableFuture getPropertyValue( String propertyName ) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); } if (StringUtils.isEmpty(propertyName)) { - throw new IllegalArgumentException("propertyName cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "propertyName cannot be empty" + )); } - CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); - return (CompletableFuture) CompletableFuture - .completedFuture(cachedState.getState().get(propertyName)); + return Async.tryCompletable(() -> { + CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); + return (CompletableFuture) CompletableFuture + .completedFuture(cachedState.getState().get(propertyName)); + }); } /** diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryStorage.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryStorage.java index 1f2f84d5e..dcf74bafc 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryStorage.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryStorage.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.connector.Async; import org.apache.commons.lang3.StringUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -83,7 +84,7 @@ public MemoryStorage(Map values) { @Override public CompletableFuture> read(String[] keys) { if (keys == null) { - throw new IllegalArgumentException("keys cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException("keys cannot be null")); } Map storeItems = new ConcurrentHashMap<>(keys.length); @@ -96,10 +97,10 @@ public CompletableFuture> read(String[] keys) { // Check if type info is set for the class if (!(stateNode.hasNonNull(TYPENAMEFORNONENTITY))) { logger.error("Read failed: Type info not present for " + key); - throw new RuntimeException( + return Async.completeExceptionally(new RuntimeException( String .format("Read failed: Type info not present for key " + key) - ); + )); } String clsName = stateNode.get(TYPENAMEFORNONENTITY).textValue(); @@ -109,18 +110,18 @@ public CompletableFuture> read(String[] keys) { cls = Class.forName(clsName); } catch (ClassNotFoundException e) { logger.error("Read failed: Could not load class {}", clsName); - throw new RuntimeException( + return Async.completeExceptionally(new RuntimeException( String.format("Read failed: Could not load class %s", clsName) - ); + )); } // Populate dictionary storeItems.put(key, objectMapper.treeToValue(stateNode, cls)); } catch (JsonProcessingException e) { logger.error("Read failed: {}", e.toString()); - throw new RuntimeException( + return Async.completeExceptionally(new RuntimeException( String.format("Read failed: %s", e.toString()) - ); + )); } } } @@ -164,13 +165,12 @@ public CompletableFuture write(Map changes) { oldStateETag != null && !StringUtils.equals(newStoreItem.getETag(), "*") && !StringUtils.equals(newStoreItem.getETag(), oldStateETag) ) { - String msg = String.format( "eTag conflict. Original: %s, Current: %s", newStoreItem.getETag(), oldStateETag ); logger.error(msg); - throw new RuntimeException(msg); + return Async.completeExceptionally(new RuntimeException(msg)); } int newTag = eTag++; ((ObjectNode) newState).put("eTag", Integer.toString(newTag)); @@ -192,7 +192,7 @@ public CompletableFuture write(Map changes) { @Override public CompletableFuture delete(String[] keys) { if (keys == null) { - throw new IllegalArgumentException("keys cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException("keys cannot be null")); } synchronized (this.syncroot) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java index 224fbf8c5..3bdb35abf 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MemoryTranscriptStore.java @@ -4,6 +4,7 @@ package com.microsoft.bot.builder; import com.codepoetics.protonpack.StreamUtils; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import java.time.OffsetDateTime; @@ -49,7 +50,8 @@ public class MemoryTranscriptStore implements TranscriptStore { @Override public final CompletableFuture logActivity(Activity activity) { if (activity == null) { - throw new IllegalArgumentException("activity cannot be null for LogActivity()"); + return Async.completeExceptionally( + new IllegalArgumentException("activity cannot be null for LogActivity()")); } synchronized (sync) { @@ -96,11 +98,13 @@ public CompletableFuture> getTranscriptActivities( OffsetDateTime startDate ) { if (channelId == null) { - throw new IllegalArgumentException(String.format("missing %1$s", "channelId")); + return Async.completeExceptionally( + new IllegalArgumentException(String.format("missing %1$s", "channelId"))); } if (conversationId == null) { - throw new IllegalArgumentException(String.format("missing %1$s", "conversationId")); + return Async.completeExceptionally( + new IllegalArgumentException(String.format("missing %1$s", "conversationId"))); } PagedResult pagedResult = new PagedResult<>(); @@ -147,15 +151,15 @@ public CompletableFuture> getTranscriptActivities( @Override public CompletableFuture deleteTranscript(String channelId, String conversationId) { if (channelId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( String.format("%1$s should not be null", "channelId") - ); + )); } if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( String.format("%1$s should not be null", "conversationId") - ); + )); } synchronized (sync) { @@ -182,7 +186,9 @@ public CompletableFuture> listTranscripts( String continuationToken ) { if (channelId == null) { - throw new IllegalArgumentException(String.format("missing %1$s", "channelId")); + return Async.completeExceptionally(new IllegalArgumentException(String.format( + "missing %1$s", "channelId" + ))); } PagedResult pagedResult = new PagedResult<>(); diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/PrivateConversationState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/PrivateConversationState.java index 43a35533b..0dc2570c8 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/PrivateConversationState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/PrivateConversationState.java @@ -26,7 +26,7 @@ public PrivateConversationState(Storage storage) { * @return The storage key. */ @Override - public String getStorageKey(TurnContext turnContext) { + public String getStorageKey(TurnContext turnContext) throws IllegalArgumentException { if (turnContext.getActivity() == null) { throw new IllegalArgumentException("invalid activity"); } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java index b8804be8e..b0579d519 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java @@ -37,9 +37,10 @@ public class RecognizerResult implements RecognizerConvert { * Return the top scoring intent and its score. * * @return The top scoring intent and score. + * @throws IllegalArgumentException No intents available. */ @JsonIgnore - public IntentScore getTopScoringIntent() { + public IntentScore getTopScoringIntent() throws IllegalArgumentException { if (getIntents() == null) { throw new IllegalArgumentException("RecognizerResult.Intents cannot be null"); } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java index 228cf6095..bf490d079 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java @@ -48,8 +48,9 @@ public ShowTypingMiddleware() { * * @param withDelay Initial delay before sending first typing indicator. * @param withPeriod Rate at which additional typing indicators will be sent. + * @throws IllegalArgumentException delay and period must be greater than zero */ - public ShowTypingMiddleware(long withDelay, long withPeriod) { + public ShowTypingMiddleware(long withDelay, long withPeriod) throws IllegalArgumentException { if (withDelay < 0) { throw new IllegalArgumentException("Delay must be greater than or equal to zero"); } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java index df9aa5605..27f8d1e42 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java @@ -4,6 +4,7 @@ package com.microsoft.bot.builder; import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.Channels; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; @@ -69,7 +70,9 @@ public BotTelemetryClient getTelemetryClient() { */ @Override public CompletableFuture onTurn(TurnContext context, NextDelegate next) { - BotAssert.contextNotNull(context); + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } // log incoming activity at beginning of turn return onReceiveActivity(context.getActivity()).thenCompose(receiveResult -> { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java index 0b9b26c56..73858a0e5 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TraceTranscriptLogger.java @@ -6,6 +6,7 @@ import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -38,7 +39,10 @@ public class TraceTranscriptLogger implements TranscriptLogger { */ @Override public CompletableFuture logActivity(Activity activity) { - BotAssert.activityNotNull(activity); + if (activity == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } + String event = null; try { event = mapper.writeValueAsString(activity); diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 342439d07..050c311a0 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ConversationReference; @@ -269,7 +270,7 @@ public CompletableFuture sendActivity( InputHints inputHint ) { if (StringUtils.isEmpty(textReplyToSend)) { - throw new IllegalArgumentException("textReplyToSend"); + return Async.completeExceptionally(new IllegalArgumentException("textReplyToSend")); } Activity activityToSend = new Activity(ActivityTypes.MESSAGE) { @@ -302,7 +303,9 @@ public CompletableFuture sendActivity( */ @Override public CompletableFuture sendActivity(Activity activityToSend) { - BotAssert.activityNotNull(activityToSend); + if (activityToSend == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } return sendActivities(Collections.singletonList(activityToSend)) .thenApply(resourceResponses -> { @@ -327,7 +330,7 @@ public CompletableFuture sendActivity(Activity activityToSend) @Override public CompletableFuture sendActivities(List activities) { if (activities == null || activities.size() == 0) { - throw new IllegalArgumentException("activities"); + return Async.completeExceptionally(new IllegalArgumentException("activities")); } // Bind the relevant Conversation Reference properties, such as URLs and @@ -373,7 +376,6 @@ private CompletableFuture sendActivitiesThroughCallbackPipel List activities, int nextCallbackIndex ) { - if (nextCallbackIndex == onSendActivities.size()) { return sendActivitiesThroughAdapter(activities); } @@ -400,7 +402,9 @@ private CompletableFuture sendActivitiesThroughCallbackPipel */ @Override public CompletableFuture updateActivity(Activity withActivity) { - BotAssert.activityNotNull(withActivity); + if (withActivity == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } ConversationReference conversationReference = activity.getConversationReference(); withActivity.applyConversationReference(conversationReference); @@ -418,10 +422,11 @@ private CompletableFuture updateActivityInternal( Iterator updateHandlers, Supplier> callAtBottom ) { - - BotAssert.activityNotNull(updateActivity); + if (updateActivity == null) { + return Async.completeExceptionally(new IllegalArgumentException("Activity")); + } if (updateHandlers == null) { - throw new IllegalArgumentException("updateHandlers"); + return Async.completeExceptionally(new IllegalArgumentException("updateHandlers")); } // No middleware to run. @@ -461,7 +466,7 @@ private CompletableFuture updateActivityInternal( */ public CompletableFuture deleteActivity(String activityId) { if (StringUtils.isWhitespace(activityId) || StringUtils.isEmpty(activityId)) { - throw new IllegalArgumentException("activityId"); + return Async.completeExceptionally(new IllegalArgumentException("activityId")); } ConversationReference cr = activity.getConversationReference(); @@ -486,7 +491,7 @@ public CompletableFuture deleteActivity(String activityId) { @Override public CompletableFuture deleteActivity(ConversationReference conversationReference) { if (conversationReference == null) { - throw new IllegalArgumentException("conversationReference"); + return Async.completeExceptionally(new IllegalArgumentException("conversationReference")); } Supplier> actuallyDeleteStuff = () -> getAdapter() @@ -502,9 +507,11 @@ private CompletableFuture deleteActivityInternal( Iterator deleteHandlers, Supplier> callAtBottom ) { - BotAssert.conversationReferenceNotNull(cr); + if (cr == null) { + return Async.completeExceptionally(new IllegalArgumentException("ConversationReference")); + } if (deleteHandlers == null) { - throw new IllegalArgumentException("deleteHandlers"); + return Async.completeExceptionally(new IllegalArgumentException("deleteHandlers")); } // No middleware to run. diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java index 6acda3a1b..c1f5ff73e 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java @@ -26,7 +26,7 @@ public class TurnContextStateCollection implements AutoCloseable { * @return The value. * @throws IllegalArgumentException Null key. */ - public T get(String key) { + public T get(String key) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key"); } @@ -59,7 +59,7 @@ public T get(Class type) { * @param The type of the value. * @throws IllegalArgumentException For null key or value. */ - public void add(String key, T value) { + public void add(String key, T value) throws IllegalArgumentException { if (key == null) { throw new IllegalArgumentException("key"); } @@ -82,7 +82,7 @@ public void add(String key, T value) { * @param The type of the value. * @throws IllegalArgumentException For null value. */ - public void add(T value) { + public void add(T value) throws IllegalArgumentException { if (value == null) { throw new IllegalArgumentException("value"); } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java index 950e4f1e7..1aab8bdec 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserState.java @@ -26,7 +26,7 @@ public UserState(Storage withStorage) { * @return The key for the channel and sender. */ @Override - public String getStorageKey(TurnContext turnContext) { + public String getStorageKey(TurnContext turnContext) throws IllegalArgumentException { if (turnContext.getActivity() == null) { throw new IllegalArgumentException("invalid activity"); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java index b5297d69f..6264a05fe 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java @@ -9,6 +9,7 @@ import com.microsoft.bot.builder.adapters.TestFlow; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ConversationAccount; +import java.util.concurrent.CompletionException; import org.junit.Assert; import org.junit.Test; @@ -553,7 +554,7 @@ public void State_UseBotStateDirectly() { }).send(Activity.createConversationUpdateActivity()).startTest().join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void UserState_NullChannelIdThrows() { Map dictionary = new HashMap<>(); UserState userState = new UserState(new MemoryStorage(dictionary)); @@ -563,7 +564,7 @@ public void UserState_NullChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void UserState_EmptyChannelIdThrows() { Map dictionary = new HashMap<>(); UserState userState = new UserState(new MemoryStorage(dictionary)); @@ -573,7 +574,7 @@ public void UserState_EmptyChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void UserState_NullFromThrows() { Map dictionary = new HashMap<>(); UserState userState = new UserState(new MemoryStorage(dictionary)); @@ -583,7 +584,7 @@ public void UserState_NullFromThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void UserState_NullFromIdThrows() { Map dictionary = new HashMap<>(); UserState userState = new UserState(new MemoryStorage(dictionary)); @@ -593,7 +594,7 @@ public void UserState_NullFromIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void UserState_EmptyFromIdThrows() { Map dictionary = new HashMap<>(); UserState userState = new UserState(new MemoryStorage(dictionary)); @@ -603,7 +604,7 @@ public void UserState_EmptyFromIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void ConversationState_NullConversationThrows() { Map dictionary = new HashMap<>(); ConversationState conversationState = new ConversationState(new MemoryStorage(dictionary)); @@ -615,7 +616,7 @@ public void ConversationState_NullConversationThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void ConversationState_NullConversationIdThrows() { Map dictionary = new HashMap<>(); ConversationState conversationState = new ConversationState(new MemoryStorage(dictionary)); @@ -627,7 +628,7 @@ public void ConversationState_NullConversationIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void ConversationState_EmptyConversationIdThrows() { Map dictionary = new HashMap<>(); ConversationState conversationState = new ConversationState(new MemoryStorage(dictionary)); @@ -639,7 +640,7 @@ public void ConversationState_EmptyConversationIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void ConversationState_NullChannelIdThrows() { Map dictionary = new HashMap<>(); ConversationState conversationState = new ConversationState(new MemoryStorage(dictionary)); @@ -651,7 +652,7 @@ public void ConversationState_NullChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void ConversationState_EmptyChannelIdThrows() { Map dictionary = new HashMap<>(); ConversationState conversationState = new ConversationState(new MemoryStorage(dictionary)); @@ -663,7 +664,7 @@ public void ConversationState_EmptyChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_NullChannelIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -675,7 +676,7 @@ public void PrivateConversationState_NullChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_EmptyChannelIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -687,7 +688,7 @@ public void PrivateConversationState_EmptyChannelIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_NullFromThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -699,7 +700,7 @@ public void PrivateConversationState_NullFromThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_NullFromIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -711,7 +712,7 @@ public void PrivateConversationState_NullFromIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_EmptyFromIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -723,7 +724,7 @@ public void PrivateConversationState_EmptyFromIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_NullConversationThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -735,7 +736,7 @@ public void PrivateConversationState_NullConversationThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_NullConversationIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( @@ -747,7 +748,7 @@ public void PrivateConversationState_NullConversationIdThrows() { TestPocoState value = testProperty.get(context).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void PrivateConversationState_EmptyConversationIdThrows() { Map dictionary = new HashMap<>(); PrivateConversationState botState = new PrivateConversationState( diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java index 147ad10ed..08740c97e 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java @@ -6,6 +6,7 @@ import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.ConversationAccount; +import java.util.concurrent.CompletionException; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; @@ -35,8 +36,8 @@ protected void BadArgs() { try { store.logActivity(null).join(); Assert.fail("logActivity Should have thrown on null"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail("logActivity Should have thrown ArgumentNull exception on null"); } @@ -44,8 +45,8 @@ protected void BadArgs() { try { store.getTranscriptActivities(null, null).join(); Assert.fail("getTranscriptActivities Should have thrown on null"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail( "getTranscriptActivities Should have thrown ArgumentNull exception on null" @@ -55,8 +56,8 @@ protected void BadArgs() { try { store.getTranscriptActivities("asdfds", null).join(); Assert.fail("getTranscriptActivities Should have thrown on null"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail( "getTranscriptActivities Should have thrown ArgumentNull exception on null" @@ -66,8 +67,8 @@ protected void BadArgs() { try { store.listTranscripts(null).join(); Assert.fail("listTranscripts Should have thrown on null"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail("listTranscripts Should have thrown ArgumentNull exception on null"); } @@ -75,8 +76,8 @@ protected void BadArgs() { try { store.deleteTranscript(null, null).join(); Assert.fail("deleteTranscript Should have thrown on null channelId"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail( "deleteTranscript Should have thrown ArgumentNull exception on null channelId" @@ -86,8 +87,8 @@ protected void BadArgs() { try { store.deleteTranscript("test", null).join(); Assert.fail("deleteTranscript Should have thrown on null conversationId"); - } catch (IllegalArgumentException e) { - + } catch (CompletionException e) { + Assert.assertTrue(e.getCause() instanceof IllegalArgumentException); } catch (Throwable t) { Assert.fail( "deleteTranscript Should have thrown ArgumentNull exception on null conversationId" diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Async.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java similarity index 50% rename from libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Async.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java index c7b543829..a343cd9b4 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Async.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java @@ -1,53 +1,85 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.integration; - -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -/** - * Asyc and CompletableFuture helpers methods. - */ -public final class Async { - private Async() { - - } - - /** - * Executes a block and throws a completion exception if needed. - * - * @param supplier The block to execute. - * @param The type of the return value. - * @return The return value. - */ - public static T tryThrow(ThrowSupplier supplier) { - try { - return supplier.get(); - } catch (CompletionException ce) { - throw ce; - } catch (Throwable t) { - throw new CompletionException(t); - } - } - - /** - * Executes a block and returns a CompletableFuture with either the return - * value or the exception (completeExceptionally). - * - * @param supplier The block to execute. - * @param The type of the CompletableFuture value. - * @return The CompletableFuture - */ - public static CompletableFuture tryCompletion(ThrowSupplier supplier) { - CompletableFuture result = new CompletableFuture<>(); - - try { - result.complete(supplier.get()); - } catch (Throwable t) { - result.completeExceptionally(t); - } - - return result; - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.connector; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * Asyc and CompletableFuture helpers methods. + */ +public final class Async { + private Async() { + + } + + /** + * Executes a block and throws a completion exception if needed. + * + * @param supplier The block to execute. + * @param The type of the return value. + * @return The return value. + */ + public static T tryThrow(ThrowSupplier supplier) { + try { + return supplier.get(); + } catch (CompletionException ce) { + throw ce; + } catch (Throwable t) { + throw new CompletionException(t); + } + } + + /** + * Executes a block and returns a CompletableFuture with either the return + * value or the exception (completeExceptionally). + * + * @param supplier The block to execute. + * @param The type of the CompletableFuture value. + * @return The CompletableFuture + */ + public static CompletableFuture wrapBlock(ThrowSupplier supplier) { + CompletableFuture result = new CompletableFuture<>(); + + try { + result.complete(supplier.get()); + } catch (Throwable t) { + result.completeExceptionally(t); + } + + return result; + } + + /** + * Executes a block that returns a CompletableFuture, and catches any exceptions in order + * to properly return a completed exceptionally result. + * + * @param supplier The block to execute. + * @param The type of the CompletableFuture value. + * @return The CompletableFuture + */ + public static CompletableFuture tryCompletable(ThrowSupplier> supplier) { + CompletableFuture result = new CompletableFuture<>(); + + try { + return supplier.get(); + } catch (Throwable t) { + result.completeExceptionally(t); + } + + return result; + } + + /** + * Constructs a CompletableFuture completed exceptionally. + * @param ex The exception. + * @param Type of CompletableFuture. + * @return A CompletableFuture with the exception. + */ + public static CompletableFuture completeExceptionally(Throwable ex) { + CompletableFuture result = new CompletableFuture<>(); + result.completeExceptionally(ex); + return result; + } +} diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ThrowSupplier.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ThrowSupplier.java similarity index 87% rename from libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ThrowSupplier.java rename to libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ThrowSupplier.java index 1696c0808..9e8141f44 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ThrowSupplier.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/ThrowSupplier.java @@ -1,19 +1,19 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.integration; - -/** - * A Supplier that throws. - * @param The type of the Supplier return value. - */ -@FunctionalInterface -public interface ThrowSupplier { - /** - * Gets a result. - * - * @return a result - * @throws Throwable Any exception - */ - T get() throws Throwable; -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.connector; + +/** + * A Supplier that throws. + * @param The type of the Supplier return value. + */ +@FunctionalInterface +public interface ThrowSupplier { + /** + * Gets a result. + * + * @return a result + * @throws Throwable Any exception + */ + T get() throws Throwable; +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EndorsementsValidator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EndorsementsValidator.java index 74870aa0d..fe9dffabc 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EndorsementsValidator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EndorsementsValidator.java @@ -38,8 +38,10 @@ public abstract class EndorsementsValidator { * endorsement list, or the incoming activity is not * considered valid. * @return True is the expected endorsement is found in the Endorsement set. + * @throws IllegalArgumentException Missing endorsements */ - public static boolean validate(String expectedEndorsement, List endorsements) { + public static boolean validate(String expectedEndorsement, List endorsements) + throws IllegalArgumentException { // If the Activity came in and doesn't have a Channel ID then it's making no // assertions as to who endorses it. This means it should pass. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java index 454b719bd..084f9c43b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java @@ -3,6 +3,7 @@ package com.microsoft.bot.connector.authentication; +import com.microsoft.bot.connector.Async; import org.apache.commons.lang3.StringUtils; import java.time.Duration; @@ -79,8 +80,6 @@ public static CompletableFuture authenticateToken( * @param channelId The ID of the channel to validate. * @param authConfig The authentication configuration. * @return A valid ClaimsIdentity. - * - * On join: * @throws AuthenticationException A token issued by the Bot Framework will FAIL * this check. Only Emulator tokens will pass. */ @@ -93,7 +92,7 @@ public static CompletableFuture authenticateToken( AuthenticationConfiguration authConfig ) { if (authConfig == null) { - throw new IllegalArgumentException("Missing AuthenticationConfiguration"); + return Async.completeExceptionally(new IllegalArgumentException("Missing AuthenticationConfiguration")); } return channelProvider.getChannelService() diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java index 3b6e5303e..71f52d511 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java @@ -3,6 +3,7 @@ package com.microsoft.bot.connector.authentication; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -130,11 +131,7 @@ public static CompletableFuture validateAuthHeader( * @param serviceUrl The service URL for the activity. * @param authConfig The authentication configuration. * @return A task that represents the work queued to execute. - * - * On Call: * @throws IllegalArgumentException Incorrect arguments supplied - * - * On Join: * @throws AuthenticationException Authentication Error */ public static CompletableFuture validateAuthHeader( @@ -146,7 +143,8 @@ public static CompletableFuture validateAuthHeader( AuthenticationConfiguration authConfig ) { if (StringUtils.isEmpty(authHeader)) { - throw new IllegalArgumentException("No authHeader present. Auth is required."); + return Async.completeExceptionally( + new IllegalArgumentException("No authHeader present. Auth is required.")); } boolean usingEmulator = EmulatorValidation.isTokenFromEmulator(authHeader); @@ -183,8 +181,9 @@ public static CompletableFuture validateAuthHeader( * @param claims The map of claims. * @return The value of the appId claim if found (null if it can't find a * suitable claim). + * @throws IllegalArgumentException Missing claims */ - public static String getAppIdFromClaims(Map claims) { + public static String getAppIdFromClaims(Map claims) throws IllegalArgumentException { if (claims == null) { throw new IllegalArgumentException("claims"); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java index ed713763e..0ff583ac9 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java @@ -6,6 +6,7 @@ package com.microsoft.bot.connector.rest; +import com.microsoft.bot.connector.Async; import retrofit2.Retrofit; import com.microsoft.bot.connector.Attachments; import com.google.common.reflect.TypeToken; @@ -84,9 +85,9 @@ CompletableFuture> getAttachment( */ public CompletableFuture getAttachmentInfo(String attachmentId) { if (attachmentId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter attachmentId is required and cannot be null." - ); + )); } return service.getAttachmentInfo( @@ -125,13 +126,16 @@ private ServiceResponse getAttachmentInfoDelegate( */ public CompletableFuture getAttachment(String attachmentId, String viewId) { if (attachmentId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter attachmentId is required and cannot be null." - ); + )); } if (viewId == null) { - throw new IllegalArgumentException("Parameter viewId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter viewId is required and cannot be null." + )); } + return service.getAttachment( attachmentId, viewId, this.client.getAcceptLanguage(), this.client.getUserAgent() ).thenApply(responseBodyResponse -> { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java index 9628de4e9..742b48623 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java @@ -6,6 +6,7 @@ package com.microsoft.bot.connector.rest; +import com.microsoft.bot.connector.Async; import retrofit2.Retrofit; import com.microsoft.bot.connector.BotSignIn; import com.microsoft.bot.restclient.ServiceResponse; @@ -65,8 +66,11 @@ CompletableFuture> getSignInUrl( */ public CompletableFuture getSignInUrl(String state) { if (state == null) { - throw new IllegalArgumentException("Parameter state is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter state is required and cannot be null." + )); } + final String codeChallenge = null; final String emulatorUrl = null; final String finalRedirect = null; @@ -98,8 +102,11 @@ public CompletableFuture getSignInUrl( String finalRedirect ) { if (state == null) { - throw new IllegalArgumentException("Parameter state is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter state is required and cannot be null." + )); } + return service.getSignInUrl(state, codeChallenge, emulatorUrl, finalRedirect) .thenApply(responseBodyResponse -> { try { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java index ce39d99c3..14600a500 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java @@ -6,6 +6,7 @@ package com.microsoft.bot.connector.rest; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.restclient.ServiceResponseBuilder; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.AttachmentData; @@ -263,9 +264,9 @@ public CompletableFuture createConversation( ConversationParameters parameters ) { if (parameters == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter parameters is required and cannot be null." - ); + )); } Validator.validate(parameters); @@ -319,14 +320,14 @@ public CompletableFuture sendToConversation( Activity activity ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (activity == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activity is required and cannot be null." - ); + )); } Validator.validate(activity); @@ -372,35 +373,39 @@ public CompletableFuture updateActivity( Activity activity ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (activityId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activityId is required and cannot be null." - ); + )); } if (activity == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activity is required and cannot be null." - ); + )); } - Validator.validate(activity); - return service.updateActivity( - conversationId, activityId, activity, client.getAcceptLanguage(), client.getUserAgent() - ) + return Async.tryCompletable(() -> { + Validator.validate(activity); + return service.updateActivity( + conversationId, activityId, activity, client.getAcceptLanguage(), + client.getUserAgent() + ) - .thenApply(responseBodyResponse -> { - try { - return updateActivityDelegate(responseBodyResponse).body(); - } catch (ErrorResponseException e) { - throw e; - } catch (Throwable t) { - throw new ErrorResponseException("updateActivityAsync", responseBodyResponse); - } - }); + .thenApply(responseBodyResponse -> { + try { + return updateActivityDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException( + "updateActivityAsync", responseBodyResponse); + } + }); + }); } private ServiceResponse updateActivityDelegate( @@ -432,19 +437,19 @@ public CompletableFuture replyToActivity( Activity activity ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (activityId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activityId is required and cannot be null." - ); + )); } if (activity == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activity is required and cannot be null." - ); + )); } Validator.validate(activity); @@ -488,14 +493,14 @@ private ServiceResponse replyToActivityDelegate( @Override public CompletableFuture deleteActivity(String conversationId, String activityId) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (activityId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activityId is required and cannot be null." - ); + )); } return service.deleteActivity( @@ -534,9 +539,9 @@ private ServiceResponse deleteActivityDelegate( @Override public CompletableFuture> getConversationMembers(String conversationId) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } return service.getConversationMembers( @@ -579,12 +584,14 @@ public CompletableFuture getConversationMember( String conversationId ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } return service.getConversationMember( @@ -628,14 +635,14 @@ public CompletableFuture deleteConversationMember( String memberId ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (memberId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter memberId is required and cannot be null." - ); + )); } return service.deleteConversationMember( @@ -682,14 +689,14 @@ public CompletableFuture> getActivityMembers( String activityId ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (activityId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter activityId is required and cannot be null." - ); + )); } return service.getActivityMembers( @@ -729,14 +736,14 @@ public CompletableFuture uploadAttachment( AttachmentData attachmentUpload ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (attachmentUpload == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter attachmentUpload is required and cannot be null." - ); + )); } Validator.validate(attachmentUpload); @@ -783,12 +790,14 @@ public CompletableFuture sendConversationHistory( Transcript history ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (history == null) { - throw new IllegalArgumentException("Parameter history is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter history is required and cannot be null." + )); } Validator.validate(history); @@ -837,9 +846,9 @@ public CompletableFuture getConversationPagedMembers( String conversationId ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } return service.getConversationPagedMembers( @@ -889,14 +898,14 @@ public CompletableFuture getConversationPagedMembers( String continuationToken ) { if (conversationId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter conversationId is required and cannot be null." - ); + )); } if (continuationToken == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter continuationToken is required and cannot be null." - ); + )); } return service.getConversationPagedMembers( diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java index b3a96346a..89f5a7d1f 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestTeamsOperations.java @@ -7,6 +7,7 @@ package com.microsoft.bot.connector.rest; import com.google.common.reflect.TypeToken; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.teams.TeamsOperations; import com.microsoft.bot.restclient.ServiceResponse; import com.microsoft.bot.schema.teams.ConversationList; @@ -56,7 +57,9 @@ public class RestTeamsOperations implements TeamsOperations { @Override public CompletableFuture fetchChannelList(String teamId) { if (teamId == null) { - throw new IllegalArgumentException("Parameter teamId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter teamId is required and cannot be null." + )); } return service.fetchChannelList(teamId, client.getAcceptLanguage(), client.getUserAgent()) @@ -92,7 +95,9 @@ private ServiceResponse fetchChannelListDelegate( @Override public CompletableFuture fetchTeamDetails(String teamId) { if (teamId == null) { - throw new IllegalArgumentException("Parameter teamId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter teamId is required and cannot be null." + )); } return service.fetchTeamDetails(teamId, client.getAcceptLanguage(), client.getUserAgent()) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java index b79504d65..40821b4a0 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java @@ -6,6 +6,7 @@ package com.microsoft.bot.connector.rest; +import com.microsoft.bot.connector.Async; import retrofit2.Retrofit; import com.microsoft.bot.connector.UserToken; import com.google.common.reflect.TypeToken; @@ -111,13 +112,16 @@ CompletableFuture> getTokenStatus( @Override public CompletableFuture getToken(String userId, String connectionName) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (connectionName == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter connectionName is required and cannot be null." - ); + )); } + final String channelId = null; final String code = null; return service.getToken(userId, connectionName, channelId, code) @@ -149,13 +153,16 @@ public CompletableFuture getToken( String code ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (connectionName == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter connectionName is required and cannot be null." - ); + )); } + return service.getToken(userId, connectionName, channelId, code) .thenApply(responseBodyResponse -> { try { @@ -199,18 +206,21 @@ public CompletableFuture> getAadTokens( AadResourceUrls aadResourceUrls ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (connectionName == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter connectionName is required and cannot be null." - ); + )); } if (aadResourceUrls == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter aadResourceUrls is required and cannot be null." - ); + )); } + Validator.validate(aadResourceUrls); final String channelId = null; return service.getAadTokens(userId, connectionName, aadResourceUrls, channelId) @@ -242,29 +252,35 @@ public CompletableFuture> getAadTokens( String channelId ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (connectionName == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter connectionName is required and cannot be null." - ); + )); } if (aadResourceUrls == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter aadResourceUrls is required and cannot be null." - ); + )); } - Validator.validate(aadResourceUrls); - return service.getAadTokens(userId, connectionName, aadResourceUrls, channelId) - .thenApply(responseBodyResponse -> { - try { - return getAadTokensDelegate(responseBodyResponse).body(); - } catch (ErrorResponseException e) { - throw e; - } catch (Throwable t) { - throw new ErrorResponseException("getAadTokens", responseBodyResponse); - } - }); + + return Async.tryCompletable(() -> { + Validator.validate(aadResourceUrls); + return service.getAadTokens(userId, connectionName, aadResourceUrls, channelId) + .thenApply(responseBodyResponse -> { + try { + return getAadTokensDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("getAadTokens", responseBodyResponse); + } + + }); + }); } private ServiceResponse> getAadTokensDelegate( @@ -292,7 +308,9 @@ private ServiceResponse> getAadTokensDelegate( @Override public CompletableFuture signOut(String userId) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } return service.signOut(userId).thenApply(responseBodyResponse -> { @@ -321,17 +339,19 @@ public CompletableFuture signOut( String channelId ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } if (connectionName == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter connectionName is required and cannot be null." - ); + )); } if (channelId == null) { - throw new IllegalArgumentException( + return Async.completeExceptionally(new IllegalArgumentException( "Parameter channelId is required and cannot be null." - ); + )); } return service.signOut(userId, connectionName, channelId) @@ -371,8 +391,11 @@ private ServiceResponse signOutDelegate( @Override public CompletableFuture> getTokenStatus(String userId) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } + final String channelId = null; final String include = null; return service.getTokenStatus(userId, channelId, include) @@ -402,8 +425,11 @@ public CompletableFuture> getTokenStatus( String include ) { if (userId == null) { - throw new IllegalArgumentException("Parameter userId is required and cannot be null."); + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); } + return service.getTokenStatus(userId, channelId, include) .thenApply(responseBodyResponse -> { try { diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java index 11457670c..9902d8633 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java @@ -123,8 +123,8 @@ public void CreateConversationWithNullParameter() { try { ConversationResourceResponse result = connector.getConversations().createConversation(null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -178,8 +178,8 @@ public void GetConversationMembersWithNullConversationId() { try { List members = connector.getConversations().getConversationMembers(null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -328,20 +328,20 @@ public void SendToConversationWithNullConversationId() { }}; try { - connector.getConversations().sendToConversation(null, activity); + connector.getConversations().sendToConversation(null, activity).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @Test public void SendToConversationWithNullActivity() { try { - connector.getConversations().sendToConversation("id",null); + connector.getConversations().sendToConversation("id",null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -452,20 +452,20 @@ public void GetActivityMembersWithInvalidConversationId() { @Test public void GetActivityMembersWithNullConversationId() { try { - connector.getConversations().getActivityMembers(null, "id"); + connector.getConversations().getActivityMembers(null, "id").join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @Test public void GetActivityMembersWithNullActivityId() { try { - connector.getConversations().getActivityMembers("id", null); + connector.getConversations().getActivityMembers("id", null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -544,10 +544,10 @@ public void ReplyToActivityWithNullConversationId() { }}; try { - connector.getConversations().replyToActivity(null, "id", activity); + connector.getConversations().replyToActivity(null, "id", activity).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -560,20 +560,20 @@ public void ReplyToActivityWithNullActivityId() { }}; try { - connector.getConversations().replyToActivity("id", null, activity); + connector.getConversations().replyToActivity("id", null, activity).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @Test public void ReplyToActivityWithNullActivity() { try { - connector.getConversations().replyToActivity("id", "id", null); + connector.getConversations().replyToActivity("id", "id", null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -597,8 +597,8 @@ public void ReplyToActivityWithNullReply() { try { ResourceResponse replyResponse = connector.getConversations().replyToActivity(conversation.getId(), response.getId(), null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -659,8 +659,8 @@ public void DeleteActivityWithNullConversationId() { try { connector.getConversations().deleteActivity(null, "id").join(); Assert.fail("expected exception did not occur."); - } catch(IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch(CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -669,8 +669,8 @@ public void DeleteActivityWithNullActivityId() { try { connector.getConversations().deleteActivity("id", null).join(); Assert.fail("expected exception did not occur."); - } catch(IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch(CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -743,10 +743,10 @@ public void UpdateActivityWithNullConversationId() { }}; try { - connector.getConversations().updateActivity(null, "id", activity); + connector.getConversations().updateActivity(null, "id", activity).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @@ -759,20 +759,20 @@ public void UpdateActivityWithNullActivityId() { }}; try { - connector.getConversations().updateActivity("id", null, activity); + connector.getConversations().updateActivity("id", null, activity).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } @Test public void UpdateActivityWithNullActivity() { try { - connector.getConversations().updateActivity("id", "id", null); + connector.getConversations().updateActivity("id", "id", null).join(); Assert.fail("expected exception did not occur."); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("cannot be null")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("cannot be null")); } } diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java index 45db7c97a..ce755c5a4 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java @@ -98,8 +98,8 @@ public void EmptyHeaderBotWithNoCredentialsShouldThrow() throws ExecutionExcepti "", null).join(); Assert.fail("Should have thrown IllegalArgumentException"); - } catch (IllegalArgumentException e) { - Assert.assertTrue(e.getMessage().contains("authHeader")); + } catch (CompletionException e) { + Assert.assertTrue(e.getCause().getMessage().contains("authHeader")); } } diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java index 23fd4b6cf..75fbd1c98 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java @@ -4,6 +4,7 @@ import com.microsoft.bot.connector.rest.RestConnectorClient; import com.microsoft.bot.connector.rest.RestOAuthClient; import com.microsoft.bot.schema.AadResourceUrls; +import java.util.concurrent.CompletionException; import org.junit.Assert; import org.junit.Test; @@ -35,7 +36,7 @@ public void OAuthClient_ShouldThrowOnNullCredentials() { OAuthClient client = new RestOAuthClient("http://localhost", null); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetUserToken_ShouldThrowOnEmptyConnectionName() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); client.getUserToken().getToken("userid", null).join(); @@ -52,40 +53,40 @@ public void GetUserToken_ShouldReturnNullOnInvalidConnectionstring() { }).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void SignOutUser_ShouldThrowOnEmptyUserId() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getUserToken().signOut(null); + client.getUserToken().signOut(null).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetSigninLink_ShouldThrowOnNullState() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getBotSignIn().getSignInUrl(null); + client.getBotSignIn().getSignInUrl(null).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetTokenStatus_ShouldThrowOnNullUserId() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getUserToken().getTokenStatus(null); + client.getUserToken().getTokenStatus(null).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetAadTokensAsync_ShouldThrowOnNullUserId() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getUserToken().getAadTokens(null, "connection", new AadResourceUrls()); + client.getUserToken().getAadTokens(null, "connection", new AadResourceUrls()).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetAadTokensAsync_ShouldThrowOnNullConncetionName() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getUserToken().getAadTokens("user", null, new AadResourceUrls()); + client.getUserToken().getAadTokens("user", null, new AadResourceUrls()).join(); } - @Test(expected = IllegalArgumentException.class) + @Test(expected = CompletionException.class) public void GetAadTokensAsync_ShouldThrowOnNullResourceUrls() { OAuthClient client = new RestOAuthClient("http://localhost", new BotAccessTokenStub("token")); - client.getUserToken().getAadTokens("user", "connection", null); + client.getUserToken().getAadTokens("user", "connection", null).join(); } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java index d6b36780f..11848f35d 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java @@ -170,9 +170,10 @@ public static T getAs( * @param obj of type T * @param The type of the value. * @return This Entity with the properties from the passed sub-Entity. + * @throws IllegalArgumentException For arguments that can't be converted. */ @JsonIgnore - public Entity setAs(T obj) { + public Entity setAs(T obj) throws IllegalArgumentException { // Serialize String tempJson; try { From 1b478b457c3a52b616b3a09bc3dfe4251646ed95 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 26 Jan 2021 22:15:05 -0600 Subject: [PATCH 046/221] Removed swagger files (#918) Co-authored-by: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> --- libraries/swagger/ConnectorAPI.json | 2691 -------------------------- libraries/swagger/README.md | 38 - libraries/swagger/TokenAPI.json | 396 ---- libraries/swagger/generateClient.cmd | 17 - libraries/swagger/package-lock.json | 54 - 5 files changed, 3196 deletions(-) delete mode 100644 libraries/swagger/ConnectorAPI.json delete mode 100644 libraries/swagger/README.md delete mode 100644 libraries/swagger/TokenAPI.json delete mode 100644 libraries/swagger/generateClient.cmd delete mode 100644 libraries/swagger/package-lock.json diff --git a/libraries/swagger/ConnectorAPI.json b/libraries/swagger/ConnectorAPI.json deleted file mode 100644 index f3a5b6e49..000000000 --- a/libraries/swagger/ConnectorAPI.json +++ /dev/null @@ -1,2691 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "v3", - "title": "Microsoft Bot Connector API - v3.0", - "description": "The Bot Connector REST API allows your bot to send and receive messages to channels configured in the\r\n[Bot Framework Developer Portal](https://dev.botframework.com). The Connector service uses industry-standard REST\r\nand JSON over HTTPS.\r\n\r\nClient libraries for this REST API are available. See below for a list.\r\n\r\nMany bots will use both the Bot Connector REST API and the associated [Bot State REST API](/en-us/restapi/state). The\r\nBot State REST API allows a bot to store and retrieve state associated with users and conversations.\r\n\r\nAuthentication for both the Bot Connector and Bot State REST APIs is accomplished with JWT Bearer tokens, and is\r\ndescribed in detail in the [Connector Authentication](/en-us/restapi/authentication) document.\r\n\r\n# Client Libraries for the Bot Connector REST API\r\n\r\n* [Bot Builder for C#](/en-us/csharp/builder/sdkreference/)\r\n* [Bot Builder for Node.js](/en-us/node/builder/overview/)\r\n* Generate your own from the [Connector API Swagger file](https://raw.githubusercontent.com/Microsoft/BotBuilder/master/CSharp/Library/Microsoft.Bot.Connector.Shared/Swagger/ConnectorAPI.json)\r\n\r\n© 2016 Microsoft", - "termsOfService": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx", - "contact": { - "name": "Bot Framework", - "url": "https://botframework.com", - "email": "botframework@microsoft.com" - }, - "license": { - "name": "The MIT License (MIT)", - "url": "https://opensource.org/licenses/MIT" - } - }, - "host": "api.botframework.com", - "schemes": [ - "https" - ], - "paths": { - "/v3/attachments/{attachmentId}": { - "get": { - "tags": [ - "Attachments" - ], - "summary": "GetAttachmentInfo", - "description": "Get AttachmentInfo structure describing the attachment views", - "operationId": "Attachments_GetAttachmentInfo", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "attachmentId", - "in": "path", - "description": "attachment id", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An attachmentInfo object is returned which describes the:\r\n* type of the attachment\r\n* name of the attachment\r\n\r\n\r\nand an array of views:\r\n* Size - size of the object\r\n* ViewId - View Id which can be used to fetch a variation on the content (ex: original or thumbnail)", - "schema": { - "$ref": "#/definitions/AttachmentInfo" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/attachments/{attachmentId}/views/{viewId}": { - "get": { - "tags": [ - "Attachments" - ], - "summary": "GetAttachment", - "description": "Get the named view as binary content", - "operationId": "Attachments_GetAttachment", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "attachmentId", - "in": "path", - "description": "attachment id", - "required": true, - "type": "string" - }, - { - "name": "viewId", - "in": "path", - "description": "View id from attachmentInfo", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "Attachment stream", - "schema": { - "format": "byte", - "type": "file" - } - }, - "301": { - "description": "The Location header describes where the content is now." - }, - "302": { - "description": "The Location header describes where the content is now." - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetConversations", - "description": "List the Conversations in which this bot has participated.\r\n\r\nGET from this method with a skip token\r\n\r\nThe return value is a ConversationsResult, which contains an array of ConversationMembers and a skip token. If the skip token is not empty, then \r\nthere are further values to be returned. Call this method again with the returned token to get more values.\r\n\r\nEach ConversationMembers object contains the ID of the conversation and an array of ChannelAccounts that describe the members of the conversation.", - "operationId": "Conversations_GetConversations", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "continuationToken", - "in": "query", - "description": "skip or continuation token", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An object will be returned containing \r\n* an array (Conversations) of ConversationMembers objects\r\n* a continuation token\r\n\r\nEach ConversationMembers object contains:\r\n* the Id of the conversation\r\n* an array (Members) of ChannelAccount objects", - "schema": { - "$ref": "#/definitions/ConversationsResult" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "post": { - "tags": [ - "Conversations" - ], - "summary": "CreateConversation", - "description": "Create a new Conversation.\r\n\r\nPOST to this method with a\r\n* Bot being the bot creating the conversation\r\n* IsGroup set to true if this is not a direct message (default is false)\r\n* Array containing the members to include in the conversation\r\n\r\nThe return value is a ResourceResponse which contains a conversation id which is suitable for use\r\nin the message payload and REST API uris.\r\n\r\nMost channels only support the semantics of bots initiating a direct message conversation. An example of how to do that would be:\r\n\r\n```\r\nvar resource = await connector.conversations.CreateConversation(new ConversationParameters(){ Bot = bot, members = new ChannelAccount[] { new ChannelAccount(\"user1\") } );\r\nawait connect.Conversations.SendToConversationAsync(resource.Id, new Activity() ... ) ;\r\n\r\n```", - "operationId": "Conversations_CreateConversation", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "parameters", - "in": "body", - "description": "Parameters to create the conversation from", - "required": true, - "schema": { - "$ref": "#/definitions/ConversationParameters" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "201": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing \r\n* the ID for the conversation\r\n* ActivityId for the activity if provided. If ActivityId is null then the channel doesn't support returning resource id's for activity.", - "schema": { - "$ref": "#/definitions/ConversationResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities": { - "post": { - "tags": [ - "Conversations" - ], - "summary": "SendToConversation", - "description": "This method allows you to send an activity to the end of a conversation.\r\n\r\nThis is slightly different from ReplyToActivity().\r\n* SendToConversation(conversationId) - will append the activity to the end of the conversation according to the timestamp or semantics of the channel.\r\n* ReplyToActivity(conversationId,ActivityId) - adds the activity as a reply to another activity, if the channel supports it. If the channel does not support nested replies, ReplyToActivity falls back to SendToConversation.\r\n\r\nUse ReplyToActivity when replying to a specific activity in the conversation.\r\n\r\nUse SendToConversation in all other cases.", - "operationId": "Conversations_SendToConversation", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "Activity to send", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities/history": { - "post": { - "tags": [ - "Conversations" - ], - "summary": "SendConversationHistory", - "description": "This method allows you to upload the historic activities to the conversation.\r\n\r\nSender must ensure that the historic activities have unique ids and appropriate timestamps. The ids are used by the client to deal with duplicate activities and the timestamps are used by the client to render the activities in the right order.", - "operationId": "Conversations_SendConversationHistory", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "history", - "in": "body", - "description": "Historic activities", - "required": true, - "schema": { - "$ref": "#/definitions/Transcript" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities/{activityId}": { - "put": { - "tags": [ - "Conversations" - ], - "summary": "UpdateActivity", - "description": "Edit an existing activity.\r\n\r\nSome channels allow you to edit an existing activity to reflect the new state of a bot conversation.\r\n\r\nFor example, you can remove buttons after someone has clicked \"Approve\" button.", - "operationId": "Conversations_UpdateActivity", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId to update", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "replacement Activity", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "post": { - "tags": [ - "Conversations" - ], - "summary": "ReplyToActivity", - "description": "This method allows you to reply to an activity.\r\n\r\nThis is slightly different from SendToConversation().\r\n* SendToConversation(conversationId) - will append the activity to the end of the conversation according to the timestamp or semantics of the channel.\r\n* ReplyToActivity(conversationId,ActivityId) - adds the activity as a reply to another activity, if the channel supports it. If the channel does not support nested replies, ReplyToActivity falls back to SendToConversation.\r\n\r\nUse ReplyToActivity when replying to a specific activity in the conversation.\r\n\r\nUse SendToConversation in all other cases.", - "operationId": "Conversations_ReplyToActivity", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId the reply is to (OPTIONAL)", - "required": true, - "type": "string" - }, - { - "name": "activity", - "in": "body", - "description": "Activity to send", - "required": true, - "schema": { - "$ref": "#/definitions/Activity" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - }, - "delete": { - "tags": [ - "Conversations" - ], - "summary": "DeleteActivity", - "description": "Delete an existing activity.\r\n\r\nSome channels allow you to delete an existing activity, and if successful this method will remove the specified activity.", - "operationId": "Conversations_DeleteActivity", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "activityId to delete", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "The operation succeeded, there is no response." - }, - "202": { - "description": "The request has been accepted for processing, but the processing has not been completed" - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/members": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetConversationMembers", - "description": "Enumerate the members of a conversation. \r\n\r\nThis REST API takes a ConversationId and returns an array of ChannelAccount objects representing the members of the conversation.", - "operationId": "Conversations_GetConversationMembers", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of ChannelAccount objects", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/pagedmembers": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetConversationPagedMembers", - "description": "Enumerate the members of a conversation one page at a time.\r\n\r\nThis REST API takes a ConversationId. Optionally a pageSize and/or continuationToken can be provided. It returns a PagedMembersResult, which contains an array\r\nof ChannelAccounts representing the members of the conversation and a continuation token that can be used to get more values.\r\n\r\nOne page of ChannelAccounts records are returned with each call. The number of records in a page may vary between channels and calls. The pageSize parameter can be used as \r\na suggestion. If there are no additional results the response will not contain a continuation token. If there are no members in the conversation the Members will be empty or not present in the response.\r\n\r\nA response to a request that has a continuation token from a prior request may rarely return members from a previous request.", - "operationId": "Conversations_GetConversationPagedMembers", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "pageSize", - "in": "query", - "description": "Suggested page size", - "required": false, - "type": "integer", - "format": "int32" - }, - { - "name": "continuationToken", - "in": "query", - "description": "Continuation Token", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "OK", - "schema": { - "$ref": "#/definitions/PagedMembersResult" - } - } - } - } - }, - "/v3/conversations/{conversationId}/members/{memberId}": { - "delete": { - "tags": [ - "Conversations" - ], - "summary": "DeleteConversationMember", - "description": "Deletes a member from a conversation. \r\n\r\nThis REST API takes a ConversationId and a memberId (of type string) and removes that member from the conversation. If that member was the last member\r\nof the conversation, the conversation will also be deleted.", - "operationId": "Conversations_DeleteConversationMember", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "memberId", - "in": "path", - "description": "ID of the member to delete from this conversation", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "The operation succeeded, there is no response." - }, - "204": { - "description": "The operation succeeded but no content was returned." - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/activities/{activityId}/members": { - "get": { - "tags": [ - "Conversations" - ], - "summary": "GetActivityMembers", - "description": "Enumerate the members of an activity. \r\n\r\nThis REST API takes a ConversationId and a ActivityId, returning an array of ChannelAccount objects representing the members of the particular activity in the conversation.", - "operationId": "Conversations_GetActivityMembers", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "activityId", - "in": "path", - "description": "Activity ID", - "required": true, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of ChannelAccount objects", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/v3/conversations/{conversationId}/attachments": { - "post": { - "tags": [ - "Conversations" - ], - "summary": "UploadAttachment", - "description": "Upload an attachment directly into a channel's blob storage.\r\n\r\nThis is useful because it allows you to store data in a compliant store when dealing with enterprises.\r\n\r\nThe response is a ResourceResponse which contains an AttachmentId which is suitable for using with the attachments API.", - "operationId": "Conversations_UploadAttachment", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json" - ], - "parameters": [ - { - "name": "conversationId", - "in": "path", - "description": "Conversation ID", - "required": true, - "type": "string" - }, - { - "name": "attachmentUpload", - "in": "body", - "description": "Attachment data", - "required": true, - "schema": { - "$ref": "#/definitions/AttachmentData" - } - } - ], - "responses": { - "200": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "201": { - "description": "A ResourceResponse object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "202": { - "description": "An object will be returned containing the ID for the resource.", - "schema": { - "$ref": "#/definitions/ResourceResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - } - }, - "definitions": { - "AttachmentInfo": { - "description": "Metadata for an attachment", - "type": "object", - "properties": { - "name": { - "description": "Name of the attachment", - "type": "string" - }, - "type": { - "description": "ContentType of the attachment", - "type": "string" - }, - "views": { - "description": "attachment views", - "type": "array", - "items": { - "$ref": "#/definitions/AttachmentView" - } - } - } - }, - "AttachmentView": { - "description": "Attachment View name and size", - "type": "object", - "properties": { - "viewId": { - "description": "Id of the attachment", - "type": "string" - }, - "size": { - "format": "int32", - "description": "Size of the attachment", - "type": "integer" - } - } - }, - "ErrorResponse": { - "description": "An HTTP API response", - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Error", - "description": "Error message" - } - } - }, - "Error": { - "description": "Object representing error information", - "type": "object", - "properties": { - "code": { - "description": "Error code", - "type": "string" - }, - "message": { - "description": "Error message", - "type": "string" - }, - "innerHttpError": { - "$ref": "#/definitions/InnerHttpError", - "description": "Error from inner http call" - } - } - }, - "InnerHttpError": { - "description": "Object representing inner http error", - "type": "object", - "properties": { - "statusCode": { - "format": "int32", - "description": "HttpStatusCode from failed request", - "type": "integer" - }, - "body": { - "description": "Body from failed request", - "type": "object" - } - } - }, - "ConversationParameters": { - "description": "Parameters for creating a new conversation", - "type": "object", - "properties": { - "isGroup": { - "description": "IsGroup", - "type": "boolean" - }, - "bot": { - "$ref": "#/definitions/ChannelAccount", - "description": "The bot address for this conversation" - }, - "members": { - "description": "Members to add to the conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "topicName": { - "description": "(Optional) Topic of the conversation (if supported by the channel)", - "type": "string" - }, - "tenantId": { - "description": "(Optional) The tenant ID in which the conversation should be created", - "type": "string" - }, - "activity": { - "$ref": "#/definitions/Activity", - "description": "(Optional) When creating a new conversation, use this activity as the initial message to the conversation" - }, - "channelData": { - "description": "Channel specific payload for creating the conversation", - "type": "object" - } - } - }, - "ChannelAccount": { - "description": "Channel account information needed to route a message", - "type": "object", - "properties": { - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456)", - "type": "string" - }, - "name": { - "description": "Display friendly name", - "type": "string" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "type": "string" - }, - "role": { - "$ref": "#/definitions/RoleTypes", - "description": "Role of the entity behind the account (Example: User, Bot, etc.)" - } - } - }, - "Activity": { - "description": "An Activity is the basic communication type for the Bot Framework 3.0 protocol.", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/ActivityTypes", - "description": "Contains the activity type." - }, - "id": { - "description": "Contains an ID that uniquely identifies the activity on the channel.", - "type": "string" - }, - "timestamp": { - "format": "date-time", - "description": "Contains the date and time that the message was sent, in UTC, expressed in ISO-8601 format.", - "type": "string" - }, - "localTimestamp": { - "format": "date-time", - "description": "Contains the local date and time of the message, expressed in ISO-8601 format.\r\nFor example, 2016-09-23T13:07:49.4714686-07:00.", - "type": "string" - }, - "localTimezone": { - "description": "Contains the name of the local timezone of the message, expressed in IANA Time Zone database format.\r\nFor example, America/Los_Angeles.", - "type": "string" - }, - "callerId": { - "description": "A string containing an IRI identifying the caller of a bot. This field is not intended to be transmitted\r\nover the wire, but is instead populated by bots and clients based on cryptographically verifiable data\r\nthat asserts the identity of the callers (e.g. tokens).", - "type": "string" - }, - "serviceUrl": { - "description": "Contains the URL that specifies the channel's service endpoint. Set by the channel.", - "type": "string" - }, - "channelId": { - "description": "Contains an ID that uniquely identifies the channel. Set by the channel.", - "type": "string" - }, - "from": { - "$ref": "#/definitions/ChannelAccount", - "description": "Identifies the sender of the message." - }, - "conversation": { - "$ref": "#/definitions/ConversationAccount", - "description": "Identifies the conversation to which the activity belongs." - }, - "recipient": { - "$ref": "#/definitions/ChannelAccount", - "description": "Identifies the recipient of the message." - }, - "textFormat": { - "$ref": "#/definitions/TextFormatTypes", - "description": "Format of text fields Default:markdown" - }, - "attachmentLayout": { - "$ref": "#/definitions/AttachmentLayoutTypes", - "description": "The layout hint for multiple attachments. Default: list." - }, - "membersAdded": { - "description": "The collection of members added to the conversation.", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "membersRemoved": { - "description": "The collection of members removed from the conversation.", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - }, - "reactionsAdded": { - "description": "The collection of reactions added to the conversation.", - "type": "array", - "items": { - "$ref": "#/definitions/MessageReaction" - } - }, - "reactionsRemoved": { - "description": "The collection of reactions removed from the conversation.", - "type": "array", - "items": { - "$ref": "#/definitions/MessageReaction" - } - }, - "topicName": { - "description": "The updated topic name of the conversation.", - "type": "string" - }, - "historyDisclosed": { - "description": "Indicates whether the prior history of the channel is disclosed.", - "type": "boolean" - }, - "locale": { - "description": "A locale name for the contents of the text field.\r\nThe locale name is a combination of an ISO 639 two- or three-letter culture code associated with a language\r\nand an ISO 3166 two-letter subculture code associated with a country or region.\r\nThe locale name can also correspond to a valid BCP-47 language tag.", - "type": "string" - }, - "text": { - "description": "The text content of the message.", - "type": "string" - }, - "speak": { - "description": "The text to speak.", - "type": "string" - }, - "inputHint": { - "$ref": "#/definitions/InputHints", - "description": "Indicates whether your bot is accepting,\r\nexpecting, or ignoring user input after the message is delivered to the client." - }, - "summary": { - "description": "The text to display if the channel cannot render cards.", - "type": "string" - }, - "suggestedActions": { - "$ref": "#/definitions/SuggestedActions", - "description": "The suggested actions for the activity." - }, - "attachments": { - "description": "Attachments", - "type": "array", - "items": { - "$ref": "#/definitions/Attachment" - } - }, - "entities": { - "description": "Represents the entities that were mentioned in the message.", - "type": "array", - "items": { - "$ref": "#/definitions/Entity" - } - }, - "channelData": { - "description": "Contains channel-specific content.", - "type": "object" - }, - "action": { - "description": "Indicates whether the recipient of a contactRelationUpdate was added or removed from the sender's contact list.", - "type": "string" - }, - "replyToId": { - "description": "Contains the ID of the message to which this message is a reply.", - "type": "string" - }, - "label": { - "description": "A descriptive label for the activity.", - "type": "string" - }, - "valueType": { - "description": "The type of the activity's value object.", - "type": "string" - }, - "value": { - "description": "A value that is associated with the activity.", - "type": "object" - }, - "name": { - "description": "The name of the operation associated with an invoke or event activity.", - "type": "string" - }, - "relatesTo": { - "$ref": "#/definitions/ConversationReference", - "description": "A reference to another conversation or activity." - }, - "code": { - "$ref": "#/definitions/EndOfConversationCodes", - "description": "The a code for endOfConversation activities that indicates why the conversation ended." - }, - "expiration": { - "format": "date-time", - "description": "The time at which the activity should be considered to be \"expired\" and should not be presented to the recipient.", - "type": "string" - }, - "importance": { - "$ref": "#/definitions/ActivityImportance", - "description": "The importance of the activity." - }, - "deliveryMode": { - "$ref": "#/definitions/DeliveryModes", - "description": "A delivery hint to signal to the recipient alternate delivery paths for the activity.\r\nThe default delivery mode is \"default\"." - }, - "listenFor": { - "description": "List of phrases and references that speech and language priming systems should listen for", - "type": "array", - "items": { - "type": "string" - } - }, - "textHighlights": { - "description": "The collection of text fragments to highlight when the activity contains a ReplyToId value.", - "type": "array", - "items": { - "$ref": "#/definitions/TextHighlight" - } - }, - "semanticAction": { - "$ref": "#/definitions/SemanticAction", - "description": "An optional programmatic action accompanying this request" - } - } - }, - "ConversationAccount": { - "description": "Conversation account represents the identity of the conversation within a channel", - "type": "object", - "properties": { - "isGroup": { - "description": "Indicates whether the conversation contains more than two participants at the time the activity was generated", - "type": "boolean" - }, - "conversationType": { - "description": "Indicates the type of the conversation in channels that distinguish between conversation types", - "type": "string" - }, - "tenantId": { - "description": "This conversation's tenant ID", - "type": "string" - }, - "id": { - "description": "Channel id for the user or bot on this channel (Example: joe@smith.com, or @joesmith or 123456)", - "type": "string" - }, - "name": { - "description": "Display friendly name", - "type": "string" - }, - "aadObjectId": { - "description": "This account's object ID within Azure Active Directory (AAD)", - "type": "string" - }, - "role": { - "$ref": "#/definitions/RoleTypes", - "description": "Role of the entity behind the account (Example: User, Bot, etc.)" - } - } - }, - "MessageReaction": { - "description": "Message reaction object", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/MessageReactionTypes", - "description": "Message reaction type" - } - } - }, - "SuggestedActions": { - "description": "SuggestedActions that can be performed", - "type": "object", - "properties": { - "to": { - "description": "Ids of the recipients that the actions should be shown to. These Ids are relative to the channelId and a subset of all recipients of the activity", - "type": "array", - "items": { - "type": "string" - } - }, - "actions": { - "description": "Actions that can be shown to the user", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "Attachment": { - "description": "An attachment within an activity", - "type": "object", - "properties": { - "contentType": { - "description": "mimetype/Contenttype for the file", - "type": "string" - }, - "contentUrl": { - "description": "Content Url", - "type": "string" - }, - "content": { - "description": "Embedded content", - "type": "object" - }, - "name": { - "description": "(OPTIONAL) The name of the attachment", - "type": "string" - }, - "thumbnailUrl": { - "description": "(OPTIONAL) Thumbnail associated with attachment", - "type": "string" - } - } - }, - "Entity": { - "description": "Metadata object pertaining to an activity", - "type": "object", - "properties": { - "type": { - "description": "Type of this entity (RFC 3987 IRI)", - "type": "string" - } - } - }, - "ConversationReference": { - "description": "An object relating to a particular point in a conversation", - "type": "object", - "properties": { - "activityId": { - "description": "(Optional) ID of the activity to refer to", - "type": "string" - }, - "user": { - "$ref": "#/definitions/ChannelAccount", - "description": "(Optional) User participating in this conversation" - }, - "bot": { - "$ref": "#/definitions/ChannelAccount", - "description": "Bot participating in this conversation" - }, - "conversation": { - "$ref": "#/definitions/ConversationAccount", - "description": "Conversation reference" - }, - "channelId": { - "description": "Channel ID", - "type": "string" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the referenced conversation may be performed", - "type": "string" - } - } - }, - "TextHighlight": { - "description": "Refers to a substring of content within another field", - "type": "object", - "properties": { - "text": { - "description": "Defines the snippet of text to highlight", - "type": "string" - }, - "occurrence": { - "format": "int32", - "description": "Occurrence of the text field within the referenced text, if multiple exist.", - "type": "integer" - } - } - }, - "SemanticAction": { - "description": "Represents a reference to a programmatic action", - "type": "object", - "properties": { - "state": { - "$ref": "#/definitions/SemanticActionStates", - "description": "State of this action. Allowed values: `start`, `continue`, `done`" - }, - "id": { - "description": "ID of this action", - "type": "string" - }, - "entities": { - "description": "Entities associated with this action", - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/Entity" - } - } - } - }, - "CardAction": { - "description": "A clickable action", - "type": "object", - "properties": { - "type": { - "$ref": "#/definitions/ActionTypes", - "description": "The type of action implemented by this button" - }, - "title": { - "description": "Text description which appears on the button", - "type": "string" - }, - "image": { - "description": "Image URL which will appear on the button, next to text label", - "type": "string" - }, - "text": { - "description": "Text for this action", - "type": "string" - }, - "displayText": { - "description": "(Optional) text to display in the chat feed if the button is clicked", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for action. Content of this property depends on the ActionType", - "type": "object" - }, - "channelData": { - "description": "Channel-specific data associated with this action", - "type": "object" - } - } - }, - "ConversationResourceResponse": { - "description": "A response containing a resource", - "type": "object", - "properties": { - "activityId": { - "description": "ID of the Activity (if sent)", - "type": "string" - }, - "serviceUrl": { - "description": "Service endpoint where operations concerning the conversation may be performed", - "type": "string" - }, - "id": { - "description": "Id of the resource", - "type": "string" - } - } - }, - "ConversationsResult": { - "description": "Conversations result", - "type": "object", - "properties": { - "continuationToken": { - "description": "Paging token", - "type": "string" - }, - "conversations": { - "description": "List of conversations", - "type": "array", - "items": { - "$ref": "#/definitions/ConversationMembers" - } - } - } - }, - "ConversationMembers": { - "description": "Conversation and its members", - "type": "object", - "properties": { - "id": { - "description": "Conversation ID", - "type": "string" - }, - "members": { - "description": "List of members in this conversation", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - } - }, - "ResourceResponse": { - "description": "A response containing a resource ID", - "type": "object", - "properties": { - "id": { - "description": "Id of the resource", - "type": "string" - } - } - }, - "Transcript": { - "description": "Transcript", - "type": "object", - "properties": { - "activities": { - "description": "A collection of Activities that conforms to the Transcript schema.", - "type": "array", - "items": { - "$ref": "#/definitions/Activity" - } - } - } - }, - "PagedMembersResult": { - "description": "Page of members.", - "type": "object", - "properties": { - "continuationToken": { - "description": "Paging token", - "type": "string" - }, - "members": { - "description": "The Channel Accounts.", - "type": "array", - "items": { - "$ref": "#/definitions/ChannelAccount" - } - } - } - }, - "AttachmentData": { - "description": "Attachment data", - "type": "object", - "properties": { - "type": { - "description": "Content-Type of the attachment", - "type": "string" - }, - "name": { - "description": "Name of the attachment", - "type": "string" - }, - "originalBase64": { - "format": "byte", - "description": "Attachment content", - "type": "string" - }, - "thumbnailBase64": { - "format": "byte", - "description": "Attachment thumbnail", - "type": "string" - } - } - }, - "HeroCard": { - "description": "A Hero card (card with a single, large image)", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "CardImage": { - "description": "An image on a card", - "type": "object", - "properties": { - "url": { - "description": "URL thumbnail image for major content property", - "type": "string" - }, - "alt": { - "description": "Image description intended for screen readers", - "type": "string" - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "Action assigned to specific Attachment" - } - } - }, - "AnimationCard": { - "description": "An animation card (Ex: gif or short video clip)", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card. When this field contains more than one URL, each URL is an alternative format of the same content.", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder. Allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "duration": { - "description": "Describes the length of the media content without requiring a receiver to open the content. Formatted as an ISO 8601 Duration field.", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "ThumbnailUrl": { - "description": "Thumbnail URL", - "type": "object", - "properties": { - "url": { - "description": "URL pointing to the thumbnail to use for media content", - "type": "string" - }, - "alt": { - "description": "HTML alt text to include on this thumbnail image", - "type": "string" - } - } - }, - "MediaUrl": { - "description": "Media URL", - "type": "object", - "properties": { - "url": { - "description": "Url for the media", - "type": "string" - }, - "profile": { - "description": "Optional profile hint to the client to differentiate multiple MediaUrl objects from each other", - "type": "string" - } - } - }, - "AudioCard": { - "description": "Audio card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card. When this field contains more than one URL, each URL is an alternative format of the same content.", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder. Allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "duration": { - "description": "Describes the length of the media content without requiring a receiver to open the content. Formatted as an ISO 8601 Duration field.", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "BasicCard": { - "description": "A basic card", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "MediaCard": { - "description": "Media card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card. When this field contains more than one URL, each URL is an alternative format of the same content.", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder. Allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "duration": { - "description": "Describes the length of the media content without requiring a receiver to open the content. Formatted as an ISO 8601 Duration field.", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "ReceiptCard": { - "description": "A receipt card", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "facts": { - "description": "Array of Fact objects", - "type": "array", - "items": { - "$ref": "#/definitions/Fact" - } - }, - "items": { - "description": "Array of Receipt Items", - "type": "array", - "items": { - "$ref": "#/definitions/ReceiptItem" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card" - }, - "total": { - "description": "Total amount of money paid (or to be paid)", - "type": "string" - }, - "tax": { - "description": "Total amount of tax paid (or to be paid)", - "type": "string" - }, - "vat": { - "description": "Total amount of VAT paid (or to be paid)", - "type": "string" - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "Fact": { - "description": "Set of key-value pairs. Advantage of this section is that key and value properties will be \r\nrendered with default style information with some delimiter between them. So there is no need for developer to specify style information.", - "type": "object", - "properties": { - "key": { - "description": "The key for this Fact", - "type": "string" - }, - "value": { - "description": "The value for this Fact", - "type": "string" - } - } - }, - "ReceiptItem": { - "description": "An item on a receipt card", - "type": "object", - "properties": { - "title": { - "description": "Title of the Card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle appears just below Title field, differs from Title in font styling only", - "type": "string" - }, - "text": { - "description": "Text field appears just below subtitle, differs from Subtitle in font styling only", - "type": "string" - }, - "image": { - "$ref": "#/definitions/CardImage", - "description": "Image" - }, - "price": { - "description": "Amount with currency", - "type": "string" - }, - "quantity": { - "description": "Number of items of given kind", - "type": "string" - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the Item bubble." - } - } - }, - "SigninCard": { - "description": "A card representing a request to sign in", - "type": "object", - "properties": { - "text": { - "description": "Text for signin request", - "type": "string" - }, - "buttons": { - "description": "Action to use to perform signin", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "OAuthCard": { - "description": "A card representing a request to perform a sign in via OAuth", - "type": "object", - "properties": { - "text": { - "description": "Text for signin request", - "type": "string" - }, - "connectionName": { - "description": "The name of the registered connection", - "type": "string" - }, - "buttons": { - "description": "Action to use to perform signin", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - } - } - }, - "ThumbnailCard": { - "description": "A thumbnail card (card with a single, small thumbnail image)", - "type": "object", - "properties": { - "title": { - "description": "Title of the card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of the card", - "type": "string" - }, - "text": { - "description": "Text for the card", - "type": "string" - }, - "images": { - "description": "Array of images for the card", - "type": "array", - "items": { - "$ref": "#/definitions/CardImage" - } - }, - "buttons": { - "description": "Set of actions applicable to the current card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "tap": { - "$ref": "#/definitions/CardAction", - "description": "This action will be activated when user taps on the card itself" - } - } - }, - "VideoCard": { - "description": "Video card", - "type": "object", - "properties": { - "title": { - "description": "Title of this card", - "type": "string" - }, - "subtitle": { - "description": "Subtitle of this card", - "type": "string" - }, - "text": { - "description": "Text of this card", - "type": "string" - }, - "image": { - "$ref": "#/definitions/ThumbnailUrl", - "description": "Thumbnail placeholder" - }, - "media": { - "description": "Media URLs for this card. When this field contains more than one URL, each URL is an alternative format of the same content.", - "type": "array", - "items": { - "$ref": "#/definitions/MediaUrl" - } - }, - "buttons": { - "description": "Actions on this card", - "type": "array", - "items": { - "$ref": "#/definitions/CardAction" - } - }, - "shareable": { - "description": "This content may be shared with others (default:true)", - "type": "boolean" - }, - "autoloop": { - "description": "Should the client loop playback at end of content (default:true)", - "type": "boolean" - }, - "autostart": { - "description": "Should the client automatically start playback of media in this card (default:true)", - "type": "boolean" - }, - "aspect": { - "description": "Aspect ratio of thumbnail/media placeholder. Allowed values are \"16:9\" and \"4:3\"", - "type": "string" - }, - "duration": { - "description": "Describes the length of the media content without requiring a receiver to open the content. Formatted as an ISO 8601 Duration field.", - "type": "string" - }, - "value": { - "description": "Supplementary parameter for this card", - "type": "object" - } - } - }, - "GeoCoordinates": { - "description": "GeoCoordinates (entity type: \"https://schema.org/GeoCoordinates\")", - "type": "object", - "properties": { - "elevation": { - "format": "double", - "description": "Elevation of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "latitude": { - "format": "double", - "description": "Latitude of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "longitude": { - "format": "double", - "description": "Longitude of the location [WGS 84](https://en.wikipedia.org/wiki/World_Geodetic_System)", - "type": "number" - }, - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "Mention": { - "description": "Mention information (entity type: \"mention\")", - "type": "object", - "properties": { - "mentioned": { - "$ref": "#/definitions/ChannelAccount", - "description": "The mentioned user" - }, - "text": { - "description": "Sub Text which represents the mention (can be null or empty)", - "type": "string" - }, - "type": { - "description": "Type of this entity (RFC 3987 IRI)", - "type": "string" - } - } - }, - "Place": { - "description": "Place (entity type: \"https://schema.org/Place\")", - "type": "object", - "properties": { - "address": { - "description": "Address of the place (may be `string` or complex object of type `PostalAddress`)", - "type": "object" - }, - "geo": { - "description": "Geo coordinates of the place (may be complex object of type `GeoCoordinates` or `GeoShape`)", - "type": "object" - }, - "hasMap": { - "description": "Map to the place (may be `string` (URL) or complex object of type `Map`)", - "type": "object" - }, - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "Thing": { - "description": "Thing (entity type: \"https://schema.org/Thing\")", - "type": "object", - "properties": { - "type": { - "description": "The type of the thing", - "type": "string" - }, - "name": { - "description": "The name of the thing", - "type": "string" - } - } - }, - "MediaEventValue": { - "description": "Supplementary parameter for media events", - "type": "object", - "properties": { - "cardValue": { - "description": "Callback parameter specified in the Value field of the MediaCard that originated this event", - "type": "object" - } - } - }, - "TokenRequest": { - "description": "A request to receive a user token", - "type": "object", - "properties": { - "provider": { - "description": "The provider to request a user token from", - "type": "string" - }, - "settings": { - "description": "A collection of settings for the specific provider for this request", - "type": "object", - "additionalProperties": { - "type": "object" - } - } - } - }, - "TokenResponse": { - "description": "A response that includes a user token", - "type": "object", - "properties": { - "channelId": { - "description": "The channelId of the TokenResponse", - "type": "string" - }, - "connectionName": { - "description": "The connection name", - "type": "string" - }, - "token": { - "description": "The user token", - "type": "string" - }, - "expiration": { - "description": "Expiration for the token, in ISO 8601 format (e.g. \"2007-04-05T14:30Z\")", - "type": "string" - } - } - }, - "ActivityTypes": { - "description": "Types of Activities", - "enum": [ - "message", - "contactRelationUpdate", - "conversationUpdate", - "typing", - "endOfConversation", - "event", - "invoke", - "deleteUserData", - "messageUpdate", - "messageDelete", - "installationUpdate", - "messageReaction", - "suggestion", - "trace", - "handoff" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "ActivityTypes", - "modelAsString": true - } - }, - "AttachmentLayoutTypes": { - "description": "Attachment layout types", - "enum": [ - "list", - "carousel" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "AttachmentLayoutTypes", - "modelAsString": true - } - }, - "SemanticActionStates": { - "description": "Indicates whether the semantic action is starting, continuing, or done", - "enum": [ - "start", - "continue", - "done" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "SemanticActionStates", - "modelAsString": true - } - }, - "ActionTypes": { - "description": "Defines action types for clickable buttons.", - "enum": [ - "openUrl", - "imBack", - "postBack", - "playAudio", - "playVideo", - "showImage", - "downloadFile", - "signin", - "call", - "payment", - "messageBack" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "ActionTypes", - "modelAsString": true - } - }, - "ContactRelationUpdateActionTypes": { - "description": "Action types valid for ContactRelationUpdate activities", - "enum": [ - "add", - "remove" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "ContactRelationUpdateActionTypes", - "modelAsString": true - } - }, - "InstallationUpdateActionTypes": { - "description": "Action types valid for InstallationUpdate activities", - "enum": [ - "add", - "remove" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "InstallationUpdateActionTypes", - "modelAsString": true - } - }, - "MessageReactionTypes": { - "description": "Message reaction types", - "enum": [ - "like", - "plusOne" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "MessageReactionTypes", - "modelAsString": true - } - }, - "TextFormatTypes": { - "description": "Text format types", - "enum": [ - "markdown", - "plain", - "xml" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "TextFormatTypes", - "modelAsString": true - } - }, - "InputHints": { - "description": "Indicates whether the bot is accepting, expecting, or ignoring input", - "enum": [ - "acceptingInput", - "ignoringInput", - "expectingInput" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "InputHints", - "modelAsString": true - } - }, - "EndOfConversationCodes": { - "description": "Codes indicating why a conversation has ended", - "enum": [ - "unknown", - "completedSuccessfully", - "userCancelled", - "botTimedOut", - "botIssuedInvalidMessage", - "channelFailed" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "EndOfConversationCodes", - "modelAsString": true - } - }, - "ActivityImportance": { - "description": "Defines the importance of an Activity", - "enum": [ - "low", - "normal", - "high" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "ActivityImportance", - "modelAsString": true - } - }, - "RoleTypes": { - "description": "Role of the entity behind the account (Example: User, Bot, etc.)", - "enum": [ - "user", - "bot" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "RoleTypes", - "modelAsString": true - } - }, - "DeliveryModes": { - "description": "Values for deliveryMode field", - "enum": [ - "normal", - "notification" - ], - "type": "string", - "properties": {}, - "x-ms-enum": { - "name": "DeliveryModes", - "modelAsString": true - } - }, - "MicrosoftPayMethodData": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "merchantId": { - "description": "Microsoft Pay Merchant ID", - "type": "string" - }, - "supportedNetworks": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "array", - "items": { - "type": "string" - } - }, - "supportedTypes": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "PaymentAddress": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "country": { - "description": "This is the CLDR (Common Locale Data Repository) region code. For example, US, GB, CN, or JP", - "type": "string" - }, - "addressLine": { - "description": "This is the most specific part of the address. It can include, for example, a street name, a house number, apartment number, a rural delivery route, descriptive instructions, or a post office box number.", - "type": "array", - "items": { - "type": "string" - } - }, - "region": { - "description": "This is the top level administrative subdivision of the country. For example, this can be a state, a province, an oblast, or a prefecture.", - "type": "string" - }, - "city": { - "description": "This is the city/town portion of the address.", - "type": "string" - }, - "dependentLocality": { - "description": "This is the dependent locality or sublocality within a city. For example, used for neighborhoods, boroughs, districts, or UK dependent localities.", - "type": "string" - }, - "postalCode": { - "description": "This is the postal code or ZIP code, also known as PIN code in India.", - "type": "string" - }, - "sortingCode": { - "description": "This is the sorting code as used in, for example, France.", - "type": "string" - }, - "languageCode": { - "description": "This is the BCP-47 language code for the address. It's used to determine the field separators and the order of fields when formatting the address for display.", - "type": "string" - }, - "organization": { - "description": "This is the organization, firm, company, or institution at this address.", - "type": "string" - }, - "recipient": { - "description": "This is the name of the recipient or contact person.", - "type": "string" - }, - "phone": { - "description": "This is the phone number of the recipient or contact person.", - "type": "string" - } - } - }, - "PaymentCurrencyAmount": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "currency": { - "description": "A currency identifier", - "type": "string" - }, - "value": { - "description": "Decimal monetary value", - "type": "string" - }, - "currencySystem": { - "description": "Currency system", - "type": "string" - } - } - }, - "PaymentDetails": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "total": { - "$ref": "#/definitions/PaymentItem", - "description": "Contains the total amount of the payment request" - }, - "displayItems": { - "description": "Contains line items for the payment request that the user agent may display", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentItem" - } - }, - "shippingOptions": { - "description": "A sequence containing the different shipping options for the user to choose from", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentShippingOption" - } - }, - "modifiers": { - "description": "Contains modifiers for particular payment method identifiers", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentDetailsModifier" - } - }, - "error": { - "description": "Error description", - "type": "string" - } - } - }, - "PaymentItem": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "label": { - "description": "Human-readable description of the item", - "type": "string" - }, - "amount": { - "$ref": "#/definitions/PaymentCurrencyAmount", - "description": "Monetary amount for the item" - }, - "pending": { - "description": "When set to true this flag means that the amount field is not final.", - "type": "boolean" - } - } - }, - "PaymentShippingOption": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "id": { - "description": "String identifier used to reference this PaymentShippingOption", - "type": "string" - }, - "label": { - "description": "Human-readable description of the item", - "type": "string" - }, - "amount": { - "$ref": "#/definitions/PaymentCurrencyAmount", - "description": "Contains the monetary amount for the item" - }, - "selected": { - "description": "Indicates whether this is the default selected PaymentShippingOption", - "type": "boolean" - } - } - }, - "PaymentDetailsModifier": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "supportedMethods": { - "description": "Contains a sequence of payment method identifiers", - "type": "array", - "items": { - "type": "string" - } - }, - "total": { - "$ref": "#/definitions/PaymentItem", - "description": "This value overrides the total field in the PaymentDetails dictionary for the payment method identifiers in the supportedMethods field" - }, - "additionalDisplayItems": { - "description": "Provides additional display items that are appended to the displayItems field in the PaymentDetails dictionary for the payment method identifiers in the supportedMethods field", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentItem" - } - }, - "data": { - "description": "A JSON-serializable object that provides optional information that might be needed by the supported payment methods", - "type": "object" - } - } - }, - "PaymentMethodData": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "supportedMethods": { - "description": "Required sequence of strings containing payment method identifiers for payment methods that the merchant web site accepts", - "type": "array", - "items": { - "type": "string" - } - }, - "data": { - "description": "A JSON-serializable object that provides optional information that might be needed by the supported payment methods", - "type": "object" - } - } - }, - "PaymentOptions": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "requestPayerName": { - "description": "Indicates whether the user agent should collect and return the payer's name as part of the payment request", - "type": "boolean" - }, - "requestPayerEmail": { - "description": "Indicates whether the user agent should collect and return the payer's email address as part of the payment request", - "type": "boolean" - }, - "requestPayerPhone": { - "description": "Indicates whether the user agent should collect and return the payer's phone number as part of the payment request", - "type": "boolean" - }, - "requestShipping": { - "description": "Indicates whether the user agent should collect and return a shipping address as part of the payment request", - "type": "boolean" - }, - "shippingType": { - "description": "If requestShipping is set to true, then the shippingType field may be used to influence the way the user agent presents the user interface for gathering the shipping address", - "type": "string" - } - } - }, - "PaymentRequest": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "id": { - "description": "ID of this payment request", - "type": "string" - }, - "methodData": { - "description": "Allowed payment methods for this request", - "type": "array", - "items": { - "$ref": "#/definitions/PaymentMethodData" - } - }, - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Details for this request" - }, - "options": { - "$ref": "#/definitions/PaymentOptions", - "description": "Provides information about the options desired for the payment request" - }, - "expires": { - "description": "Expiration for this request, in ISO 8601 duration format (e.g., 'P1D')", - "type": "string" - } - } - }, - "PaymentRequestComplete": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "id": { - "description": "Payment request ID", - "type": "string" - }, - "paymentRequest": { - "$ref": "#/definitions/PaymentRequest", - "description": "Initial payment request" - }, - "paymentResponse": { - "$ref": "#/definitions/PaymentResponse", - "description": "Corresponding payment response" - } - } - }, - "PaymentResponse": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "methodName": { - "description": "The payment method identifier for the payment method that the user selected to fulfil the transaction", - "type": "string" - }, - "details": { - "description": "A JSON-serializable object that provides a payment method specific message used by the merchant to process the transaction and determine successful fund transfer", - "type": "object" - }, - "shippingAddress": { - "$ref": "#/definitions/PaymentAddress", - "description": "If the requestShipping flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then shippingAddress will be the full and final shipping address chosen by the user" - }, - "shippingOption": { - "description": "If the requestShipping flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then shippingOption will be the id attribute of the selected shipping option", - "type": "string" - }, - "payerEmail": { - "description": "If the requestPayerEmail flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then payerEmail will be the email address chosen by the user", - "type": "string" - }, - "payerPhone": { - "description": "If the requestPayerPhone flag was set to true in the PaymentOptions passed to the PaymentRequest constructor, then payerPhone will be the phone number chosen by the user", - "type": "string" - } - } - }, - "PaymentRequestCompleteResult": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "result": { - "description": "Result of the payment request completion", - "type": "string" - } - } - }, - "PaymentRequestUpdate": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "id": { - "description": "ID for the payment request to update", - "type": "string" - }, - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Update payment details" - }, - "shippingAddress": { - "$ref": "#/definitions/PaymentAddress", - "description": "Updated shipping address" - }, - "shippingOption": { - "description": "Updated shipping options", - "type": "string" - } - } - }, - "PaymentRequestUpdateResult": { - "deprecated": true, - "description": "Deprecated. Bot Framework no longer supports payments.", - "type": "object", - "properties": { - "details": { - "$ref": "#/definitions/PaymentDetails", - "description": "Update payment details" - } - } - } - }, - "securityDefinitions": { - "bearer_auth": { - "type": "apiKey", - "description": "Access token to authenticate calls to the Bot Connector Service.", - "name": "Authorization", - "in": "header" - } - } - } \ No newline at end of file diff --git a/libraries/swagger/README.md b/libraries/swagger/README.md deleted file mode 100644 index 78ef9ab29..000000000 --- a/libraries/swagger/README.md +++ /dev/null @@ -1,38 +0,0 @@ -# BotFramework Connector - -> see https://aka.ms/autorest - -Configuration for generating BotFramework Connector SDK. - -``` yaml -add-credentials: true -openapi-type: data-plane -``` -The current release for the BotFramework Connector is v3.0. - -# Releases - -## Connector API 3.0 - -``` yaml -input-file: ConnectorAPI.json -``` - -### Connector API 3.0 - Java Settings -These settings apply only when `--java` is specified on the command line. -``` yaml $(java) -java: - override-client-name: ConnectorClient - license-header: MICROSOFT_MIT_NO_VERSION - azure-arm: true - use-internal-constructors: true - namespace: com.microsoft.bot.schema - output-folder: ./generated -directive: - from: ConnectorAPI.json - where: $..['x-ms-enum'] - transform: > - if( $['modelAsString'] ) { - $['modelAsString'] = false; - } -``` \ No newline at end of file diff --git a/libraries/swagger/TokenAPI.json b/libraries/swagger/TokenAPI.json deleted file mode 100644 index 76f1dc0bb..000000000 --- a/libraries/swagger/TokenAPI.json +++ /dev/null @@ -1,396 +0,0 @@ -{ - "swagger": "2.0", - "info": { - "version": "token", - "title": "Microsoft Bot Token API - V3.1", - "termsOfService": "https://www.microsoft.com/en-us/legal/intellectualproperty/copyright/default.aspx", - "contact": { - "name": "Bot Framework", - "url": "https://botframework.com", - "email": "botframework@microsoft.com" - }, - "license": { - "name": "The MIT License (MIT)", - "url": "https://opensource.org/licenses/MIT" - } - }, - "host": "token.botframework.com", - "schemes": [ - "https" - ], - "paths": { - "/api/botsignin/GetSignInUrl": { - "get": { - "tags": [ - "BotSignIn" - ], - "operationId": "BotSignIn_GetSignInUrl", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "state", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "code_challenge", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "emulatorUrl", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "finalRedirect", - "in": "query", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "", - "schema": { - "type": "string" - } - } - } - } - }, - "/api/usertoken/GetToken": { - "get": { - "tags": [ - "UserToken" - ], - "operationId": "UserToken_GetToken", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "userId", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "connectionName", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "channelId", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "code", - "in": "query", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "A Token Response object will be returned\r\n", - "schema": { - "$ref": "#/definitions/TokenResponse" - } - }, - "404": { - "description": "Resource was not found\r\n", - "schema": { - "$ref": "#/definitions/TokenResponse" - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/api/usertoken/GetAadTokens": { - "post": { - "tags": [ - "UserToken" - ], - "operationId": "UserToken_GetAadTokens", - "consumes": [ - "application/json", - "text/json", - "application/xml", - "text/xml", - "application/x-www-form-urlencoded" - ], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "userId", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "connectionName", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "aadResourceUrls", - "in": "body", - "required": true, - "schema": { - "$ref": "#/definitions/AadResourceUrls" - } - }, - { - "name": "channelId", - "in": "query", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of key value pairs", - "schema": { - "type": "object", - "additionalProperties": { - "$ref": "#/definitions/TokenResponse" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/api/usertoken/SignOut": { - "delete": { - "tags": [ - "UserToken" - ], - "operationId": "UserToken_SignOut", - "consumes": [], - "produces": [], - "parameters": [ - { - "name": "userId", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "connectionName", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "channelId", - "in": "query", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "The operation succeeded, there is no response.", - "schema": { - "$ref": "#/definitions/Void" - } - }, - "204": { - "description": "No Content" - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - }, - "/api/usertoken/GetTokenStatus": { - "get": { - "tags": [ - "UserToken" - ], - "operationId": "UserToken_GetTokenStatus", - "consumes": [], - "produces": [ - "application/json", - "text/json", - "application/xml", - "text/xml" - ], - "parameters": [ - { - "name": "userId", - "in": "query", - "required": true, - "type": "string" - }, - { - "name": "channelId", - "in": "query", - "required": false, - "type": "string" - }, - { - "name": "include", - "in": "query", - "required": false, - "type": "string" - } - ], - "responses": { - "200": { - "description": "An array of TokenStatus objects", - "schema": { - "type": "array", - "items": { - "$ref": "#/definitions/TokenStatus" - } - } - }, - "default": { - "description": "The operation failed and the response is an error object describing the status code and failure.", - "schema": { - "$ref": "#/definitions/ErrorResponse" - } - } - } - } - } - }, - "definitions": { - "TokenResponse": { - "type": "object", - "properties": { - "channelId": { - "type": "string" - }, - "connectionName": { - "type": "string" - }, - "token": { - "type": "string" - }, - "expiration": { - "type": "string" - } - } - }, - "ErrorResponse": { - "type": "object", - "properties": { - "error": { - "$ref": "#/definitions/Error" - } - } - }, - "Error": { - "type": "object", - "properties": { - "code": { - "type": "string" - }, - "message": { - "type": "string" - }, - "innerHttpError": { - "$ref": "#/definitions/InnerHttpError" - } - } - }, - "InnerHttpError": { - "type": "object", - "properties": { - "statusCode": { - "format": "int32", - "type": "integer" - }, - "body": { - "type": "object" - } - } - }, - "AadResourceUrls": { - "type": "object", - "properties": { - "resourceUrls": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "Void": { - "type": "object", - "properties": {} - }, - "TokenStatus": { - "description": "The status of a particular token", - "type": "object", - "properties": { - "channelId": { - "description": "The channelId of the token status pertains to", - "type": "string" - }, - "connectionName": { - "description": "The name of the connection the token status pertains to", - "type": "string" - }, - "hasToken": { - "description": "True if a token is stored for this ConnectionName", - "type": "boolean" - }, - "serviceProviderDisplayName": { - "description": "The display name of the service provider for which this Token belongs to", - "type": "string" - } - } - } - }, - "securityDefinitions": { - "bearer_auth": { - "type": "apiKey", - "description": "Access token to authenticate calls to the Bot Connector Service.", - "name": "Authorization", - "in": "header" - } - } -} \ No newline at end of file diff --git a/libraries/swagger/generateClient.cmd b/libraries/swagger/generateClient.cmd deleted file mode 100644 index 084eb7361..000000000 --- a/libraries/swagger/generateClient.cmd +++ /dev/null @@ -1,17 +0,0 @@ -call npm install replace@0.3.0 - -del /q generated -del /q ..\botbuilder-schema\src\main\java\com\microsoft\bot\schema\models\ - -call autorest .\README.md --java --add-credentials - -robocopy .\generated\src\main\java\com\microsoft\bot\schema\models ..\botbuilder-schema\src\main\java\com\microsoft\bot\schema\models *.* /move /xf *Exception.java - -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.models.ErrorResponseException;" "import com.microsoft.bot.connector.models.ErrorResponseException;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.ConnectorClient;" "import com.microsoft.bot.connector.ConnectorClient;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.Attachments;" "import com.microsoft.bot.connector.Attachments;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.bot.schema.Conversations;" "import com.microsoft.bot.connector.Conversations;" . -r -q --include="*.java" -call .\node_modules\.bin\replace "import com.microsoft.rest.RestException;" "import com.microsoft.rest.RestException;import com.microsoft.bot.schema.ErrorResponse;" . -r -q --include="ErrorResponseException.java" -call .\node_modules\.bin\replace "package com.microsoft.bot.schema" "package com.microsoft.bot.connector" . -r -q --include="*.java" - -robocopy .\generated\src\main\java\com\microsoft\bot\schema ..\bot-connector\src\main\java\com\microsoft\bot\connector *.* /e /move diff --git a/libraries/swagger/package-lock.json b/libraries/swagger/package-lock.json deleted file mode 100644 index 2ddf92e87..000000000 --- a/libraries/swagger/package-lock.json +++ /dev/null @@ -1,54 +0,0 @@ -{ - "requires": true, - "lockfileVersion": 1, - "dependencies": { - "colors": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/colors/-/colors-0.5.1.tgz", - "integrity": "sha1-fQAj6usVTo7p/Oddy5I9DtFmd3Q=" - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=" - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" - } - }, - "nomnom": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/nomnom/-/nomnom-1.6.2.tgz", - "integrity": "sha1-hKZqJgF0QI/Ft3oY+IjszET7aXE=", - "requires": { - "colors": "0.5.x", - "underscore": "~1.4.4" - } - }, - "replace": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/replace/-/replace-0.3.0.tgz", - "integrity": "sha1-YAgXIRiGWFlatqeU63/ty0yNOcc=", - "requires": { - "colors": "0.5.x", - "minimatch": "~0.2.9", - "nomnom": "1.6.x" - } - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=" - }, - "underscore": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.4.4.tgz", - "integrity": "sha1-YaajIBBiKvoHljvzJSA88SI51gQ=" - } - } -} From 5e1ca81cef11aabee220758696b962440cc0c6c7 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 29 Jan 2021 08:16:18 -0600 Subject: [PATCH 047/221] Lparrish/dialog (#922) * dialogs.choices * bot-dialogs: periodic push (mostly functional ObjectPath) * Dialog Updates * Interim commit and push to protect work in progress * Pushing to protect work in progress * Safety Checkin * Setting up unit tests for Dialogs * Updates to get VS Code test runner working * Completed DialogManager and DialogStateManager * Updates to match namespaces and file structure. * First unit test for DialogStateManager now working. * Addin DialogStateManager unit tests and fixes. * Waterfall dialogs and Prompts * Fixes and unit tests * Choice Prompt unit tests * Additional unit tests and fixes * Unit tests and fixes. * ComponentDialog tests and fixes * DialogContainerTests and fixes to Dialog * Additional tests and fixes * Final unit tests * Correct failing unit test. * Fixes for merge issues. * Update to new exception handling pattern. * Remove uneeded unit test inports. * Added copyright notices. * Update DialogContext * Added recognizer libraries Co-authored-by: tracyboehrer --- etc/bot-checkstyle.xml | 4 +- .../com/microsoft/bot/builder/BotAdapter.java | 5 + .../com/microsoft/bot/builder/BotState.java | 62 +- .../bot/builder/BotTelemetryClient.java | 12 + .../bot/builder/ComponentRegistration.java | 34 + .../bot/builder/DelegatingTurnContext.java | 18 + .../microsoft/bot/builder/NextDelegate.java | 3 + .../bot/builder/NullBotTelemetryClient.java | 5 + .../bot/builder/OnTurnErrorHandler.java | 3 + .../bot/builder/RegisterClassMiddleware.java | 74 + .../microsoft/bot/builder/TurnContext.java | 22 +- .../bot/builder/TurnContextImpl.java | 30 + .../builder/TurnContextStateCollection.java | 57 +- .../bot/builder/ActivityHandlerTests.java | 3 + .../bot/builder/BotFrameworkAdapterTests.java | 3 + .../bot/builder/BotStateSetTests.java | 3 + .../bot/builder/InspectionTests.java | 4 +- .../bot/builder/MemoryConnectorClient.java | 3 + .../bot/builder/MemoryConversations.java | 3 + .../bot/builder/MemoryTranscriptTests.java | 3 + .../microsoft/bot/builder/MentionTests.java | 3 + .../bot/builder/MessageFactoryTests.java | 3 + .../bot/builder/MockAppCredentials.java | 3 + .../bot/builder/MockConnectorClient.java | 3 + .../bot/builder/OnTurnErrorTests.java | 3 + .../builder/ShowTypingMiddlewareTests.java | 3 + .../bot/builder/TelemetryMiddlewareTests.java | 3 + .../bot/builder/TestAdapterTests.java | 3 + .../bot/builder/TranscriptBaseTests.java | 3 + .../bot/builder/adapters/TestAdapter.java | 74 + .../bot/builder/adapters/TestFlow.java | 14 +- .../TeamsActivityHandlerHidingTests.java | 3 + .../com/microsoft/bot/connector/Channels.java | 5 + .../bot/connector/Conversations.java | 6 +- .../microsoft/bot/connector/OAuthClient.java | 3 + .../bot/connector/OAuthClientConfig.java | 3 + .../AuthenticationConstants.java | 5 + .../AuthenticationException.java | 3 + .../CredentialsAuthenticator.java | 3 + .../authentication/SkillValidation.java | 103 + .../bot/connector/rest/RestOAuthClient.java | 3 + .../connector/teams/TeamsConnectorClient.java | 3 + .../restclient/interceptors/package-info.java | 3 + .../bot/restclient/package-info.java | 3 + .../bot/restclient/protocol/package-info.java | 3 + .../bot/restclient/retry/package-info.java | 3 + .../restclient/serializer/package-info.java | 3 + .../bot/connector/AttachmentsTest.java | 3 + .../bot/connector/BotAccessTokenStub.java | 3 + .../bot/connector/BotConnectorTestBase.java | 3 + .../bot/connector/JwtTokenExtractorTests.java | 3 + .../bot/connector/OAuthConnectorTests.java | 3 + .../bot/connector/OAuthTestBase.java | 3 + .../bot/connector/RetryParamsTests.java | 3 + .../microsoft/bot/connector/RetryTests.java | 3 + .../bot/connector/UserAgentTest.java | 3 + .../connector/base/InterceptorManager.java | 3 + .../bot/connector/base/NetworkCallRecord.java | 3 + .../bot/connector/base/RecordedData.java | 3 + .../bot/connector/base/TestBase.java | 3 + .../bot/restclient/AnimalShelter.java | 3 + .../AnimalWithTypeIdContainingDot.java | 3 + .../CatWithTypeIdContainingDot.java | 3 + .../bot/restclient/ComposeTurtles.java | 3 + .../DogWithTypeIdContainingDot.java | 3 + .../bot/restclient/FlattenableAnimalInfo.java | 3 + ...NonEmptyAnimalWithTypeIdContainingDot.java | 3 + .../RabbitWithTypeIdContainingDot.java | 3 + .../TurtleWithTypeIdContainingDot.java | 3 + libraries/bot-dialogs/pom.xml | 51 +- .../bot/dialogs/ComponentDialog.java | 418 ++++ .../com/microsoft/bot/dialogs/Dialog.java | 336 ++- .../bot/dialogs/DialogCompletion.java | 54 - .../bot/dialogs/DialogContainer.java | 174 +- .../microsoft/bot/dialogs/DialogContext.java | 674 ++++-- .../bot/dialogs/DialogContextPath.java | 29 + .../bot/dialogs/DialogDependencies.java | 18 + .../microsoft/bot/dialogs/DialogEvent.java | 63 + .../microsoft/bot/dialogs/DialogEvents.java | 29 + .../microsoft/bot/dialogs/DialogInstance.java | 109 + .../microsoft/bot/dialogs/DialogManager.java | 408 ++++ .../bot/dialogs/DialogManagerResult.java | 68 + .../com/microsoft/bot/dialogs/DialogPath.java | 64 + .../microsoft/bot/dialogs/DialogReason.java | 27 + .../com/microsoft/bot/dialogs/DialogSet.java | 214 ++ .../microsoft/bot/dialogs/DialogState.java | 48 + .../bot/dialogs/DialogTurnResult.java | 90 + .../bot/dialogs/DialogTurnStatus.java | 26 + .../dialogs/DialogsComponentRegistration.java | 67 + .../com/microsoft/bot/dialogs/IDialog.java | 19 - .../bot/dialogs/IDialogContinue.java | 21 - .../microsoft/bot/dialogs/MessageOptions.java | 57 - .../com/microsoft/bot/dialogs/ObjectPath.java | 865 ++++++++ .../microsoft/bot/dialogs/PersistedState.java | 74 + .../bot/dialogs/PersistedStateKeys.java | 48 + .../com/microsoft/bot/dialogs/ScopePath.java | 58 + .../com/microsoft/bot/dialogs/ThisPath.java | 19 + .../com/microsoft/bot/dialogs/TurnPath.java | 57 + .../bot/dialogs/WaterfallDialog.java | 277 +++ .../microsoft/bot/dialogs/WaterfallStep.java | 23 + .../bot/dialogs/WaterfallStepContext.java | 132 ++ .../bot/dialogs/choices/Channel.java | 145 ++ .../microsoft/bot/dialogs/choices/Choice.java | 85 + .../bot/dialogs/choices/ChoiceFactory.java | 399 ++++ .../dialogs/choices/ChoiceFactoryOptions.java | 173 ++ .../dialogs/choices/ChoiceRecognizers.java | 141 ++ .../microsoft/bot/dialogs/choices/Find.java | 311 +++ .../dialogs/choices/FindChoicesOptions.java | 96 + .../dialogs/choices/FindValuesOptions.java | 100 + .../bot/dialogs/choices/FoundChoice.java | 91 + .../bot/dialogs/choices/FoundValue.java | 73 + .../bot/dialogs/choices/ListStyle.java | 28 + .../bot/dialogs/choices/ModelResult.java | 99 + .../bot/dialogs/choices/SortedValue.java | 59 + .../microsoft/bot/dialogs/choices/Token.java | 92 + .../bot/dialogs/choices/Tokenizer.java | 86 + .../dialogs/choices/TokenizerFunction.java | 21 + .../bot/dialogs/choices/package-info.java | 8 + .../dialogs/memory/ComponentMemoryScopes.java | 19 + .../memory/ComponentPathResolvers.java | 15 + .../dialogs/memory/DialogStateManager.java | 834 ++++++++ .../DialogStateManagerConfiguration.java | 53 + .../bot/dialogs/memory/PathResolver.java | 16 + .../PathResolvers/AliasPathResolver.java | 90 + .../PathResolvers/AtAtPathResolver.java | 17 + .../memory/PathResolvers/AtPathResolver.java | 53 + .../PathResolvers/DollarPathResolver.java | 17 + .../PathResolvers/HashPathResolver.java | 17 + .../PathResolvers/PercentPathResolver.java | 17 + .../memory/PathResolvers/package-info.java | 8 + .../bot/dialogs/memory/package-info.java | 8 + .../memory/scopes/BotStateMemoryScope.java | 91 + .../memory/scopes/ClassMemoryScope.java | 47 + .../scopes/ConversationMemoryScope.java | 19 + .../memory/scopes/DialogClassMemoryScope.java | 56 + .../scopes/DialogContextMemoryScope.java | 84 + .../memory/scopes/DialogMemoryScope.java | 82 + .../dialogs/memory/scopes/MemoryScope.java | 114 + .../dialogs/memory/scopes/ReadOnlyObject.java | 129 ++ .../memory/scopes/SettingsMemoryScope.java | 73 + .../memory/scopes/ThisMemoryScope.java | 44 + .../memory/scopes/TurnMemoryScope.java | 57 + .../memory/scopes/UserMemoryScope.java | 19 + .../dialogs/memory/scopes/package-info.java | 8 + .../microsoft/bot/dialogs/package-info.java | 8 + .../bot/dialogs/prompts/ActivityPrompt.java | 290 +++ .../bot/dialogs/prompts/AttachmentPrompt.java | 118 + .../microsoft/bot/dialogs/prompts/Choice.java | 43 - .../bot/dialogs/prompts/ChoicePrompt.java | 311 +++ .../bot/dialogs/prompts/ConfirmPrompt.java | 353 +++ .../bot/dialogs/prompts/DateTimePrompt.java | 186 ++ .../dialogs/prompts/DateTimeResolution.java | 99 + .../bot/dialogs/prompts/NumberPrompt.java | 252 +++ .../microsoft/bot/dialogs/prompts/Prompt.java | 382 ++++ .../dialogs/prompts/PromptCultureModel.java | 133 ++ .../dialogs/prompts/PromptCultureModels.java | 308 +++ .../bot/dialogs/prompts/PromptOptions.java | 107 + .../prompts/PromptRecognizerResult.java | 81 + .../bot/dialogs/prompts/PromptValidator.java | 28 + .../prompts/PromptValidatorContext.java | 93 + .../bot/dialogs/prompts/TextPrompt.java | 135 ++ .../bot/dialogs/prompts/package-info.java | 8 + .../microsoft/recognizers/text/Culture.java | 41 + .../recognizers/text/CultureInfo.java | 14 + .../recognizers/text/ExtendedModelResult.java | 19 + .../recognizers/text/ExtractResult.java | 103 + .../recognizers/text/IExtractor.java | 7 + .../microsoft/recognizers/text/IModel.java | 10 + .../microsoft/recognizers/text/IParser.java | 5 + .../microsoft/recognizers/text/Metadata.java | 49 + .../recognizers/text/ModelFactory.java | 81 + .../recognizers/text/ModelResult.java | 20 + .../recognizers/text/ParseResult.java | 46 + .../recognizers/text/Recognizer.java | 42 + .../recognizers/text/ResolutionKey.java | 10 + .../text/choice/ChoiceOptions.java | 5 + .../text/choice/ChoiceRecognizer.java | 74 + .../recognizers/text/choice/Constants.java | 8 + .../config/BooleanParserConfiguration.java | 19 + .../config/IChoiceParserConfiguration.java | 7 + .../EnglishBooleanExtractorConfiguration.java | 70 + .../choice/extractors/BooleanExtractor.java | 9 + .../extractors/ChoiceExtractDataResult.java | 19 + .../choice/extractors/ChoiceExtractor.java | 170 ++ .../IBooleanExtractorConfiguration.java | 9 + .../IChoiceExtractorConfiguration.java | 16 + .../text/choice/models/BooleanModel.java | 43 + .../text/choice/models/ChoiceModel.java | 43 + .../text/choice/parsers/BooleanParser.java | 9 + .../text/choice/parsers/ChoiceParser.java | 43 + .../parsers/OptionsOtherMatchParseResult.java | 14 + .../parsers/OptionsParseDataResult.java | 15 + .../text/choice/resources/ChineseChoice.java | 23 + .../text/choice/resources/EnglishChoice.java | 23 + .../text/choice/resources/FrenchChoice.java | 23 + .../choice/resources/PortugueseChoice.java | 23 + .../text/choice/resources/SpanishChoice.java | 23 + .../text/choice/utilities/UnicodeUtils.java | 29 + .../recognizers/text/datetime/Constants.java | 182 ++ .../text/datetime/DatePeriodTimexType.java | 8 + .../text/datetime/DateTimeOptions.java | 26 + .../text/datetime/DateTimeRecognizer.java | 93 + .../text/datetime/DateTimeResolutionKey.java | 11 + .../text/datetime/TimeTypeConstants.java | 18 + .../config/BaseOptionsConfiguration.java | 31 + .../config/IOptionsConfiguration.java | 10 + .../EnglishDateExtractorConfiguration.java | 241 +++ ...glishDatePeriodExtractorConfiguration.java | 311 +++ ...lishDateTimeAltExtractorConfiguration.java | 90 + ...EnglishDateTimeExtractorConfiguration.java | 160 ++ ...hDateTimePeriodExtractorConfiguration.java | 281 +++ ...EnglishDurationExtractorConfiguration.java | 138 ++ .../EnglishHolidayExtractorConfiguration.java | 31 + .../EnglishMergedExtractorConfiguration.java | 220 ++ .../EnglishSetExtractorConfiguration.java | 120 ++ .../EnglishTimeExtractorConfiguration.java | 140 ++ ...glishTimePeriodExtractorConfiguration.java | 132 ++ ...EnglishTimeZoneExtractorConfiguration.java | 93 + ...lishCommonDateTimeParserConfiguration.java | 290 +++ .../EnglishDateParserConfiguration.java | 351 +++ .../EnglishDatePeriodParserConfiguration.java | 575 +++++ ...EnglishDateTimeAltParserConfiguration.java | 48 + .../EnglishDateTimeParserConfiguration.java | 256 +++ ...lishDateTimePeriodParserConfiguration.java | 337 +++ .../EnglishDurationParserConfiguration.java | 145 ++ .../EnglishHolidayParserConfiguration.java | 225 ++ .../EnglishMergedParserConfiguration.java | 77 + .../EnglishSetParserConfiguration.java | 251 +++ .../EnglishTimeParserConfiguration.java | 187 ++ .../EnglishTimePeriodParserConfiguration.java | 159 ++ .../datetime/english/parsers/TimeParser.java | 63 + .../EnglishDatetimeUtilityConfiguration.java | 77 + .../extractors/AbstractYearExtractor.java | 108 + .../extractors/BaseDateExtractor.java | 478 +++++ .../extractors/BaseDatePeriodExtractor.java | 464 ++++ .../extractors/BaseDateTimeAltExtractor.java | 598 ++++++ .../extractors/BaseDateTimeExtractor.java | 292 +++ .../BaseDateTimePeriodExtractor.java | 575 +++++ .../extractors/BaseDurationExtractor.java | 281 +++ .../extractors/BaseHolidayExtractor.java | 60 + .../BaseMergedDateTimeExtractor.java | 361 ++++ .../datetime/extractors/BaseSetExtractor.java | 160 ++ .../extractors/BaseTimeExtractor.java | 149 ++ .../extractors/BaseTimePeriodExtractor.java | 275 +++ .../extractors/BaseTimeZoneExtractor.java | 133 ++ .../datetime/extractors/IDateExtractor.java | 7 + .../extractors/IDateTimeExtractor.java | 13 + .../extractors/IDateTimeListExtractor.java | 12 + .../extractors/IDateTimeZoneExtractor.java | 9 + .../config/IDateExtractorConfiguration.java | 62 + .../IDatePeriodExtractorConfiguration.java | 79 + .../IDateTimeAltExtractorConfiguration.java | 24 + .../IDateTimeExtractorConfiguration.java | 48 + ...IDateTimePeriodExtractorConfiguration.java | 81 + .../IDurationExtractorConfiguration.java | 45 + .../IHolidayExtractorConfiguration.java | 7 + .../config/IMergedExtractorConfiguration.java | 68 + .../config/ISetExtractorConfiguration.java | 38 + .../config/ITimeExtractorConfiguration.java | 18 + .../ITimePeriodExtractorConfiguration.java | 31 + .../ITimeZoneExtractorConfiguration.java | 19 + .../config/ProcessedSuperfluousWords.java | 23 + .../extractors/config/ResultIndex.java | 27 + .../extractors/config/ResultTimex.java | 27 + .../FrenchDateExtractorConfiguration.java | 245 +++ ...renchDatePeriodExtractorConfiguration.java | 328 +++ ...enchDateTimeAltExtractorConfiguration.java | 86 + .../FrenchDateTimeExtractorConfiguration.java | 171 ++ ...hDateTimePeriodExtractorConfiguration.java | 283 +++ .../FrenchDurationExtractorConfiguration.java | 139 ++ .../FrenchHolidayExtractorConfiguration.java | 38 + .../FrenchMergedExtractorConfiguration.java | 194 ++ .../FrenchSetExtractorConfiguration.java | 113 + .../FrenchTimeExtractorConfiguration.java | 87 + ...renchTimePeriodExtractorConfiguration.java | 150 ++ .../FrenchTimeZoneExtractorConfiguration.java | 43 + ...enchCommonDateTimeParserConfiguration.java | 292 +++ .../FrenchDateParserConfiguration.java | 336 +++ .../FrenchDatePeriodParserConfiguration.java | 559 +++++ .../FrenchDateTimeAltParserConfiguration.java | 48 + .../FrenchDateTimeParserConfiguration.java | 258 +++ ...enchDateTimePeriodParserConfiguration.java | 331 +++ .../FrenchDurationParserConfiguration.java | 144 ++ .../FrenchHolidayParserConfiguration.java | 226 ++ .../FrenchMergedParserConfiguration.java | 75 + .../parsers/FrenchSetParserConfiguration.java | 195 ++ .../french/parsers/FrenchTimeParser.java | 57 + .../FrenchTimeParserConfiguration.java | 156 ++ .../FrenchTimePeriodParserConfiguration.java | 158 ++ .../FrenchDatetimeUtilityConfiguration.java | 87 + .../text/datetime/models/DateTimeModel.java | 94 + .../text/datetime/parsers/BaseDateParser.java | 739 +++++++ .../parsers/BaseDatePeriodParser.java | 1897 +++++++++++++++++ .../parsers/BaseDateTimeAltParser.java | 256 +++ .../datetime/parsers/BaseDateTimeParser.java | 398 ++++ .../parsers/BaseDateTimePeriodParser.java | 1036 +++++++++ .../datetime/parsers/BaseDurationParser.java | 414 ++++ .../datetime/parsers/BaseHolidayParser.java | 204 ++ .../BaseHolidayParserConfiguration.java | 163 ++ .../parsers/BaseMergedDateTimeParser.java | 834 ++++++++ .../text/datetime/parsers/BaseSetParser.java | 251 +++ .../text/datetime/parsers/BaseTimeParser.java | 358 ++++ .../parsers/BaseTimePeriodParser.java | 697 ++++++ .../datetime/parsers/BaseTimeZoneParser.java | 168 ++ .../datetime/parsers/DateTimeParseResult.java | 37 + .../datetime/parsers/IDateTimeParser.java | 15 + .../config/BaseDateParserConfiguration.java | 17 + .../ICommonDateTimeParserConfiguration.java | 80 + .../config/IDateParserConfiguration.java | 100 + .../IDatePeriodParserConfiguration.java | 155 ++ .../IDateTimeAltParserConfiguration.java | 17 + .../config/IDateTimeParserConfiguration.java | 70 + .../IDateTimePeriodParserConfiguration.java | 84 + .../config/IDurationParserConfiguration.java | 44 + .../config/IHolidayParserConfiguration.java | 24 + .../config/IMergedParserConfiguration.java | 29 + .../config/ISetParserConfiguration.java | 58 + .../config/ITimeParserConfiguration.java | 26 + .../ITimePeriodParserConfiguration.java | 40 + .../config/MatchedTimeRangeResult.java | 57 + .../parsers/config/PrefixAdjustResult.java | 13 + .../parsers/config/SuffixAdjustResult.java | 17 + .../text/datetime/resources/BaseDateTime.java | 111 + .../datetime/resources/ChineseDateTime.java | 1016 +++++++++ .../datetime/resources/EnglishDateTime.java | 1446 +++++++++++++ .../datetime/resources/EnglishTimeZone.java | 374 ++++ .../datetime/resources/FrenchDateTime.java | 1234 +++++++++++ .../resources/PortugueseDateTime.java | 983 +++++++++ .../datetime/resources/SpanishDateTime.java | 1204 +++++++++++ .../SpanishDateExtractorConfiguration.java | 245 +++ ...anishDatePeriodExtractorConfiguration.java | 320 +++ ...nishDateTimeAltExtractorConfiguration.java | 90 + ...SpanishDateTimeExtractorConfiguration.java | 170 ++ ...hDateTimePeriodExtractorConfiguration.java | 283 +++ ...SpanishDurationExtractorConfiguration.java | 139 ++ .../SpanishHolidayExtractorConfiguration.java | 36 + .../SpanishMergedExtractorConfiguration.java | 198 ++ .../SpanishSetExtractorConfiguration.java | 119 ++ .../SpanishTimeExtractorConfiguration.java | 132 ++ ...anishTimePeriodExtractorConfiguration.java | 154 ++ ...SpanishTimeZoneExtractorConfiguration.java | 44 + .../spanish/parsers/DateTimePeriodParser.java | 133 ++ ...nishCommonDateTimeParserConfiguration.java | 287 +++ .../SpanishDateParserConfiguration.java | 336 +++ .../SpanishDatePeriodParserConfiguration.java | 621 ++++++ ...SpanishDateTimeAltParserConfiguration.java | 48 + .../SpanishDateTimeParserConfiguration.java | 237 ++ ...nishDateTimePeriodParserConfiguration.java | 347 +++ .../SpanishDurationParserConfiguration.java | 145 ++ .../SpanishHolidayParserConfiguration.java | 137 ++ .../SpanishMergedParserConfiguration.java | 76 + .../SpanishSetParserConfiguration.java | 218 ++ .../SpanishTimeParserConfiguration.java | 161 ++ .../SpanishTimePeriodParserConfiguration.java | 152 ++ .../SpanishDatetimeUtilityConfiguration.java | 86 + .../text/datetime/utilities/AgoLaterUtil.java | 197 ++ .../datetime/utilities/ConditionalMatch.java | 27 + .../text/datetime/utilities/DateContext.java | 165 ++ .../utilities/DateTimeFormatUtil.java | 248 +++ .../utilities/DateTimeResolutionResult.java | 134 ++ .../text/datetime/utilities/DateUtil.java | 156 ++ .../utilities/DurationParsingUtil.java | 187 ++ .../utilities/GetModAndDateResult.java | 22 + .../datetime/utilities/HolidayFunctions.java | 31 + .../IDateTimeUtilityConfiguration.java | 28 + .../utilities/MatchedTimexResult.java | 31 + .../text/datetime/utilities/MatchingUtil.java | 106 + .../utilities/MatchingUtilResult.java | 15 + .../utilities/NthBusinessDayResult.java | 14 + .../utilities/RangeTimexComponents.java | 11 + .../datetime/utilities/RegexExtension.java | 58 + .../datetime/utilities/StringExtension.java | 13 + .../utilities/TimeOfDayResolutionResult.java | 50 + .../utilities/TimeZoneResolutionResult.java | 26 + .../datetime/utilities/TimeZoneUtility.java | 64 + .../text/datetime/utilities/TimexUtility.java | 314 +++ .../text/datetime/utilities/Token.java | 87 + .../recognizers/text/matcher/AaNode.java | 53 + .../text/matcher/AbstractMatcher.java | 21 + .../text/matcher/AcAutomation.java | 89 + .../recognizers/text/matcher/IMatcher.java | 10 + .../recognizers/text/matcher/ITokenizer.java | 7 + .../recognizers/text/matcher/MatchResult.java | 66 + .../text/matcher/MatchStrategy.java | 6 + .../recognizers/text/matcher/Node.java | 43 + .../text/matcher/NumberWithUnitTokenizer.java | 86 + .../text/matcher/SimpleTokenizer.java | 78 + .../text/matcher/StringMatcher.java | 99 + .../recognizers/text/matcher/Token.java | 30 + .../recognizers/text/matcher/TrieTree.java | 67 + .../recognizers/text/number/Constants.java | 21 + .../text/number/LongFormatType.java | 53 + .../recognizers/text/number/NumberMode.java | 12 + .../text/number/NumberOptions.java | 16 + .../text/number/NumberRangeConstants.java | 21 + .../text/number/NumberRecognizer.java | 217 ++ .../chinese/ChineseNumberExtractorMode.java | 18 + .../chinese/extractors/CardinalExtractor.java | 41 + .../chinese/extractors/DoubleExtractor.java | 48 + .../chinese/extractors/FractionExtractor.java | 39 + .../chinese/extractors/IntegerExtractor.java | 66 + .../chinese/extractors/NumberExtractor.java | 63 + .../extractors/NumberRangeExtractor.java | 78 + .../chinese/extractors/OrdinalExtractor.java | 38 + .../extractors/PercentageExtractor.java | 99 + .../ChineseNumberParserConfiguration.java | 82 + ...ChineseNumberRangeParserConfiguration.java | 96 + .../english/extractors/CardinalExtractor.java | 66 + .../english/extractors/DoubleExtractor.java | 78 + .../english/extractors/FractionExtractor.java | 74 + .../english/extractors/IntegerExtractor.java | 75 + .../english/extractors/NumberExtractor.java | 116 + .../extractors/NumberRangeExtractor.java | 56 + .../english/extractors/OrdinalExtractor.java | 65 + .../extractors/PercentageExtractor.java | 47 + .../EnglishNumberParserConfiguration.java | 105 + ...EnglishNumberRangeParserConfiguration.java | 97 + .../extractors/BaseNumberExtractor.java | 155 ++ .../extractors/BaseNumberRangeExtractor.java | 232 ++ .../extractors/BasePercentageExtractor.java | 241 +++ .../number/extractors/PreProcessResult.java | 18 + .../french/extractors/CardinalExtractor.java | 55 + .../french/extractors/DoubleExtractor.java | 48 + .../french/extractors/FractionExtractor.java | 41 + .../french/extractors/IntegerExtractor.java | 47 + .../french/extractors/NumberExtractor.java | 106 + .../french/extractors/OrdinalExtractor.java | 35 + .../extractors/PercentageExtractor.java | 41 + .../FrenchNumberParserConfiguration.java | 105 + .../german/extractors/CardinalExtractor.java | 55 + .../german/extractors/DoubleExtractor.java | 48 + .../german/extractors/FractionExtractor.java | 42 + .../german/extractors/IntegerExtractor.java | 49 + .../german/extractors/NumberExtractor.java | 110 + .../german/extractors/OrdinalExtractor.java | 37 + .../extractors/PercentageExtractor.java | 39 + .../GermanNumberParserConfiguration.java | 113 + .../number/models/AbstractNumberModel.java | 63 + .../text/number/models/NumberModel.java | 17 + .../text/number/models/NumberRangeModel.java | 17 + .../text/number/models/OrdinalModel.java | 17 + .../text/number/models/PercentModel.java | 17 + .../parsers/AgnosticNumberParserFactory.java | 52 + .../parsers/AgnosticNumberParserType.java | 11 + .../number/parsers/BaseCJKNumberParser.java | 498 +++++ .../BaseCJKNumberParserConfiguration.java | 314 +++ .../text/number/parsers/BaseNumberParser.java | 635 ++++++ .../BaseNumberParserConfiguration.java | 171 ++ .../number/parsers/BaseNumberRangeParser.java | 224 ++ .../number/parsers/BasePercentageParser.java | 67 + .../ICJKNumberParserConfiguration.java | 57 + .../parsers/INumberParserConfiguration.java | 79 + .../INumberRangeParserConfiguration.java | 29 + .../number/parsers/NumberFormatUtility.java | 94 + .../extractors/CardinalExtractor.java | 56 + .../extractors/DoubleExtractor.java | 47 + .../extractors/FractionExtractor.java | 42 + .../extractors/IntegerExtractor.java | 48 + .../extractors/NumberExtractor.java | 104 + .../extractors/OrdinalExtractor.java | 35 + .../extractors/PercentageExtractor.java | 39 + .../PortugueseNumberParserConfiguration.java | 142 ++ .../text/number/resources/BaseNumbers.java | 48 + .../text/number/resources/ChineseNumeric.java | 522 +++++ .../text/number/resources/EnglishNumeric.java | 504 +++++ .../text/number/resources/FrenchNumeric.java | 508 +++++ .../text/number/resources/GermanNumeric.java | 512 +++++ .../number/resources/PortugueseNumeric.java | 536 +++++ .../text/number/resources/SpanishNumeric.java | 629 ++++++ .../spanish/extractors/CardinalExtractor.java | 56 + .../spanish/extractors/DoubleExtractor.java | 47 + .../spanish/extractors/FractionExtractor.java | 42 + .../spanish/extractors/IntegerExtractor.java | 64 + .../spanish/extractors/NumberExtractor.java | 104 + .../spanish/extractors/OrdinalExtractor.java | 52 + .../extractors/PercentageExtractor.java | 38 + .../SpanishNumberParserConfiguration.java | 129 ++ .../text/numberwithunit/Constants.java | 19 + .../numberwithunit/NumberWithUnitOptions.java | 15 + .../NumberWithUnitRecognizer.java | 256 +++ .../extractors/AgeExtractorConfiguration.java | 43 + ...eNumberWithUnitExtractorConfiguration.java | 112 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 58 + .../parsers/AgeParserConfiguration.java | 19 + ...neseNumberWithUnitParserConfiguration.java | 39 + .../parsers/CurrencyParserConfiguration.java | 32 + .../parsers/DimensionParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../extractors/AgeExtractorConfiguration.java | 45 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...hNumberWithUnitExtractorConfiguration.java | 77 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 32 + .../parsers/DimensionParserConfiguration.java | 18 + ...lishNumberWithUnitParserConfiguration.java | 37 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../extractors/BaseMergedUnitExtractor.java | 185 ++ ...INumberWithUnitExtractorConfiguration.java | 38 + .../extractors/NumberWithUnitExtractor.java | 427 ++++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...hNumberWithUnitExtractorConfiguration.java | 77 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + ...enchNumberWithUnitParserConfiguration.java | 38 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 23 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + ...nNumberWithUnitExtractorConfiguration.java | 75 + .../LengthExtractorConfiguration.java | 43 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 18 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 18 + .../parsers/DimensionParserConfiguration.java | 18 + ...rmanNumberWithUnitParserConfiguration.java | 37 + .../parsers/LengthParserConfiguration.java | 18 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../models/AbstractNumberWithUnitModel.java | 103 + .../text/numberwithunit/models/AgeModel.java | 18 + .../numberwithunit/models/CurrencyModel.java | 18 + .../models/CurrencyUnitValue.java | 14 + .../numberwithunit/models/DimensionModel.java | 18 + .../models/PrefixUnitResult.java | 11 + .../models/TemperatureModel.java | 18 + .../text/numberwithunit/models/UnitValue.java | 12 + .../parsers/BaseCurrencyParser.java | 188 ++ .../parsers/BaseMergedUnitParser.java | 27 + ...BaseNumberWithUnitParserConfiguration.java | 72 + .../INumberWithUnitParserConfiguration.java | 30 + .../parsers/NumberWithUnitParser.java | 143 ++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + .../LengthExtractorConfiguration.java | 43 + ...eNumberWithUnitExtractorConfiguration.java | 76 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + .../parsers/LengthParserConfiguration.java | 18 + ...ueseNumberWithUnitParserConfiguration.java | 39 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../resources/BaseCurrency.java | 281 +++ .../numberwithunit/resources/BaseUnits.java | 32 + .../resources/ChineseNumericWithUnit.java | 639 ++++++ .../resources/EnglishNumericWithUnit.java | 721 +++++++ .../resources/FrenchNumericWithUnit.java | 401 ++++ .../resources/GermanNumericWithUnit.java | 451 ++++ .../resources/JapaneseNumericWithUnit.java | 546 +++++ .../resources/PortugueseNumericWithUnit.java | 510 +++++ .../resources/SpanishNumericWithUnit.java | 517 +++++ .../extractors/AgeExtractorConfiguration.java | 43 + .../AreaExtractorConfiguration.java | 43 + .../CurrencyExtractorConfiguration.java | 43 + .../DimensionExtractorConfiguration.java | 51 + .../LengthExtractorConfiguration.java | 43 + ...hNumberWithUnitExtractorConfiguration.java | 76 + .../SpeedExtractorConfiguration.java | 43 + .../TemperatureExtractorConfiguration.java | 56 + .../VolumeExtractorConfiguration.java | 43 + .../WeightExtractorConfiguration.java | 43 + .../parsers/AgeParserConfiguration.java | 19 + .../parsers/AreaParserConfiguration.java | 18 + .../parsers/CurrencyParserConfiguration.java | 19 + .../parsers/DimensionParserConfiguration.java | 18 + .../parsers/LengthParserConfiguration.java | 18 + ...nishNumberWithUnitParserConfiguration.java | 39 + .../parsers/SpeedParserConfiguration.java | 18 + .../TemperatureParserConfiguration.java | 18 + .../parsers/VolumeParserConfiguration.java | 18 + .../parsers/WeightParserConfiguration.java | 18 + .../utilities/DictionaryUtils.java | 41 + .../utilities/StringComparer.java | 27 + .../text/resources/CodeGenerator.java | 132 ++ .../text/resources/ResourceConfig.java | 8 + .../text/resources/ResourceDefinitions.java | 8 + .../text/resources/ResourcesGenerator.java | 49 + .../text/resources/datatypes/Dictionary.java | 8 + .../text/resources/datatypes/List.java | 6 + .../text/resources/datatypes/NestedRegex.java | 6 + .../text/resources/datatypes/ParamsRegex.java | 6 + .../text/resources/datatypes/SimpleRegex.java | 5 + .../resources/writters/BooleanWriter.java | 20 + .../resources/writters/CharacterWriter.java | 20 + .../resources/writters/DefaultWriter.java | 21 + .../resources/writters/DictionaryWriter.java | 94 + .../text/resources/writters/ICodeWriter.java | 36 + .../resources/writters/IntegerWriter.java | 21 + .../text/resources/writters/ListWriter.java | 31 + .../resources/writters/NestedRegexWriter.java | 24 + .../resources/writters/ParamsRegexWriter.java | 29 + .../resources/writters/SimpleRegexWriter.java | 19 + .../recognizers/text/utilities/Capture.java | 13 + .../text/utilities/DefinitionLoader.java | 25 + .../text/utilities/DoubleUtility.java | 12 + .../text/utilities/FormatUtility.java | 83 + .../text/utilities/IntegerUtility.java | 12 + .../recognizers/text/utilities/Match.java | 25 + .../text/utilities/MatchGroup.java | 15 + .../text/utilities/QueryProcessor.java | 120 ++ .../text/utilities/RegExpUtility.java | 419 ++++ .../utilities/StringReplacerCallback.java | 7 + .../text/utilities/StringUtility.java | 27 + .../src/main/resources/naughtyStrings.txt | 742 +++++++ .../bot/dialogs/ComponentDialogTests.java | 557 +++++ .../bot/dialogs/DialogContainerTests.java | 113 + .../bot/dialogs/DialogManagerTests.java | 524 +++++ .../microsoft/bot/dialogs/DialogSetTests.java | 211 ++ .../bot/dialogs/DialogStateManagerTests.java | 266 +++ .../microsoft/bot/dialogs/LamdbaDialog.java | 46 + .../bot/dialogs/MemoryScopeTests.java | 125 ++ .../bot/dialogs/ObjectPathTests.java | 506 +++++ .../bot/dialogs/PromptCultureModelTests.java | 76 + .../dialogs/PromptValidatorContextTests.java | 203 ++ .../bot/dialogs/ReplaceDialogTests.java | 218 ++ .../com/microsoft/bot/dialogs/TestLocale.java | 171 ++ .../microsoft/bot/dialogs/WaterfallTests.java | 545 +++++ .../dialogs/choices/ChoiceFactoryTests.java | 191 ++ .../dialogs/choices/ChoicesChannelTests.java | 133 ++ .../choices/ChoicesRecognizerTests.java | 218 ++ .../choices/ChoicesTokenizerTests.java | 82 + .../dialogs/prompts/ActivityPromptTests.java | 314 +++ .../prompts/AttachmentPromptTests.java | 153 ++ .../ChoicePromptLocaleVariationTests.java | 142 ++ .../dialogs/prompts/ChoicePromptTests.java | 833 ++++++++ .../prompts/ConfirmPromptLocTests.java | 200 ++ .../dialogs/prompts/ConfirmPromptTests.java | 390 ++++ .../dialogs/prompts/DateTimePromptTests.java | 192 ++ .../dialogs/prompts/EventActivityPrompt.java | 28 + .../bot/dialogs/prompts/NumberPromptMock.java | 36 + .../dialogs/prompts/NumberPromptTests.java | 635 ++++++ .../bot/dialogs/prompts/TextPromptTests.java | 325 +++ .../ClasspathPropertiesConfiguration.java | 13 +- .../bot/integration/Configuration.java | 11 +- .../microsoft/bot/schema/Serialization.java | 119 ++ .../microsoft/bot/schema/SignInConstants.java | 3 + .../schema/teams/MeetingParticipantInfo.java | 3 + .../microsoft/bot/schema/ActivityTest.java | 3 + .../microsoft/bot/schema/CardActionTest.java | 3 + pom.xml | 2 +- .../models/AdaptiveCard.java | 3 + .../teamsactionpreview/models/Body.java | 3 + .../teamsactionpreview/models/Choice.java | 3 + .../teamstaskmodule/TeamsTaskModuleBot.java | 2 +- 688 files changed, 79326 insertions(+), 498 deletions(-) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java delete mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/ChineseNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java create mode 100644 libraries/bot-dialogs/src/main/resources/naughtyStrings.txt create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java diff --git a/etc/bot-checkstyle.xml b/etc/bot-checkstyle.xml index 41b3575cb..337417b9e 100644 --- a/etc/bot-checkstyle.xml +++ b/etc/bot-checkstyle.xml @@ -159,7 +159,9 @@ - + + + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index 2d2006e6b..c9d2f64f7 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -13,6 +13,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; /** * Represents a bot adapter that can connect a bot to a service endpoint. This @@ -194,6 +195,10 @@ protected CompletableFuture runPipeline( // Call any registered Middleware Components looking for ReceiveActivity() if (context.getActivity() != null) { + if (!StringUtils.isEmpty(context.getActivity().getLocale())) { + context.setLocale(context.getActivity().getLocale()); + } + return middlewareSet.receiveActivityWithStatus(context, callback) .exceptionally(exception -> { if (onTurnError != null) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java index e0261da7c..5c56dfbff 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java @@ -175,7 +175,9 @@ public CompletableFuture saveChanges(TurnContext turnContext, boolean forc */ public CompletableFuture clearState(TurnContext turnContext) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } turnContext.getTurnState().replace(contextServiceKey, new CachedBotState()); @@ -190,7 +192,9 @@ public CompletableFuture clearState(TurnContext turnContext) { */ public CompletableFuture delete(TurnContext turnContext) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } String storageKey = getStorageKey(turnContext); @@ -220,6 +224,20 @@ public JsonNode get(TurnContext turnContext) { return new ObjectMapper().valueToTree(cachedState.state); } + /** + * Gets the cached bot state instance that wraps the raw cached data for this BotState from the turn context. + * + * @param turnContext The context object for this turn. + * @return The cached bot state instance. + */ + public CachedBotState getCachedState(TurnContext turnContext) { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null"); + } + + return turnContext.getTurnState().get(contextServiceKey); + } + /** * When overridden in a derived class, gets the key to use when reading and * writing state to and from storage. @@ -274,11 +292,15 @@ protected CompletableFuture deletePropertyValue( String propertyName ) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext cannot be null." + )); } if (StringUtils.isEmpty(propertyName)) { - throw new IllegalArgumentException("propertyName cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "propertyName cannot be empty" + )); } CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); @@ -300,11 +322,15 @@ protected CompletableFuture setPropertyValue( Object value ) { if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null." + )); } if (StringUtils.isEmpty(propertyName)) { - throw new IllegalArgumentException("propertyName cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "propertyName cannot be empty" + )); } CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); @@ -315,7 +341,7 @@ protected CompletableFuture setPropertyValue( /** * Internal cached bot state. */ - private static class CachedBotState { + public static class CachedBotState { /** * In memory cache of BotState properties. */ @@ -348,26 +374,46 @@ private static class CachedBotState { hash = computeHash(withState); } - Map getState() { + /** + * @return The Map of key value pairs which are the state. + */ + public Map getState() { return state; } + /** + * @param withState The key value pairs to set the state with. + */ void setState(Map withState) { state = withState; } + /** + * @return The hash value for the state. + */ String getHash() { return hash; } + /** + * @param witHashCode Set the hash value. + */ void setHash(String witHashCode) { hash = witHashCode; } + /** + * + * @return Boolean to tell if the state has changed. + */ boolean isChanged() { return !StringUtils.equals(hash, computeHash(state)); } + /** + * @param obj The object to compute the hash for. + * @return The computed has for the provided object. + */ String computeHash(Object obj) { if (obj == null) { return ""; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java index 4acf819ab..9b0ace749 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotTelemetryClient.java @@ -153,6 +153,18 @@ void trackException( */ void trackTrace(String message, Severity severityLevel, Map properties); + /** + * Log a DialogView using the TrackPageView method on the IBotTelemetryClient if + * IBotPageViewTelemetryClient has been implemented. Alternatively log the information out via + * TrackTrace. + * + * @param dialogName The name of the dialog to log the entry / start for. + * @param properties Named string values you can use to search and classify + * events. + * @param metrics Measurements associated with this event. + */ + void trackDialogView(String dialogName, Map properties, Map metrics); + /** * Flushes the in-memory buffer and any metrics being pre-aggregated. */ diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java new file mode 100644 index 000000000..8c808fe95 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ComponentRegistration.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.ConcurrentHashMap; + +/** + * ComponentRegistration is a signature class for discovering assets from components. + */ +@SuppressWarnings("checkstyle:HideUtilityClassConstructor") +public class ComponentRegistration { + + private static final ConcurrentHashMap, ComponentRegistration> COMPONENTS = + new ConcurrentHashMap, ComponentRegistration>(); + + /** + * Add a component which implements registration methods. + * + * @param componentRegistration The component to add to the registration. + */ + public static void add(ComponentRegistration componentRegistration) { + COMPONENTS.put(componentRegistration.getClass(), componentRegistration); + } + + /** + * Gets list of all ComponentRegistration objects registered. + * + * @return A array of ComponentRegistration objects. + */ + public static Iterable getComponents() { + return COMPONENTS.values(); + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java index 8256ed509..db0999daa 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/DelegatingTurnContext.java @@ -29,6 +29,24 @@ public DelegatingTurnContext(TurnContext withTurnContext) { innerTurnContext = withTurnContext; } + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + @Override + public String getLocale() { + return innerTurnContext.getLocale(); + } + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + @Override + public void setLocale(String withLocale) { + innerTurnContext.setLocale(withLocale); + } + /** * Gets the inner context's activity. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java index f429207c2..ec89525a6 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NextDelegate.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java index 54f8fb6b4..e45217f1b 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/NullBotTelemetryClient.java @@ -68,4 +68,9 @@ public void trackTrace(String message, Severity severityLevel, Map properties, Map metrics) { + + } } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java index 4b5b222c3..3f699e89f 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/OnTurnErrorHandler.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java new file mode 100644 index 000000000..6d4e7ce30 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RegisterClassMiddleware.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.CompletableFuture; + +import com.nimbusds.oauth2.sdk.util.StringUtils; + +/** + * Middleware for adding an object to or registering a service with the current + * turn context. + * + * @param The typeof service to add. + */ +public class RegisterClassMiddleware implements Middleware { + private String key; + + /** + * Initializes a new instance of the RegisterClassMiddleware class. + * + * @param service The Service to register. + */ + public RegisterClassMiddleware(T service) { + this.service = service; + } + + /** + * Initializes a new instance of the RegisterClassMiddleware class. + * + * @param service The Service to register. + * @param key optional key for service object in turn state. Default is name + * of service. + */ + public RegisterClassMiddleware(T service, String key) { + this.service = service; + this.key = key; + } + + private T service; + + /** + * Gets the Service. + * + * @return The Service. + */ + public T getService() { + return service; + } + + /** + * Sets the Service. + * + * @param withService The value to set the Service to. + */ + public void setService(T withService) { + this.service = withService; + } + + @Override + /** + * Adds the associated object or service to the current turn context. + * @param turnContext The context object for this turn. + * @param next The delegate to call to continue the bot middleware pipeline. + */ + public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { + if (!StringUtils.isBlank(key)) { + turnContext.getTurnState().add(key, service); + } else { + turnContext.getTurnState().add(service); + } + return next.next(); + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java index d51ae99be..cea9ebbcf 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java @@ -50,11 +50,31 @@ static CompletableFuture traceActivity( String valueType, String label ) { - return turnContext .sendActivity(turnContext.getActivity().createTrace(name, value, valueType, label)); } + /** + * @param turnContext The turnContext. + * @param name The name of the activity. + * @return A future with the ResourceReponse. + */ + static CompletableFuture traceActivity(TurnContext turnContext, String name) { + return traceActivity(turnContext, name, null, null, null); + } + + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + String getLocale(); + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + void setLocale(String withLocale); + /** * Gets the bot adapter that created this context object. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 050c311a0..2d53e94c3 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -9,6 +9,8 @@ import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.InputHints; import com.microsoft.bot.schema.ResourceResponse; +import java.util.Locale; +import org.apache.commons.lang3.LocaleUtils; import org.apache.commons.lang3.StringUtils; import java.util.ArrayList; @@ -62,6 +64,8 @@ public class TurnContextImpl implements TurnContext, AutoCloseable { */ private Boolean responded = false; + private static final String STATE_TURN_LOCALE = "turn.locale"; + /** * Creates a context object. * @@ -187,6 +191,32 @@ public boolean getResponded() { return responded; } + /** + * Gets the locale on this context object. + * @return The string of locale on this context object. + */ + @Override + public String getLocale() { + return getTurnState().get(STATE_TURN_LOCALE); + } + + /** + * Set the locale on this context object. + * @param withLocale The string of locale on this context object. + */ + @Override + public void setLocale(String withLocale) { + if (StringUtils.isEmpty(withLocale)) { + getTurnState().remove(STATE_TURN_LOCALE); + } else if ( + LocaleUtils.isAvailableLocale(new Locale.Builder().setLanguageTag(withLocale).build()) + ) { + getTurnState().replace(STATE_TURN_LOCALE, withLocale); + } else { + getTurnState().replace(STATE_TURN_LOCALE, Locale.ENGLISH.getCountry()); + } + } + /** * Sends a message activity to the sender of the incoming activity. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java index c1f5ff73e..f42e8b5f4 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextStateCollection.java @@ -39,21 +39,30 @@ public T get(String key) throws IllegalArgumentException { } } + /** + * Returns the Services stored in the TurnContextStateCollection. + * @return the Map of String, Object pairs that contains the names and services for this collection. + */ + public Map getTurnStateServices() { + return state; + } + + /** * Get a service by type using its full type name as the key. * * @param type The type of service to be retrieved. This will use the value - * returned by Class.getSimpleName as the key. + * returned by Class.getName as the key. * @param The type of the value. * @return The service stored under the specified key. */ public T get(Class type) { - return get(type.getSimpleName()); + return get(type.getName()); } /** * Adds a value to the turn's context. - * + * * @param key The name of the value. * @param value The value to add. * @param The type of the value. @@ -76,7 +85,7 @@ public void add(String key, T value) throws IllegalArgumentException { } /** - * Add a service using its type name ({@link Class#getSimpleName()} as the key. + * Add a service using its type name ({@link Class#getName()} as the key. * * @param value The service to add. * @param The type of the value. @@ -87,12 +96,12 @@ public void add(T value) throws IllegalArgumentException { throw new IllegalArgumentException("value"); } - add(value.getClass().getSimpleName(), value); + add(value.getClass().getName(), value); } /** * Removes a value. - * + * * @param key The name of the value to remove. */ public void remove(String key) { @@ -101,7 +110,7 @@ public void remove(String key) { /** * Replaces a value. - * + * * @param key The name of the value to replace. * @param value The new value. */ @@ -110,6 +119,26 @@ public void replace(String key, Object value) { add(key, value); } + /** + * Replaces a value. + * @param value The service to add. + * @param The type of the value. + */ + public void replace(T value) { + String key = value.getClass().getName(); + replace(key, value); + } + + /** + * Returns true if this contains a mapping for the specified + * key. + * @param key The name of the value. + * @return True if the key exists. + */ + public boolean containsKey(String key) { + return state.containsKey(key); + } + /** * Auto call of {@link #close}. */ @@ -124,7 +153,7 @@ public void finalize() { /** * Close all contained {@link AutoCloseable} values. - * + * * @throws Exception Exceptions encountered by children during close. */ @Override @@ -138,4 +167,16 @@ public void close() throws Exception { } } } + + /** + * Copy the values from another TurnContextStateCollection. + * @param other The collection to copy. + */ + public void copy(TurnContextStateCollection other) { + if (other != null) { + for (String key : other.state.keySet()) { + state.put(key, other.state.get(key)); + } + } + } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 6b6d7590f..4b358b2db 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.schema.*; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java index af3ec53d8..4c68ed00f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.databind.JsonNode; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java index 959f73147..16ac34da1 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateSetTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import org.junit.Assert; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java index a20b7bb23..a9ad40382 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java @@ -61,7 +61,7 @@ public void ScenarioWithInspectionMiddlewareOpenAttach() throws IOException { // (1) send the /INSPECT open command from the emulator to the middleware Activity openActivity = MessageFactory.text("/INSPECT open"); - TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST); + TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST, true); inspectionAdapter.processActivity(openActivity, turnContext -> { inspectionMiddleware.processCommand(turnContext).join(); return CompletableFuture.completedFuture(null); @@ -164,7 +164,7 @@ public void ScenarioWithInspectionMiddlewareOpenAttachWithMention() throws IOExc // (1) send the /INSPECT open command from the emulator to the middleware Activity openActivity = MessageFactory.text("/INSPECT open"); - TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST); + TestAdapter inspectionAdapter = new TestAdapter(Channels.TEST, true); inspectionAdapter.processActivity(openActivity, turnContext -> { inspectionMiddleware.processCommand(turnContext).join(); return CompletableFuture.completedFuture(null); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java index d18d99dc4..809dab22c 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Attachments; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java index 32da8e319..24027f868 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Conversations; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java index 00d6fa233..f8b64a8db 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryTranscriptTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import org.junit.Test; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java index 06199d2ad..17ff38043 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MentionTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java index b8fa3bec7..babf68f8c 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java index fe949002e..19c23e5c7 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockAppCredentials.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import java.util.concurrent.CompletableFuture; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java index 757c51894..aa06621de 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MockConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.connector.Attachments; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java index 1dc72d537..9fd3e5287 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java index 6e1393961..daa681438 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ShowTypingMiddlewareTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java index 126a1aabb..8326fe099 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java index 7514e9c0d..b6be7ecc3 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.microsoft.bot.builder.adapters.TestAdapter; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java index 08740c97e..ce3581070 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptBaseTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder; import com.codepoetics.protonpack.collectors.CompletableFutures; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 6153529d6..d4425747b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -19,6 +19,8 @@ public class TestAdapter extends BotAdapter { private final Queue botReplies = new LinkedList<>(); private int nextId = 0; private ConversationReference conversationReference; + private String locale; + private boolean sendTraceActivity = false; private static class UserTokenKey { public String connectionName; @@ -54,6 +56,11 @@ public TestAdapter() { } public TestAdapter(String channelId) { + this(channelId, false); + } + + public TestAdapter(String channelId, boolean sendTraceActivity) { + this.sendTraceActivity = sendTraceActivity; setConversationReference(new ConversationReference() { { setChannelId(channelId); @@ -77,6 +84,7 @@ public TestAdapter(String channelId) { setId("Conversation1"); } }); + setLocale(this.getLocale()); } }); } @@ -108,6 +116,7 @@ public TestAdapter(ConversationReference reference) { setId("Conversation1"); } }); + setLocale(this.getLocale()); } }); } @@ -123,6 +132,35 @@ public TestAdapter use(Middleware middleware) { return this; } + /** + * Adds middleware to the adapter to register an Storage object on the turn context. + * The middleware registers the state objects on the turn context at the start of each turn. + * @param storage The storage object to register. + * @return The updated adapter. + */ + public TestAdapter useStorage(Storage storage) { + if (storage == null) { + throw new IllegalArgumentException("Storage cannot be null"); + } + return this.use(new RegisterClassMiddleware(storage)); + } + + /** + * Adds middleware to the adapter to register one or more BotState objects on the turn context. + * The middleware registers the state objects on the turn context at the start of each turn. + * @param botstates The state objects to register. + * @return The updated adapter. + */ + public TestAdapter useBotState(BotState... botstates) { + if (botstates == null) { + throw new IllegalArgumentException("botstates cannot be null"); + } + for (BotState botState : botstates) { + this.use(new RegisterClassMiddleware(botState)); + } + return this; + } + public CompletableFuture processActivity(Activity activity, BotCallbackHandler callback) { return CompletableFuture.supplyAsync(() -> { synchronized (conversationReference()) { @@ -149,6 +187,9 @@ public CompletableFuture processActivity(Activity activity, BotCallbackHan if (activity.getTimestamp() == null || activity.getTimestamp().toEpochSecond() == 0) activity.setTimestamp(OffsetDateTime.now(ZoneId.of("UTC"))); + if (activity.getLocalTimestamp() == null || activity.getLocalTimestamp().toEpochSecond() == 0) + activity.setLocalTimestamp(OffsetDateTime.now()); + return activity; }).thenCompose(activity1 -> { TurnContextImpl context = new TurnContextImpl(this, activity1); @@ -210,6 +251,12 @@ public CompletableFuture sendActivities( Thread.sleep(delayMs); } catch (InterruptedException e) { } + } else if (activity.getType() == ActivityTypes.TRACE) { + if (sendTraceActivity) { + synchronized (botReplies) { + botReplies.add(activity); + } + } } else { synchronized (botReplies) { botReplies.add(activity); @@ -452,4 +499,31 @@ public CompletableFuture> getAadTokens( ) { return CompletableFuture.completedFuture(new HashMap<>()); } + + public static ConversationReference createConversationReference(String name, String user, String bot) { + ConversationReference reference = new ConversationReference(); + reference.setChannelId("test"); + reference.setServiceUrl("https://test.com"); + reference.setConversation(new ConversationAccount(false, name, name, null, null, null, null)); + reference.setUser(new ChannelAccount(user.toLowerCase(), user.toLowerCase())); + reference.setBot(new ChannelAccount(bot.toLowerCase(), bot.toLowerCase())); + reference.setLocale("en-us"); + return reference; + } + + public void setLocale(String locale) { + this.locale = locale; + } + + public String getLocale() { + return locale; + } + + public void setSendTraceActivity(boolean sendTraceActivity) { + this.sendTraceActivity = sendTraceActivity; + } + + public boolean getSendTraceActivity() { + return sendTraceActivity; + } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java index 77e60f6a7..8672bc282 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestFlow.java @@ -73,7 +73,19 @@ public TestFlow send(String userSays) throws IllegalArgumentException { } /** - * Send an activity from the user to the bot + * Creates a conversation update activity and process it the activity. + * @return A new TestFlow Object + */ + public TestFlow sendConverationUpdate() { + return new TestFlow(testTask.thenCompose(result -> { + Activity cu = Activity.createConversationUpdateActivity(); + cu.getMembersAdded().add(this.adapter.conversationReference().getUser()); + return this.adapter.processActivity(cu, callback); + }), this); + } + + /** + * Send an activity from the user to the bot. * * @param userActivity * @return diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java index d16405e2b..9160ae1f5 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.builder.teams; import com.microsoft.bot.builder.ActivityHandler; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java index 0da52675c..b2bae63ee 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java @@ -26,6 +26,11 @@ private Channels() { */ public static final String DIRECTLINE = "directline"; + /** + * Direct Line Speech channel. + */ + public static final String DIRECTLINESPEECH = "directlinespeech"; + /** * Email channel. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java index bd45a8675..503710b8e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Conversations.java @@ -194,7 +194,9 @@ CompletableFuture replyToActivity( */ default CompletableFuture replyToActivity(Activity activity) { if (StringUtils.isEmpty(activity.getReplyToId())) { - throw new IllegalArgumentException("ReplyToId cannot be empty"); + return Async.completeExceptionally(new IllegalArgumentException( + "ReplyToId cannot be empty" + )); } return replyToActivity( @@ -228,7 +230,7 @@ default CompletableFuture replyToActivity(Activity activity) { /** * Retrieves a single member of a conversation by ID. - * + * * @param userId The user id. * @param conversationId The conversation id. * @return The ChannelAccount for the user. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java index eadfd0a48..9a9b6cce2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java index e32abb937..ae70776f2 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.AuthenticationConstants; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java index 6c21854c9..64da23a36 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java @@ -141,6 +141,11 @@ private AuthenticationConstants() { */ public static final String APPID_CLAIM = "appid"; + /** + * AppId used for creating skill claims when there is no appId and password configured. + */ + public static final String ANONYMOUS_SKILL_APPID = "AnonymousSkill"; + /** * The default clock skew in minutes. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java index 4f43761bd..b876a5d29 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationException.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.authentication; /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java index 48edc6d4b..f4e18086b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.authentication; import com.microsoft.aad.msal4j.ClientCredentialFactory; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java new file mode 100644 index 000000000..813fba475 --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.connector.authentication; + +import java.time.Duration; +import java.util.Map; +import java.util.Optional; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.apache.commons.lang3.StringUtils; + +/** + * Validates JWT tokens sent to and from a Skill. + */ +@SuppressWarnings("PMD") +public final class SkillValidation { + + private SkillValidation() { + + } + + /// + /// TO SKILL FROM BOT and TO BOT FROM SKILL: Token validation parameters when + /// connecting a bot to a skill. + /// + private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = new TokenValidationParameters(true, + Stream.of( + // Auth v3.1, 1.0 token + "https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/", + // Auth v3.1, 2.0 token + "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0", + // Auth v3.2, 1.0 token + "https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + // Auth v3.2, 2.0 token + "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0", + // Auth for US Gov, 1.0 token + "https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/", + // Auth for US Gov, 2.0 token + "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0", + // Auth for US Gov, 1.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/", + // Auth for US Gov, 2.0 token + "https://login.microsoftonline.us/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0") + .collect(Collectors.toList()), + false, // Audience validation takes place manually in code. + true, Duration.ofMinutes(5), true); + + /** + * Checks if the given list of claims represents a skill. A skill claim should + * contain: An AuthenticationConstants.VersionClaim" claim. An + * AuthenticationConstants.AudienceClaim claim. An + * AuthenticationConstants.AppIdClaim claim (v1) or an a + * AuthenticationConstants.AuthorizedParty claim (v2). And the appId claim + * should be different than the audience claim. When a channel (webchat, teams, + * etc.) invokes a bot, the + * is set to + * but when a bot calls another bot, the audience claim is set to the appId of + * the bot being invoked. The protocol supports v1 and v2 tokens: For v1 tokens, + * the AuthenticationConstants.AppIdClaim is present and set to the app Id of + * the calling bot. For v2 tokens, the AuthenticationConstants.AuthorizedParty + * is present and set to the app Id of the calling bot. + * + * @param claims A map of claims + * @return True if the list of claims is a skill claim, false if is not. + */ + public static Boolean isSkillClaim(Map claims) { + + for (Map.Entry entry : claims.entrySet()) { + if (entry.getValue() == AuthenticationConstants.ANONYMOUS_SKILL_APPID + && entry.getKey() == AuthenticationConstants.APPID_CLAIM) { + return true; + } + } + + Optional> version = claims.entrySet().stream() + .filter((x) -> x.getKey() == AuthenticationConstants.VERSION_CLAIM).findFirst(); + if (!version.isPresent()) { + // Must have a version claim. + return false; + } + + Optional> audience = claims.entrySet().stream() + .filter((x) -> x.getKey() == AuthenticationConstants.AUDIENCE_CLAIM).findFirst(); + + if (!audience.isPresent() + || AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER == audience.get().getValue()) { + // The audience is https://api.botframework.com and not an appId. + return false; + } + + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (StringUtils.isBlank(appId)) { + return false; + } + + // Skill claims must contain and app ID and the AppID must be different than the + // audience. + return appId != audience.get().getValue(); + } + +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java index 01b11d6e7..f4d6e673a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestOAuthClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.rest; import com.microsoft.bot.connector.BotSignIn; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java index 97e135c97..24e15aab1 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/teams/TeamsConnectorClient.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.teams; import com.microsoft.bot.restclient.RestClient; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java index 76526b810..d0191e6d8 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/interceptors/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains default interceptors for making HTTP requests. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java index b6b410a6d..cf2ef3499 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains the runtime classes required for AutoRest generated * clients to compile and function. To learn more about AutoRest generator, diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java index 0c47c4a00..da523673b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/protocol/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that interfaces defining the behaviors * of the necessary components of a Rest Client. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java index 65d0fe5aa..50d38d144 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/retry/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that define the retry behaviors when an error * occurs during a REST call. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java index bd389f68d..ceedba31d 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/package-info.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + /** * The package contains classes that handle serialization and deserialization * for the REST call payloads. diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java index 469fd7c8a..68297d38f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.schema.*; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java index 38f013ca3..459c18799 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotAccessTokenStub.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java index 9fa4f1ec7..d99652b30 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/BotConnectorTestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.base.TestBase; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java index 67b93f347..de9604db5 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.auth0.jwt.algorithms.Algorithm; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java index 75fbd1c98..41351c98a 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthConnectorTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java index 0af43a281..ac572fd29 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/OAuthTestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java index 686a16400..2757acbc9 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryParamsTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.RetryParams; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java index 2eb8d711b..9d8b9b4c0 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.Retry; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java index 53a40783a..b125a6169 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/UserAgentTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector; import org.junit.Assert; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java index 8488a2b00..60123f8b2 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/InterceptorManager.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import com.fasterxml.jackson.databind.ObjectMapper; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java index 2341e6f87..53c1117c7 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/NetworkCallRecord.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import java.util.Map; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java index bbd40fdef..0d73b7b03 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/RecordedData.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import java.util.LinkedList; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java index 6f42a4e21..a673ec18c 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/base/TestBase.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.connector.base; import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java index 1d0a801f9..2fa5fe22f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalShelter.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java index f8039d710..fb472db7f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/AnimalWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonSubTypes; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java index 052d35162..6000619bb 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/CatWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java index 749bc8e84..9227fb385 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/ComposeTurtles.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java index 463ccdbef..674eaf18b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/DogWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java index 8fa997cbf..14e4cd19e 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/FlattenableAnimalInfo.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java index e4fded082..9d5edd32f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/NonEmptyAnimalWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java index 4e05f6760..99390abdc 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/RabbitWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java index ba4cdf40f..5248d154a 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/restclient/TurtleWithTypeIdContainingDot.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.restclient; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index 1958da61d..25da5f40d 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -1,5 +1,5 @@ - + 4.0.0 @@ -53,21 +53,51 @@ org.slf4j slf4j-api - com.microsoft.azure azure-documentdb - 2.4.1 + 2.6.0 - com.microsoft.azure + com.azure azure-storage-blob - 11.0.1 + 12.8.0 + + + com.microsoft.bot + bot-integration-core + + + com.microsoft.bot + bot-builder + 4.6.0-preview8 - com.microsoft.bot bot-builder + ${project.version} + test-jar + test + + + com.google.guava + guava + 24.1-jre + + + org.javatuples + javatuples + 1.2 + + + org.apache.commons + commons-lang3 + 3.7 + + + org.yaml + snakeyaml + 1.20 @@ -98,15 +128,13 @@ - - org.apache.maven.plugins maven-pmd-plugin true - com/microsoft/bot/dialogs/** + com/microsoft/recognizers/** @@ -114,10 +142,9 @@ org.apache.maven.plugins maven-checkstyle-plugin - com/microsoft/bot/dialogs/** + com/microsoft/recognizers/** - diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java new file mode 100644 index 000000000..92022a2ae --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java @@ -0,0 +1,418 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; + + /** + * A {@link Dialog} that is composed of other dialogs. + * + * A component dialog has an inner {@link DialogSet} and {@link DialogContext} ,which provides + * an inner dialog stack that is hidden from the parent dialog. + */ + public class ComponentDialog extends DialogContainer { + + private String initialDialogId; + + /** + * The id for the persisted dialog state. + */ + public static final String PERSISTEDDIALOGSTATE = "dialogs"; + + private boolean initialized; + + /** + * Initializes a new instance of the {@link ComponentDialog} class. + * + * @param dialogId The D to assign to the new dialog within the parent dialog + * set. + */ + public ComponentDialog(String dialogId) { + super(dialogId); + } + + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext outerDc, Object options) { + + if (outerDc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "outerDc cannot be null." + )); + } + + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + DialogContext innerDc = this.createChildContext(outerDc); + DialogTurnResult turnResult = onBeginDialog(innerDc, options).join(); + + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return result to calling dialog + DialogTurnResult result = endComponent(outerDc, turnResult.getResult()).join(); + return CompletableFuture.completedFuture(result); + } + + getTelemetryClient().trackDialogView(getId(), null, null); + + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. If this method is *not* + * overridden, the component dialog calls the + * {@link DialogContext#continueDialog(CancellationToken)} method on its + * inner dialog context. If the inner dialog stack is empty, the + * component dialog ends, and if a {@link DialogTurnResult#result} is + * available, the component dialog uses that as its return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext outerDc) { + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + // Continue execution of inner dialog + DialogContext innerDc = this.createChildContext(outerDc); + DialogTurnResult turnResult = this.onContinueDialog(innerDc).join(); + + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return to calling dialog + DialogTurnResult result = this.endComponent(outerDc, turnResult.getResult()).join(); + return CompletableFuture.completedFuture(result); + } + + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a child dialog on the parent's dialog stack completed this turn, + * returning control to this dialog component. + * + * @param outerDc The {@link DialogContext} for the current turn of + * conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether this dialog + * is still active after this dialog turn has been processed. Generally, + * the child dialog was started with a call to + * {@link BeginDialog(DialogContext, Object)} in the parent's context. + * However, if the {@link DialogContext#replaceDialog(String, Object)} + * method is called, the logical child dialog may be different than the + * original. If this method is *not* overridden, the dialog + * automatically calls its {@link RepromptDialog(TurnContext, + * DialogInstance)} when the user replies. + */ + @Override + public CompletableFuture resumeDialog(DialogContext outerDc, DialogReason reason, + Object result) { + + ensureInitialized(outerDc).join(); + + this.checkForVersionChange(outerDc).join(); + + // Containers are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the container receiving an + // unexpected call to + // dialogResume() when the pushed on dialog ends. + // To avoid the container prematurely ending we need to implement this method + // and simply + // ask our inner dialog stack to re-prompt. + repromptDialog(outerDc.getContext(), outerDc.getActiveDialog()).join(); + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information for this dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // Delegate to inner dialog. + DialogContext innerDc = this.createInnerDc(turnContext, instance); + innerDc.repromptDialog().join(); + + // Notify component + return onRepromptDialog(turnContext, instance); + } + + /** + * Called when the dialog is ending. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the instance of this + * component dialog on its parent's dialog stack. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * When this method is called from the parent dialog's context, the + * component dialog cancels all of the dialogs on its inner dialog stack + * before ending. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, + DialogReason reason) { + // Forward cancel to inner dialogs + if (reason == DialogReason.CANCEL_CALLED) { + DialogContext innerDc = this.createInnerDc(turnContext, instance); + innerDc.cancelAllDialogs().join(); + } + + return onEndDialog(turnContext, instance, reason); + } + + /** + * Adds a new {@link Dialog} to the component dialog and returns the updated + * component. + * + * @param dialog The dialog to add. + * + * @return The {@link ComponentDialog} after the operation is complete. + * + * The added dialog's {@link Dialog#telemetryClient} is set to the + * {@link DialogContainer#telemetryClient} of the component dialog. + */ + public ComponentDialog addDialog(Dialog dialog) { + this.getDialogs().add(dialog); + + if (this.getInitialDialogId() == null) { + this.setInitialDialogId(dialog.getId()); + } + + return this; + } + + /** + * Creates an inner {@link DialogContext} . + * + * @param dc The parent {@link DialogContext} . + * + * @return The created Dialog Context. + */ + @Override + public DialogContext createChildContext(DialogContext dc) { + return this.createInnerDc(dc, dc.getActiveDialog()); + } + + /** + * Ensures the dialog is initialized. + * + * @param outerDc The outer {@link DialogContext} . + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture ensureInitialized(DialogContext outerDc) { + if (!this.initialized) { + this.initialized = true; + onInitialize(outerDc).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Initilizes the dialog. + * + * @param dc The {@link DialogContext} to initialize. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture onInitialize(DialogContext dc) { + if (this.getInitialDialogId() == null) { + Collection dialogs = getDialogs().getDialogs(); + if (dialogs.size() > 0) { + this.setInitialDialogId(dialogs.stream().findFirst().get().getId()); + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. By + * default, this calls the + * {@link Dialog#beginDialog(DialogContext, Object)} method of the + * component dialog's initial dialog, as defined by + * {@link InitialDialogId} . Override this method in a derived class to + * implement interrupt logic. + */ + protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { + return innerDc.beginDialog(getInitialDialogId(), options); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. By default, this calls the + * currently active inner dialog's + * {@link Dialog#continueDialog(DialogContext)} method. Override this + * method in a derived class to implement interrupt logic. + */ + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return innerDc.continueDialog(); + } + + /** + * Called when the dialog is ending. + * + * @param context The context Object for this turn. + * @param instance State information associated with the inner dialog stack of + * this component dialog. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after all inner + * dialogs have been canceled. + */ + protected CompletableFuture onEndDialog(TurnContext context, DialogInstance instance, + DialogReason reason) { + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the inner dialog stack + * of this component dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after the re-prompt + * operation completes for the inner dialog. + */ + protected CompletableFuture onRepromptDialog(TurnContext turnContext, DialogInstance instance) { + return CompletableFuture.completedFuture(null); + } + + /** + * Ends the component dialog in its parent's context. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param result Optional, value to return from the dialog component to the + * parent context. + * + * @return A task that represents the work queued to execute. + * + * If the task is successful, the result indicates that the dialog ended + * after the turn was processed by the dialog. In general, the parent + * context is the dialog or bot turn handler that started the dialog. If + * the parent is a dialog, the stack calls the parent's + * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method to return a result to the parent dialog. If the parent dialog + * does not implement `ResumeDialog`, then the parent will end, too, and + * the result is passed to the next parent context, if one exists. The + * returned {@link DialogTurnResult} contains the return value in its + * {@link DialogTurnResult#result} property. + */ + protected CompletableFuture endComponent(DialogContext outerDc, Object result) { + return outerDc.endDialog(result); + } + + private static DialogState buildDialogState(DialogInstance instance) { + DialogState state; + + if (instance.getState().containsKey(PERSISTEDDIALOGSTATE)) { + state = (DialogState) instance.getState().get(PERSISTEDDIALOGSTATE); + } else { + state = new DialogState(); + instance.getState().put(PERSISTEDDIALOGSTATE, state); + } + + if (state.getDialogStack() == null) { + state.setDialogStack(new ArrayList()); + } + + return state; + } + + private DialogContext createInnerDc(DialogContext outerDc, DialogInstance instance) { + DialogState state = buildDialogState(instance); + + return new DialogContext(this.getDialogs(), outerDc, state); + } + + // NOTE: You should only call this if you don't have a dc to work with (such as OnResume()) + private DialogContext createInnerDc(TurnContext turnContext, DialogInstance instance) { + DialogState state = buildDialogState(instance); + + return new DialogContext(this.getDialogs(), turnContext, state); + } + /** + * Gets the id assigned to the initial dialog. + * @return the InitialDialogId value as a String. + */ + public String getInitialDialogId() { + return this.initialDialogId; + } + + /** + * Sets the id assigned to the initial dialog. + * @param withInitialDialogId The InitialDialogId value. + */ + public void setInitialDialogId(String withInitialDialogId) { + this.initialDialogId = withInitialDialogId; + } + } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 4a38ca3a9..6aced56e8 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -1,111 +1,253 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -package com.microsoft.bot.dialogs; +package com.microsoft.bot.dialogs; -import com.microsoft.bot.builder.BotAssert; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; import com.microsoft.bot.builder.TurnContext; - -import java.util.HashMap; import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; /** - * Base class for controls + * Base class for all dialogs. */ -public abstract class Dialog -{ - /** - * Starts the dialog. Depending on the dialog, its possible for the dialog to finish - * immediately so it's advised to check the completion Object returned by `begin()` and ensure - * that the dialog is still active before continuing. - * @param context Context for the current turn of the conversation with the user. - * @param state A state Object that the dialog will use to persist its current state. This should be an empty Object which the dialog will populate. The bot should persist this with its other conversation state for as long as the dialog is still active. - * @param options (Optional) additional options supported by the dialog. - * @return DialogCompletion result - */ - public CompletableFuture Begin(TurnContext context, HashMap state) - { - return Begin(context, state, null); - } - public CompletableFuture Begin(TurnContext context, HashMap state, HashMap options) - { - BotAssert.contextNotNull(context); - if (state == null) - throw new NullPointerException("HashMap state"); - - // Create empty dialog set and ourselves to it - // TODO: Complete - //DialogSet dialogs = new DialogSet(); - //dialogs.Add("dialog", (IDialog)this); - - // Start the control - //HashMap result = null; - - /* - // TODO Complete - - await dc.Begin("dialog", options); - */ - CompletableFuture result = null; - /* - if (dc.ActiveDialog != null) { - result = new DialogCompletion(); - result.setIsActive(true); - result.setIsCompleted(false); - } - else{ - result = new DialogCompletion(); - result.setIsActive(false); - result.setIsCompleted(true); - result.setResult(result); - } - */ - return result; - } - - /** - * Passes a users reply to the dialog for further processing.The bot should keep calling - * 'continue()' for future turns until the dialog returns a completion Object with - * 'isCompleted == true'. To cancel or interrupt the prompt simply delete the `state` Object - * being persisted. - * @param context Context for the current turn of the conversation with the user. - * @param state A state Object that was previously initialized by a call to [begin()](#begin). - * @return DialogCompletion result - */ - public CompletableFuture Continue(TurnContext context, HashMap state) - { - BotAssert.contextNotNull(context); - if (state == null) - throw new NullPointerException("HashMap"); - - // Create empty dialog set and ourselves to it - // TODO: daveta - //DialogSet dialogs = new DialogSet(); - //dialogs.Add("dialog", (IDialog)this); - - // Continue the dialog - //HashMap result = null; - CompletableFuture result = null; - /* - TODO: daveta - var dc = new DialogContext(dialogs, context, state, (r) => { result = r; }); - if (dc.ActiveDialog != null) - { - await dc.Continue(); - return dc.ActiveDialog != null - ? - new DialogCompletion { IsActive = true, IsCompleted = false } - : - new DialogCompletion { IsActive = false, IsCompleted = true, Result = result }; - } - else - { - return new DialogCompletion { IsActive = false, IsCompleted = false }; +public abstract class Dialog { + /** + * A {@link DialogTurnResult} that indicates that the current dialog is + * still active and waiting for input from the user next turn. + */ + public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING); + + @JsonIgnore + private BotTelemetryClient telemetryClient; + + @JsonProperty(value = "id") + private String id; + + /** + * Initializes a new instance of the Dialog class. + * @param dialogId The ID to assign to this dialog. + */ + public Dialog(String dialogId) { + id = dialogId; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Gets id for the dialog. + * @return Id for the dialog. + */ + public String getId() { + if (StringUtils.isEmpty(id)) { + id = onComputeId(); } - */ + return id; + } - return result; + /** + * Sets id for the dialog. + * @param withId Id for the dialog. + */ + public void setId(String withId) { + id = withId; + } + /** + * Gets the {@link BotTelemetryClient} to use for logging. + * @return The BotTelemetryClient to use for logging. + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the {@link BotTelemetryClient} to use for logging. + * @param withTelemetryClient The BotTelemetryClient to use for logging. + */ + public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { + telemetryClient = withTelemetryClient; + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(DialogContext dc) { + return beginDialog(dc, null); + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @param options Initial information to pass to the dialog. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public abstract CompletableFuture beginDialog(DialogContext dc, Object options); + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. The result may also contain a + * return value. + */ + public CompletableFuture continueDialog(DialogContext dc) { + // By default just end the current dialog. + return dc.endDialog(null); } -} + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String)} method + * is called, the logical child dialog may be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @return If the task is successful, the result indicates whether this dialog is still + * active after this dialog turn has been processed. + */ + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason) { + return resumeDialog(dc, reason, null); + } + + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String, Object)} method + * is called, the logical child dialog may be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The type of the + * value returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog is still + * active after this dialog turn has been processed. + */ + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + // By default just end the current dialog and return result to parent. + return dc.endDialog(result); + } + + /** + * Called when the dialog should re-prompt the user for input. + * @param turnContext The context object for this turn. + * @param instance State information for this dialog. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // No-op by default + return CompletableFuture.completedFuture(null); + } + + /** + * Called when the dialog is ending. + * @param turnContext The context object for this turn. + * @param instance State information associated with the instance of this dialog on the dialog stack. + * @param reason Reason why the dialog ended. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + // No-op by default + return CompletableFuture.completedFuture(null); + } + + /** + * Gets a unique String which represents the version of this dialog. If the version changes + * between turns the dialog system will emit a DialogChanged event. + * @return Unique String which should only change when dialog has changed in a way that should restart the dialog. + */ + @JsonIgnore + public String getVersion() { + return id; + } + + /** + * Called when an event has been raised, using `DialogContext.emitEvent()`, by either the + * current dialog or a dialog that the current dialog started. + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return True if the event is handled by the current dialog and bubbling should stop. + */ + public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) { + // Before bubble + return onPreBubbleEvent(dc, e) + .thenCompose(handled -> { + // Bubble as needed + if (!handled && e.shouldBubble() && dc.getParent() != null) { + return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false); + } + + // just pass the handled value to the next stage + return CompletableFuture.completedFuture(handled); + }) + .thenCompose(handled -> { + if (!handled) { + // Post bubble + return onPostBubbleEvent(dc, e); + } + + return CompletableFuture.completedFuture(handled); + }); + } + + /** + * Called before an event is bubbled to its parent. + * + *

This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing.

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should stop. + */ + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } + + /** + * Called after an event was bubbled to all parents and wasn't handled. + * + *

This is a good place to perform default processing logic for an event. Returning `true` will + * prevent any processing of the event by child dialogs.

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should stop. + */ + protected CompletableFuture onPostBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } + + /** + * Computes an id for the Dialog. + * @return The id. + */ + protected String onComputeId() { + return this.getClass().getName(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java deleted file mode 100644 index 826064ed2..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCompletion.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.dialogs; - -import java.util.HashMap; - -/** - * Result returned to the caller of one of the various stack manipulation methods and used to - * return the result from a final call to `DialogContext.end()` to the bots logic. - */ -public class DialogCompletion -{ - - - /** - * If 'true' the dialog is still active. - */ - boolean _isActive; - public void setIsActive(boolean isActive) { - this._isActive = isActive; - } - public boolean getIsActive() { - return this._isActive; - } - - /** - * If 'true' the dialog just completed and the final [result](#result) can be retrieved. - */ - boolean _isCompleted; - public void setIsCompleted(boolean isCompleted) - { - this._isCompleted = isCompleted; - } - public boolean getIsCompleted() - { - return this._isCompleted; - } - - /** - * Result returned by a dialog that was just ended.This will only be populated in certain - * cases: - * - The bot calls `dc.begin()` to start a new dialog and the dialog ends immediately. - * - The bot calls `dc.continue()` and a dialog that was active ends. - * In all cases where it's populated, [active](#active) will be `false`. - */ - HashMap _result; - public HashMap getResult() { - return _result; - } - public void setResult(HashMap result) { - this._result = result; - } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java index c76181297..b7bccf674 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContainer.java @@ -1,50 +1,150 @@ -/* -public class DialogContainer implements IDialogContinue +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +package com.microsoft.bot.dialogs; -{ - protected DialogSet Dialogs { get; set; } - protected string DialogId { get; set; } +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.TurnContext; +import java.util.concurrent.CompletableFuture; - public DialogContainer(string dialogId, DialogSet dialogs = null) - { - if (string.IsNullOrEmpty(dialogId)) - throw new ArgumentNullException(nameof(dialogId)); +/** + * A container for a set of Dialogs. + */ +public abstract class DialogContainer extends Dialog { + @JsonIgnore + private DialogSet dialogs = new DialogSet(); - Dialogs dialogs = (dialogs != null) ? dialogs : new DialogSet(); - DialogId = dialogId; + /** + * Creates a new instance with the default dialog id. + */ + public DialogContainer() { + super(null); } - public async Task DialogBegin(DialogContext dc, IDictionary dialogArgs = null) - { - if (dc == null) - throw new ArgumentNullException(nameof(dc)); + /** + * Creates a new instance with the default dialog id. + * @param dialogId Id of the dialog. + */ + public DialogContainer(String dialogId) { + super(dialogId); + } - // Start the controls entry point dialog. - IDictionary result = null; - var cdc = new DialogContext(this.Dialogs, dc.Context, dc.ActiveDialog.State, (r) => { result = r; }); - await cdc.Begin(DialogId, dialogArgs); - // End if the controls dialog ends. - if (cdc.ActiveDialog == null) - { - await dc.End(result); - } + /** + * Returns the Dialogs as a DialogSet. + * @return The DialogSet of Dialogs. + */ + public DialogSet getDialogs() { + return dialogs; + } + + + /** + * Sets the BotTelemetryClient to use for logging. When setting this property, + * all of the contained dialogs' BotTelemetryClient properties are also set. + * + * @param withTelemetryClient The BotTelemetryClient to use for logging. + */ + @Override + public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { + super.setTelemetryClient(withTelemetryClient != null ? withTelemetryClient : new NullBotTelemetryClient()); + dialogs.setTelemetryClient(super.getTelemetryClient()); + } + + /** + * + * Creates an inner dialog context for the containers active child. + * + * @param dc Parents dialog context. + * @return A new dialog context for the active child. + */ + public abstract DialogContext createChildContext(DialogContext dc); + + /** + * Searches the current DialogSet for a Dialog by its ID. + * + * @param dialogId ID of the dialog to search for. + * @return The dialog if found; otherwise null + */ + public Dialog findDialog(String dialogId) { + return dialogs.find(dialogId); } - public async Task DialogContinue(DialogContext dc) - { - if (dc == null) - throw new ArgumentNullException(nameof(dc)); + /** + * Called when an event has been raised, using `DialogContext.emitEvent()`, by + * either the current dialog or a dialog that the current dialog started. + * + *

+ * This override will trace version changes. + *

+ * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * @return True if the event is handled by the current dialog and bubbling + * should stop. + */ + @Override + public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) { + return super.onDialogEvent(dc, e).thenCompose(handled -> { + // Trace unhandled "versionChanged" events. + if (!handled && e.getName().equals(DialogEvents.VERSION_CHANGED)) { + String traceMessage = String.format("Unhandled dialog event: {e.Name}. Active Dialog: %s", + dc.getActiveDialog().getId()); - // Continue controls dialog stack. - IDictionary result = null; - var cdc = new DialogContext(this.Dialogs, dc.Context, dc.ActiveDialog.State, (r) => { result = r; }); - await cdc.Continue(); - // End if the controls dialog ends. - if (cdc.ActiveDialog == null) - { - await dc.End(result); + dc.getDialogs().getTelemetryClient().trackTrace(traceMessage, Severity.WARNING, null); + + return TurnContext.traceActivity(dc.getContext(), traceMessage).thenApply(response -> handled); + } + + return CompletableFuture.completedFuture(handled); + }); + } + + /** + * Returns internal version identifier for this container. + * + *

+ * DialogContainers detect changes of all sub-components in the container and + * map that to an DialogChanged event. Because they do this, DialogContainers + * "hide" the internal changes and just have the .id. This isolates changes to + * the container level unless a container doesn't handle it. To support this + * DialogContainers define a protected virtual method getInternalVersion() which + * computes if this dialog or child dialogs have changed which is then examined + * via calls to checkForVersionChange(). + *

+ * + * @return version which represents the change of the internals of this + * container. + */ + protected String getInternalVersion() { + return dialogs.getVersion(); + } + + /** + * Checks to see if a containers child dialogs have changed since the current + * dialog instance was started. + * + * This should be called at the start of `beginDialog()`, `continueDialog()`, + * and `resumeDialog()`. + * + * @param dc dialog context + * @return CompletableFuture + */ + protected CompletableFuture checkForVersionChange(DialogContext dc) { + String current = dc.getActiveDialog().getVersion(); + dc.getActiveDialog().setVersion(getInternalVersion()); + + // Check for change of previously stored hash + if (current != null && !current.equals(dc.getActiveDialog().getVersion())) { + // Give bot an opportunity to handle the change. + // - If bot handles it the changeHash will have been updated as to avoid + // triggering the + // change again. + return dc.emitEvent(DialogEvents.VERSION_CHANGED, getId(), true, false).thenApply(result -> null); } + + return CompletableFuture.completedFuture(null); } } -*/ diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 935f353bd..3a0a51306 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -1,167 +1,579 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.dialogs; -// -// Copyright (c) Microsoft. All rights reserved. -// Licensed under the MIT license. -// -// Microsoft Bot Framework: http://botframework.com -// -// Bot Builder SDK GitHub: -// https://github.com/Microsoft/BotBuilder -// -// Copyright (c) Microsoft Corporation -// All rights reserved. -// -// MIT License: -// 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. -// +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextStateCollection; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.connector.Async; +import java.util.HashMap; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; /** - * Encapsulates a method that represents the code to execute after a result is available. - * - * The result is often a message from the user. - * - * @param T The type of the result. - * @param context The dialog context. - * @param result The result. - * @return A task that represents the code that will resume after the result is available. + * Provides context for the current state of the dialog stack. */ +public class DialogContext { + private DialogSet dialogs; + private TurnContext context; + private List stack; + private DialogContext parent; + private DialogStateManager state; + private TurnContextStateCollection services; -/* -public interface ResumeAfter -{ - CompletableFuture invoke(DialogContext contenxt, Available) -} + /** + * Initializes a new instance of the DialogContext class from the turn context. + * @param withDialogs The dialog set to create the dialog context for. + * @param withTurnContext The current turn context. + * @param withState The state property from which to retrieve the dialog context. + */ + public DialogContext(DialogSet withDialogs, TurnContext withTurnContext, DialogState withState) { + if (withDialogs == null) { + throw new IllegalArgumentException("DialogContext, DialogSet is required."); + } -public delegate Task ResumeAfter(IDialogContext context, IAwaitable result); -*/ + if (withTurnContext == null) { + throw new IllegalArgumentException("DialogContext, TurnContext is required."); + } -/** - * Encapsulate a method that represents the code to start a dialog. - * @param context The dialog context. - * @return A task that represents the start code for a dialog. - */ -//public delegate Task Start(IDialogContext context); + init(withDialogs, withTurnContext, withState); + } + /** + * Initializes a new instance of the DialogContext class from the turn context. + * @param withDialogs The dialog set to create the dialog context for. + * @param withParentDialogContext Parent dialog context. + * @param withState Current dialog state. + */ + public DialogContext( + DialogSet withDialogs, + DialogContext withParentDialogContext, + DialogState withState + ) { + if (withParentDialogContext == null) { + throw new IllegalArgumentException("DialogContext, DialogContext is required."); + } + init(withDialogs, withParentDialogContext.getContext(), withState); -/** - * The context for the execution of a dialog's conversational process. - */ -// DAVETA: TODO -// public interface DialogContext extends -public interface DialogContext { + parent = withParentDialogContext; + + // copy parent services into this DialogContext. + services.copy(getParent().getServices()); + } + + + /** + * @param withDialogs + * @param withTurnContext + * @param withState + */ + private void init(DialogSet withDialogs, TurnContext withTurnContext, DialogState withState) { + dialogs = withDialogs; + context = withTurnContext; + stack = withState.getDialogStack(); + state = new DialogStateManager(this, null); + services = new TurnContextStateCollection(); + + ObjectPath.setPathValue(context.getTurnState(), TurnPath.ACTIVITY, context.getActivity()); + } + + /** + * Gets the set of dialogs which are active for the current dialog container. + * @return The set of dialogs which are active for the current dialog container. + */ + public DialogSet getDialogs() { + return dialogs; + } + + /** + * Gets the context for the current turn of conversation. + * @return The context for the current turn of conversation. + */ + public TurnContext getContext() { + return context; + } + + /** + * Gets the current dialog stack. + * @return The current dialog stack. + */ + public List getStack() { + return stack; + } + + /** + * Gets the parent DialogContext, if any. Used when searching for the ID of a dialog to start. + * @return The parent "DialogContext, if any. Used when searching for the ID of a dialog to start. + */ + public DialogContext getParent() { + return parent; + } + + /** + * Set the parent DialogContext. + * @param withDialogContext The DialogContext to set the parent to. + */ + public void setParent(DialogContext withDialogContext) { + parent = withDialogContext; + } + + /** + * Gets dialog context for child if there is an active child. + * @return Dialog context for child if there is an active child. + */ + public DialogContext getChild() { + DialogInstance instance = getActiveDialog(); + if (instance != null) { + Dialog dialog = findDialog(instance.getId()); + if (dialog instanceof DialogContainer) { + return ((DialogContainer) dialog).createChildContext(this); + } } + return null; + } -/** - * Helper methods. - */ -/* -public static partial class Extensions -{*/ /** - * Post a message to be sent to the user, using previous messages to establish a conversation context. - * - * If the locale parameter is not set, locale of the incoming message will be used for reply. + * Gets the cached instance of the active dialog on the top of the stack or null if the stack is empty. + * @return The cached instance of the active dialog on the top of the stack or null if the stack is empty. + */ + public DialogInstance getActiveDialog() { + if (stack.size() > 0) { + return stack.get(0); + } + + return null; + } + + /** + * Gets or sets the DialogStateManager which manages view of all memory scopes. + * @return DialogStateManager with unified memory view of all memory scopes. + */ + public DialogStateManager getState() { + return state; + } + + /** + * Gets the services collection which is contextual to this dialog context. + * @return Services collection. + */ + public TurnContextStateCollection getServices() { + return services; + } + + /** + * Gets the current DialogManager for this dialogContext. + * @return The root dialogManager that was used to create this dialog context chain. + */ + public DialogManager getDialogManager() { + return getContext().getTurnState().get(DialogManager.class); + } + + /** + * Starts a new dialog and pushes it onto the dialog stack. + * @param dialogId ID of the dialog to start. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(String dialogId) { + return beginDialog(dialogId, null); + } + + /** + * Starts a new dialog and pushes it onto the dialog stack. + * @param dialogId ID of the dialog to start. + * @param options Optional, information to pass to the dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture beginDialog(String dialogId, Object options) { + if (StringUtils.isEmpty(dialogId)) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.beginDialog, dialogId is required" + )); + } + + // Look up dialog + Dialog dialog = findDialog(dialogId); + if (dialog == null) { + Async.completeExceptionally(new Exception(String.format( + "DialogContext.beginDialog(): A dialog with an id of '%s' wasn't found." + + " The dialog must be included in the current or parent DialogSet." + + " For example, if subclassing a ComponentDialog you can call AddDialog()" + + " within your constructor.", + dialogId + ))); + } + + // Push new instance onto stack + DialogInstance instance = new DialogInstance(dialogId, new HashMap<>()); + stack.add(0, instance); + + // Call dialog's Begin() method + return dialog.beginDialog(this, options); + } + + /** + * Helper function to simplify formatting the options for calling a prompt dialog. This helper will + * take an PromptOptions argument and then call {@link #beginDialog(String, Object)} * - * @param botToUser Communication channel to use. - * @param text The message text. - * @param locale The locale of the text. - * @return A task that represents the post operation. - */ -/* - public static async Task Post(this BotToUser botToUser, string text, string locale = null) - { - var message = botToUser.MakeMessage(); - message.Text = text; - - if (!string.IsNullOrEmpty(locale)) - { - message.Locale = locale; + * @param dialogId ID of the prompt dialog to start. + * @param options Information to pass to the prompt dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture prompt(String dialogId, PromptOptions options) { + if (StringUtils.isEmpty(dialogId)) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.prompt, dialogId is required" + )); } - await botToUser.Post(message); + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "DialogContext.prompt, PromptOptions is required" + )); + } + + return beginDialog(dialogId, options); } -*/ + /** + * Continues execution of the active dialog, if there is one, by passing the current + * DialogContext to the active dialog's {@link Dialog#continueDialog(DialogContext)} + * method. + * + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture continueDialog() { + return Async.tryCompletable(() -> { + // if we are continuing and haven't emitted the activityReceived event, emit it + // NOTE: This is backward compatible way for activity received to be fired even if + // you have legacy dialog loop + if (!getContext().getTurnState().containsKey("activityReceivedEmitted")) { + getContext().getTurnState().replace("activityReceivedEmitted", true); + + // Dispatch "activityReceived" event + // - This will queue up any interruptions. + emitEvent(DialogEvents.ACTIVITY_RECEIVED, getContext().getActivity(), true, + true + ); + } + return CompletableFuture.completedFuture(null); + }) + .thenCompose(v -> { + if (getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "Failed to continue dialog. A dialog with id %s could not be found.", + getActiveDialog().getId() + )); + } + + // Continue dialog execution + return dialog.continueDialog(this); + } + + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); + }); + } /** - * Post a message and optional SSML to be sent to the user, using previous messages to establish a conversation context. + * Helper method that supplies a null result to {@link #endDialog(Object)}. * - * If the locale parameter is not set, locale of the incoming message will be used for reply. + * @return If the task is successful, the result indicates that the dialog ended after the + * turn was processed by the dialog. + */ + public CompletableFuture endDialog() { + return endDialog(null); + } + + /** + * Ends a dialog by popping it off the stack and returns an optional result to the dialog's + * parent. The parent dialog is the dialog the started the on being ended via a call to + * either {@link #beginDialog(String, Object)} or {@link #prompt(String, PromptOptions)}. The + * parent dialog will have its {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method invoked with any returned result. If the parent dialog hasn't implemented a + * {@link Dialog#resumeDialog(DialogContext dc, DialogReason reason)} method, then it will be + * automatically ended as well and the result passed to its parent. If there are no more parent + * dialogs on the stack then processing of the turn will end. + * + * @param result Optional, result to pass to the parent context. + * @return If the task is successful, the result indicates that the dialog ended after the + * turn was processed by the dialog. + */ + public CompletableFuture endDialog(Object result) { + // End the active dialog + return endActiveDialog(DialogReason.END_CALLED, result) + .thenCompose(v -> { + // Resume parent dialog + if (getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "DialogContext.endDialog(): Can't resume previous dialog. A dialog " + + "with an id of '%s' wasn't found.", + getActiveDialog().getId()) + ); + } + + // Return result to previous dialog + return dialog.resumeDialog(this, DialogReason.END_CALLED, result); + } + + return CompletableFuture.completedFuture( + new DialogTurnResult(DialogTurnStatus.COMPLETE, result) + ); + }); + } + + /** + * Helper method for {@link #cancelAllDialogs(boolean, String, Object)} that does not cancel + * parent dialogs or pass and event. * - * @param botToUser Communication channel to use. - * @param text The message text. - * @param speak The SSML markup for text to speech. - * @param options The options for the message. - * @param locale The locale of the text. - * @return A task that represents the post operation. - */ - /* public static async Task Say(this BotToUser botToUser, string text, string speak = null, MessageOptions options = null, string locale = null) - { - var message = botToUser.MakeMessage(); - - message.Text = text; - message.Speak = speak; - - if (!string.IsNullOrEmpty(locale)) - { - message.Locale = locale; + * @return If the task is successful, the result indicates that dialogs were canceled after the + * turn was processed by the dialog or that the stack was already empty. + */ + public CompletableFuture cancelAllDialogs() { + return cancelAllDialogs(false, null, null); + } + + /** + * Deletes any existing dialog stack thus canceling all dialogs on the stack. + * + *

In general, the parent context is the dialog or bot turn handler that started the dialog. + * If the parent is a dialog, the stack calls the parent's + * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method to return a result to the parent dialog. If the parent dialog does not implement + * {@link Dialog#resumeDialog}, then the parent will end, too, and the result is passed to the next + * parent context.

+ * + * @param cancelParents If true the cancellation will bubble up through any parent dialogs as well. + * @param eventName The event. If null, {@link DialogEvents#CANCEL_DIALOG} is used. + * @param eventValue The event value. Can be null. + * @return If the task is successful, the result indicates that dialogs were canceled after the + * turn was processed by the dialog or that the stack was already empty. + */ + public CompletableFuture cancelAllDialogs( + boolean cancelParents, + String eventName, + Object eventValue + ) { + eventName = eventName != null ? eventName : DialogEvents.CANCEL_DIALOG; + + if (!stack.isEmpty() || getParent() != null) { + // Cancel all local and parent dialogs while checking for interception + boolean notify = false; + DialogContext dialogContext = this; + + while (dialogContext != null) { + if (!dialogContext.stack.isEmpty()) { + // Check to see if the dialog wants to handle the event + if (notify) { + Boolean eventHandled = dialogContext.emitEvent(eventName, eventValue, false, false).join(); + if (eventHandled) { + break; + } + } + + // End the active dialog + dialogContext.endActiveDialog(DialogReason.CANCEL_CALLED).join(); + } else { + dialogContext = cancelParents ? dialogContext.getParent() : null; + } + + notify = true; + } + + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.CANCELLED)); + } else { + // Stack was empty and no parent + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); } + } + + /** + * Helper method for {@link #replaceDialog(String, Object)} that passes null for options. + * @param dialogId ID of the new dialog to start. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture replaceDialog(String dialogId) { + return replaceDialog(dialogId, null); + } - if (options != null) - { - message.InputHint = options.InputHint; - message.TextFormat = options.TextFormat; - message.AttachmentLayout = options.AttachmentLayout; - message.Attachments = options.Attachments; - message.Entities = options.Entities; + /** + * Starts a new dialog and replaces on the stack the currently active dialog with the new one. + * This is particularly useful for creating loops or redirecting to another dialog. + * @param dialogId ID of the new dialog to start. + * @param options Optional, information to pass to the dialog being started. + * @return If the task is successful, the result indicates whether the dialog is still + * active after the turn has been processed by the dialog. + */ + public CompletableFuture replaceDialog(String dialogId, Object options) { + // End the current dialog and giving the reason. + return endActiveDialog(DialogReason.REPLACE_CALLED) + .thenCompose(v -> { + ObjectPath.setPathValue(getContext().getTurnState(), "turn.__repeatDialogId", dialogId); + + // Start replacement dialog + return beginDialog(dialogId, options); + }); + } + + /** + * Calls the currently active dialog's {@link Dialog#repromptDialog(TurnContext, DialogInstance)} + * method. Used with dialogs that implement a re-prompt behavior. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture repromptDialog() { + // Emit 'repromptDialog' event + return emitEvent(DialogEvents.REPROMPT_DIALOG, null, false, false) + .thenCompose(handled -> { + if (!handled && getActiveDialog() != null) { + // Lookup dialog + Dialog dialog = this.findDialog(getActiveDialog().getId()); + if (dialog == null) { + throw new IllegalStateException(String.format( + "DialogSet.repromptDialog: Can't find A dialog with an id of '%s'.", + getActiveDialog().getId() + )); + } + + // Ask dialog to re-prompt if supported + return dialog.repromptDialog(getContext(), getActiveDialog()); + } + return CompletableFuture.completedFuture(null); + }); + } + + /** + * Find the dialog id for the given context. + * @param dialogId dialog id to find. + * @return dialog with that id, or null. + */ + public Dialog findDialog(String dialogId) { + if (dialogs != null) { + Dialog dialog = dialogs.find(dialogId); + if (dialog != null) { + return dialog; + } + } + + if (getParent() != null) { + return getParent().findDialog(dialogId); + } + + return null; + } + + + /** + * @param name Name of the event to raise. + * @return CompletableFuture + */ + public CompletableFuture emitEvent(String name) { + return emitEvent(name, null, true, false); + } + + /** + * @param name Name of the event to raise. + * @param value Value to send along with the event. + * @param bubble Flag to control whether the event should be bubbled to its parent if not handled locally. + * Defaults to a value of `true`. + * @param fromLeaf Whether the event is emitted from a leaf node. + * @return CompletableFuture + */ + public CompletableFuture emitEvent(String name, Object value, boolean bubble, boolean fromLeaf) { + // Initialize event + DialogEvent dialogEvent = new DialogEvent() {{ + setBubble(bubble); + setName(name); + setValue(value); + }}; + + DialogContext dc = this; + + // Find starting dialog + if (fromLeaf) { + while (true) { + DialogContext childDc = dc.getChild(); + + if (childDc != null) { + dc = childDc; + } else { + break; + } + } + } + + // Dispatch to active dialog first + DialogInstance instance = dc.getActiveDialog(); + if (instance != null) { + Dialog dialog = dc.findDialog(instance.getId()); + + if (dialog != null) { + return dialog.onDialogEvent(dc, dialogEvent); + } } - await botToUser.Post(message); - }*/ + return CompletableFuture.completedFuture(false); + } + + /** + * Obtain the CultureInfo in DialogContext. + * @return A String representing the current locale. + */ + public String getLocale() { + return getContext() != null ? getContext().getLocale() : null; + } + /** - * Suspend the current dialog until the user has sent a message to the bot. - * @param stack The dialog stack. - * @param resume The method to resume when the message has been received. + * @param reason + * @return CompletableFuture */ -/* - public static void Wait(this IDialogStack stack, ResumeAfter resume) - { - stack.Wait(resume); + private CompletableFuture endActiveDialog(DialogReason reason) { + return endActiveDialog(reason, null); } -*/ + /** - * Call a child dialog, add it to the top of the stack and post the message to the child dialog. - * @param R The type of result expected from the child dialog. - * @param stack The dialog stack. - * @param child The child dialog. - * @param resume The method to resume when the child dialog has completed. - * @param message The message that will be posted to child dialog. - * @return A task representing the Forward operation. + * @param reason + * @param result + * @return CompletableFuture */ -/* public static async Task Forward(this IDialogStack stack, IDialog child, ResumeAfter resume, MessageActivity message) - { - await stack.Forward(child, resume, message, token); + private CompletableFuture endActiveDialog(DialogReason reason, Object result) { + DialogInstance instance = getActiveDialog(); + if (instance == null) { + return CompletableFuture.completedFuture(null); + } + + return Async.wrapBlock(() -> dialogs.find(instance.getId())) + .thenCompose(dialog -> { + if (dialog != null) { + // Notify dialog of end + return dialog.endDialog(getContext(), instance, reason); + } + + return CompletableFuture.completedFuture(null); + }) + .thenCompose(v -> { + // Pop dialog off stack + stack.remove(0); + + // set Turn.LastResult to result + ObjectPath.setPathValue(getContext().getTurnState(), TurnPath.LAST_RESULT, result); + + return CompletableFuture.completedFuture(null); + }); } -}*/ +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java new file mode 100644 index 000000000..8470110cb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContextPath.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for available dialog contexts. + */ +public final class DialogContextPath { + + private DialogContextPath() { + + } + + /** + * Memory Path to dialogContext's active dialog. + */ + public static final String ACTIVEDIALOG = "dialogcontext.activeDialog"; + + /** + * Memory Path to dialogContext's parent dialog. + */ + public static final String PARENT = "dialogcontext.parent"; + + /** + * Memory Path to dialogContext's stack. + */ + public static final String STACK = "dialogContext.stack"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java new file mode 100644 index 000000000..e8c69cf7a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogDependencies.java @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.List; + +/** + * Enumerate child dialog dependencies so they can be added to the containers dialogset. + */ +public interface DialogDependencies { + + /** + * Enumerate child dialog dependencies so they can be added to the containers dialogset. + * @return Dialog list + */ + List getDependencies(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java new file mode 100644 index 000000000..d6eaaee2b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvent.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Represents an event related to the "lifecycle" of the dialog. + */ +public class DialogEvent { + private boolean bubble; + private String name; + private Object value; + + /** + * Indicates whether the event will be bubbled to the parent `DialogContext` + * if not handled by the current dialog. + * @return Whether the event can be bubbled to the parent `DialogContext`. + */ + public boolean shouldBubble() { + return bubble; + } + + /** + * Sets whether the event will be bubbled to the parent `DialogContext` + * if not handled by the current dialog. + * @param withBubble Whether the event can be bubbled to the parent `DialogContext`. + */ + public void setBubble(boolean withBubble) { + bubble = withBubble; + } + + /** + * Gets name of the event being raised. + * @return Name of the event being raised. + */ + public String getName() { + return name; + } + + /** + * Sets name of the event being raised. + * @param withName Name of the event being raised. + */ + public void setName(String withName) { + name = withName; + } + + /** + * Gets optional value associated with the event. + * @return Optional value associated with the event. + */ + public Object getValue() { + return value; + } + + /** + * Sets optional value associated with the event. + * @param withValue Optional value associated with the event. + */ + public void setValue(Object withValue) { + value = withValue; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java new file mode 100644 index 000000000..d2c1dc931 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogEvents.java @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; +/** + * Represents the events related to the "lifecycle" of the dialog. + */ +public final class DialogEvents { + private DialogEvents() { } + + /// Event fired when a dialog beginDialog() is called. + public static final String BEGIN_DIALOG = "beginDialog"; + + /// Event fired when a dialog RepromptDialog is Called. + public static final String REPROMPT_DIALOG = "repromptDialog"; + + /// Event fired when a dialog is canceled. + public static final String CANCEL_DIALOG = "cancelDialog"; + + /// Event fired when an activity is received from the adapter (or a request to reprocess an activity). + public static final String ACTIVITY_RECEIVED = "activityReceived"; + + /// Event which is fired when the system has detected that deployed code has changed the execution of dialogs + /// between turns. + public static final String VERSION_CHANGED = "versionChanged"; + + /// Event fired when there was an exception thrown in the system. + public static final String ERROR = "error"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java new file mode 100644 index 000000000..55879d92f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogInstance.java @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Map; + +/** + * Contains state information associated with a Dialog on a dialog stack. + */ +public class DialogInstance { + @JsonProperty(value = "id") + private String id; + + @JsonProperty(value = "state") + private Map state; + + private int stackIndex; + private String version; + + /** + * Creates a DialogInstance with id and state. + */ + public DialogInstance() { + + } + + /** + * Creates a DialogInstance with id and state. + * @param withId The id + * @param withState The state. + */ + public DialogInstance(String withId, Map withState) { + id = withId; + state = withState; + } + + /** + * Gets the ID of the dialog. + * @return The dialog id. + */ + public String getId() { + return id; + } + + /** + * Sets the ID of the dialog. + * @param withId The dialog id. + */ + public void setId(String withId) { + id = withId; + } + + /** + * Gets the instance's persisted state. + * @return The instance's persisted state. + */ + public Map getState() { + return state; + } + + /** + * Sets the instance's persisted state. + * @param withState The instance's persisted state. + */ + public void setState(Map withState) { + state = withState; + } + + /** + * Gets stack index. Positive values are indexes within the current DC and negative values are + * indexes in the parent DC. + * @return Positive values are indexes within the current DC and negative values are indexes in + * the parent DC. + */ + public int getStackIndex() { + return stackIndex; + } + + /** + * Sets stack index. Positive values are indexes within the current DC and negative values are + * indexes in the parent DC. + * @param withStackIndex Positive values are indexes within the current DC and negative + * values are indexes in the parent DC. + */ + public void setStackIndex(int withStackIndex) { + stackIndex = withStackIndex; + } + + /** + * Gets version string. + * @return Unique string from the dialog this dialoginstance is tracking which is used + * to identify when a dialog has changed in way that should emit an event for changed content. + */ + public String getVersion() { + return version; + } + + /** + * Sets version string. + * @param withVersion Unique string from the dialog this dialoginstance is tracking which + * is used to identify when a dialog has changed in way that should emit + * an event for changed content. + */ + public void setVersion(String withVersion) { + version = withVersion; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java new file mode 100644 index 000000000..38965e924 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java @@ -0,0 +1,408 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.Collection; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.BotStateSet; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextStateCollection; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; +import com.microsoft.bot.schema.Activity; + +import org.apache.commons.lang3.NotImplementedException; + +/** + * Class which runs the dialog system. + */ +public class DialogManager { + + private final String lastAccess = "_lastAccess"; + private String rootDialogId; + private final String dialogStateProperty; + + /** + * Initializes a new instance of the + * {@link com.microsoft.bot.dialogs.DialogManager} class. + * + * @param rootDialog Root dialog to use. + * @param dialogStateProperty Alternate name for the dialogState property. + * (Default is "DialogState"). + */ + public DialogManager(Dialog rootDialog, String dialogStateProperty) { + if (rootDialog != null) { + this.setRootDialog(rootDialog); + } + + this.dialogStateProperty = dialogStateProperty != null ? dialogStateProperty : "DialogState"; + } + + private ConversationState conversationState; + + /** + * Sets the ConversationState. + * + * @param withConversationState The ConversationState. + */ + public void setConversationState(ConversationState withConversationState) { + conversationState = withConversationState; + } + + /** + * Gets the ConversationState. + * + * @return The ConversationState. + */ + public ConversationState getConversationState() { + return conversationState; + } + + private UserState userState; + + /** + * Gets the UserState. + * + * @return UserState. + */ + public UserState getUserState() { + return this.userState; + } + + /** + * Sets the UserState. + * + * @param userState UserState. + */ + public void setUserState(UserState userState) { + this.userState = userState; + } + + private TurnContextStateCollection initialTurnState = new TurnContextStateCollection(); + + /** + * Gets InitialTurnState collection to copy into the TurnState on every turn. + * + * @return TurnState. + */ + public TurnContextStateCollection getInitialTurnState() { + return initialTurnState; + } + + /** + * Gets the Root Dialog. + * + * @return the Root Dialog. + */ + public Dialog getRootDialog() { + if (rootDialogId != null) { + return this.getDialogs().find(rootDialogId); + } else { + return null; + } + + } + + /** + * Sets root dialog to use to start conversation. + * + * @param dialog Root dialog to use to start conversation. + */ + public void setRootDialog(Dialog dialog) { + setDialogs(new DialogSet()); + if (dialog != null) { + rootDialogId = dialog.getId(); + getDialogs().setTelemetryClient(dialog.getTelemetryClient()); + getDialogs().add(dialog); + registerContainerDialogs(dialog, false); + } else { + rootDialogId = null; + } + } + + @JsonIgnore + private DialogSet dialogs = new DialogSet(); + + /** + * Returns the DialogSet. + * + * @return The DialogSet. + */ + public DialogSet getDialogs() { + return dialogs; + } + + /** + * Set the DialogSet. + * + * @param withDialogSet The DialogSet being provided. + */ + public void setDialogs(DialogSet withDialogSet) { + dialogs = withDialogSet; + } + + private DialogStateManagerConfiguration stateManagerConfiguration; + + /** + * Gets the DialogStateManagerConfiguration. + * + * @return The DialogStateManagerConfiguration. + */ + public DialogStateManagerConfiguration getStateManagerConfiguration() { + return this.stateManagerConfiguration; + } + + /** + * Sets the DialogStateManagerConfiguration. + * + * @param withStateManagerConfiguration The DialogStateManagerConfiguration to + * set from. + */ + public void setStateManagerConfiguration(DialogStateManagerConfiguration withStateManagerConfiguration) { + this.stateManagerConfiguration = withStateManagerConfiguration; + } + + private Integer expireAfter; + + /** + * Gets the (optinal) number of milliseconds to expire the bot's state after. + * + * @return Number of milliseconds. + */ + public Integer getExpireAfter() { + return this.expireAfter; + } + + /** + * Sets the (optional) number of milliseconds to expire the bot's state after. + * + * @param withExpireAfter Number of milliseconds. + */ + public void setExpireAfter(Integer withExpireAfter) { + this.expireAfter = withExpireAfter; + } + + /** + * Runs dialog system in the context of an ITurnContext. + * + * @param context Turn Context + * @return result of runnign the logic against the activity. + */ + public CompletableFuture onTurn(TurnContext context) { + BotStateSet botStateSet = new BotStateSet(); + + // Preload TurnState with DM TurnState. + initialTurnState.getTurnStateServices().forEach((key, value) -> { + context.getTurnState().add(key, value); + }); + + // register DialogManager with TurnState. + context.getTurnState().replace(this); + + if (conversationState == null) { + ConversationState cState = context.getTurnState().get(ConversationState.class); + if (cState != null) { + conversationState = cState; + } else { + return Async.completeExceptionally(new IllegalStateException( + String.format("Unable to get an instance of %s from turnContext.", + ConversationState.class.toString()) + )); + } + } else { + context.getTurnState().replace(conversationState); + } + + botStateSet.add(conversationState); + + if (userState == null) { + userState = context.getTurnState().get(UserState.class); + } else { + context.getTurnState().replace(userState); + } + + if (userState != null) { + botStateSet.add(userState); + } + + // create property accessors + StatePropertyAccessor lastAccessProperty = conversationState.createProperty(lastAccess); + + OffsetDateTime lastAccessed = lastAccessProperty.get(context, () -> { + return OffsetDateTime.now(ZoneId.of("UTC")); + }).join(); + + // Check for expired conversation + if (expireAfter != null && (OffsetDateTime.now(ZoneId.of("UTC")).toInstant().toEpochMilli() + - lastAccessed.toInstant().toEpochMilli()) >= expireAfter) { + conversationState.clearState(context).join(); + } + + lastAccessed = OffsetDateTime.now(ZoneId.of("UTC")); + lastAccessProperty.set(context, lastAccessed).join(); + + // get dialog stack + StatePropertyAccessor dialogsProperty = conversationState.createProperty(dialogStateProperty); + + DialogState dialogState = dialogsProperty.get(context, DialogState::new).join(); + + // Create DialogContext + DialogContext dc = new DialogContext(dialogs, context, dialogState); + + dc.getServices().getTurnStateServices().forEach((key, value) -> { + dc.getServices().add(key, value); + }); + + // map TurnState into root dialog context.services + context.getTurnState().getTurnStateServices().forEach((key, value) -> { + dc.getServices().add(key, value); + }); + + // get the DialogStateManager configuration + DialogStateManager dialogStateManager = new DialogStateManager(dc, stateManagerConfiguration); + dialogStateManager.loadAllScopesAsync().join(); + dc.getContext().getTurnState().add(dialogStateManager); + + DialogTurnResult turnResult = null; + + // Loop as long as we are getting valid OnError handled we should continue + // executing the + // actions for the turn. + // NOTE: We loop around this block because each pass through we either complete + // the turn and + // break out of the loop + // or we have had an exception AND there was an OnError action which captured + // the error. + // We need to continue the turn based on the actions the OnError handler + // introduced. + Boolean endOfTurn = false; + while (!endOfTurn) { + try { + ClaimsIdentity claimIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (claimIdentity != null && SkillValidation.isSkillClaim(claimIdentity.claims())) { + // The bot is running as a skill. + turnResult = handleSkillOnTurn().join(); + } else { + // The bot is running as root bot. + turnResult = handleBotOnTurn(dc).join(); + } + + // turn successfully completed, break the loop + endOfTurn = true; + } catch (Exception err) { + // fire error event, bubbling from the leaf. + Boolean handled = dc.emitEvent(DialogEvents.ERROR, err, true, true).join(); + + if (!handled) { + // error was NOT handled, throw the exception and end the turn. (This will + // trigger + // the Adapter.OnError handler and end the entire dialog stack) + return Async.completeExceptionally(new RuntimeException(err)); + } + } + } + + // save all state scopes to their respective botState locations. + dialogStateManager.saveAllChanges(); + + // save BotState changes + botStateSet.saveAllChanges(dc.getContext(), false); + + DialogManagerResult result = new DialogManagerResult(); + result.setTurnResult(turnResult); + return CompletableFuture.completedFuture(result); + } + + /// + /// Helper to send a trace activity with a memory snapshot of the active dialog + /// DC. + /// + private static CompletableFuture sendStateSnapshotTrace(DialogContext dc, String traceLabel) { + // send trace of memory + JsonNode snapshot = getActiveDialogContext(dc).getState().getMemorySnapshot(); + Activity traceActivity = (Activity) Activity.createTraceActivity("Bot State", + "https://www.botframework.com/schemas/botState", snapshot, traceLabel); + dc.getContext().sendActivity(traceActivity).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Recursively walk up the DC stack to find the active DC. + */ + private static DialogContext getActiveDialogContext(DialogContext dialogContext) { + DialogContext child = dialogContext.getChild(); + if (child == null) { + return dialogContext; + } else { + return getActiveDialogContext(child); + } + } + + @SuppressWarnings({"TodoComment"}) + //TODO: Add Skills support here + private CompletableFuture handleSkillOnTurn() { + return Async.completeExceptionally(new NotImplementedException( + "Skills are not implemented in this release" + )); + } + + /** + * Recursively traverses the Dialog tree and registers instances of + * DialogContainer in the DialogSet for this + * instance. + * + * @param dialog Root of the Dialog subtree to iterate and register + * containers from. + * @param registerRoot Whether to register the root of the subtree. + */ + private void registerContainerDialogs(Dialog dialog, Boolean registerRoot) { + if (dialog instanceof DialogContainer) { + if (registerRoot) { + getDialogs().add(dialog); + } + + Collection dlogs = ((DialogContainer) dialog).getDialogs().getDialogs(); + + for (Dialog dlg : dlogs) { + registerContainerDialogs(dlg, true); + } + } + } + + private CompletableFuture handleBotOnTurn(DialogContext dc) { + DialogTurnResult turnResult; + + // the bot is running as a root bot. + if (dc.getActiveDialog() == null) { + // start root dialog + turnResult = dc.beginDialog(rootDialogId).join(); + } else { + // Continue execution + // - This will apply any queued up interruptions and execute the current/next + // step(s). + turnResult = dc.continueDialog().join(); + + if (turnResult.getStatus() == DialogTurnStatus.EMPTY) { + // restart root dialog + turnResult = dc.beginDialog(rootDialogId).join(); + } + } + + sendStateSnapshotTrace(dc, "BotState").join(); + + return CompletableFuture.completedFuture(turnResult); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java new file mode 100644 index 000000000..d73d91c28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManagerResult.java @@ -0,0 +1,68 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.schema.Activity; + +/** + * Represents the result of the Dialog Manager turn. + */ +public class DialogManagerResult { + /** + * The result returned to the caller. + */ + private DialogTurnResult turnResult; + + /** + * The array of resulting activities. + */ + private Activity[] activities; + + /** + * The resulting new state. + */ + private PersistedState newState; + + /** + * @return DialogTurnResult + */ + public DialogTurnResult getTurnResult() { + return this.turnResult; + } + + /** + * @param withTurnResult Sets the turnResult. + */ + public void setTurnResult(DialogTurnResult withTurnResult) { + this.turnResult = withTurnResult; + } + + /** + * @return Activity[] + */ + public Activity[] getActivities() { + return this.activities; + } + + /** + * @param withActivities Sets the activites. + */ + public void setActivities(Activity[] withActivities) { + this.activities = withActivities; + } + + /** + * @return PersistedState + */ + public PersistedState getNewState() { + return this.newState; + } + + /** + * @param withNewState sets the newState. + */ + public void setNewState(PersistedState withNewState) { + this.newState = withNewState; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java new file mode 100644 index 000000000..8827644f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogPath.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for available dialogs. + */ +public final class DialogPath { + + private DialogPath() { + + } + + /** + * Counter of emitted events. + */ + public static final String EVENTCOUNTER = "dialog.eventCounter"; + + /** + * Currently expected properties. + */ + public static final String EXPECTEDPROPERTIES = "dialog.expectedProperties"; + + /** + * Default operation to use for entities where there is no identified operation entity. + */ + public static final String DEFAULTOPERATION = "dialog.defaultOperation"; + + /** + * Last surfaced entity ambiguity event. + */ + public static final String LASTEVENT = "dialog.lastEvent"; + + /** + * Currently required properties. + */ + public static final String REQUIREDPROPERTIES = "dialog.requiredProperties"; + + /** + * Number of retries for the current Ask. + */ + public static final String RETRIES = "dialog.retries"; + + /** + * Last intent. + */ + public static final String LASTINTENT = "dialog.lastIntent"; + + /** + * Last trigger event: defined in FormEvent, ask, clarifyEntity etc. + */ + public static final String LASTTRIGGEREVENT = "dialog.lastTriggerEvent"; + + /** + * Utility function to get just the property name without the memory scope prefix. + * @param property Memory scope property path. + * @return Name of the property without the prefix. + */ + public static String getPropertyName(String property) { + return property.replace("dialog.", ""); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java new file mode 100644 index 000000000..efe2a152d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogReason.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Indicates in which a dialog-related method is being called. + */ +public enum DialogReason { + /// A dialog was started. + BEGIN_CALLED, + + /// A dialog was continued. + CONTINUE_CALLED, + + /// A dialog was ended normally. + END_CALLED, + + /// A dialog was ending because it was replaced. + REPLACE_CALLED, + + /// A dialog was canceled. + CANCEL_CALLED, + + /// A preceding step of the dialog was skipped. + NEXT_CALLED +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java new file mode 100644 index 000000000..c5cbb4b61 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java @@ -0,0 +1,214 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.google.common.hash.Hashing; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; + +import java.nio.charset.StandardCharsets; +import java.util.Collection; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.collections4.map.HashedMap; +import org.apache.commons.lang3.StringUtils; + +/** + * A collection of Dialog objects that can all call each other. + */ +public class DialogSet { + private Map dialogs = new HashedMap<>(); + private StatePropertyAccessor dialogState; + @JsonIgnore + private BotTelemetryClient telemetryClient; + private String version; + + /** + * Initializes a new instance of the DialogSet class. + * + *

+ * To start and control the dialogs in this dialog set, create a DialogContext + * and use its methods to start, continue, or end dialogs. To create a dialog + * context, call createContext(TurnContext). + *

+ * + * @param withDialogState The state property accessor with which to manage the + * stack for this dialog set. + */ + public DialogSet(StatePropertyAccessor withDialogState) { + if (withDialogState == null) { + throw new IllegalArgumentException("DialogState is required"); + } + dialogState = withDialogState; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Creates a DialogSet without state. + */ + public DialogSet() { + dialogState = null; + telemetryClient = new NullBotTelemetryClient(); + } + + /** + * Gets the BotTelemetryClient to use for logging. + * + * @return The BotTelemetryClient to use for logging. + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the BotTelemetryClient to use for logging. + * + *

+ * When this property is set, it sets the Dialog.TelemetryClient of each dialog + * in the set to the new value. + *

+ * + * @param withBotTelemetryClient The BotTelemetryClient to use for logging. + */ + public void setTelemetryClient(BotTelemetryClient withBotTelemetryClient) { + telemetryClient = withBotTelemetryClient != null ? withBotTelemetryClient : new NullBotTelemetryClient(); + + for (Dialog dialog : dialogs.values()) { + dialog.setTelemetryClient(telemetryClient); + } + } + + /** + * Gets a unique string which represents the combined versions of all dialogs in + * this dialogset. + * + * @return Version will change when any of the child dialogs version changes. + */ + public String getVersion() { + if (version == null) { + StringBuilder sb = new StringBuilder(); + for (Dialog dialog : dialogs.values()) { + String v = dialog.getVersion(); + if (!StringUtils.isEmpty(v)) { + sb.append(v); + } + } + + version = Hashing.sha256().hashString(sb.toString(), StandardCharsets.UTF_8).toString(); + } + + return version; + } + + /** + * Adds a new dialog to the set and returns the set to allow fluent chaining. If + * the Dialog.Id being added already exists in the set, the dialogs id will be + * updated to include a suffix which makes it unique. So adding 2 dialogs named + * "duplicate" to the set would result in the first one having an id of + * "duplicate" and the second one having an id of "duplicate2". + * + * @param dialog The dialog to add. The added dialog's Dialog.TelemetryClient is + * set to the BotTelemetryClient of the dialog set. + * @return The dialog set after the operation is complete. + */ + public DialogSet add(Dialog dialog) { + // Ensure new version hash is computed + version = null; + + if (dialog == null) { + throw new IllegalArgumentException("Dialog is required"); + } + + if (dialogs.containsKey(dialog.getId())) { + // If we are trying to add the same exact instance, it's not a name collision. + // No operation required since the instance is already in the dialog set. + if (dialogs.get(dialog.getId()) == dialog) { + return this; + } + + // If we are adding a new dialog with a conflicting name, add a suffix to avoid + // dialog name collisions. + int nextSuffix = 2; + + while (true) { + String suffixId = dialog.getId() + nextSuffix; + + if (!dialogs.containsKey(suffixId)) { + dialog.setId(suffixId); + break; + } + + nextSuffix++; + } + } + + dialog.setTelemetryClient(telemetryClient); + dialogs.put(dialog.getId(), dialog); + + // Automatically add any dependencies the dialog might have + if (dialog instanceof DialogDependencies) { + for (Dialog dependencyDialog : ((DialogDependencies) dialog).getDependencies()) { + add(dependencyDialog); + } + } + + return this; + } + + /** + * Creates a DialogContext which can be used to work with the dialogs in the + * DialogSet. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @return A CompletableFuture representing the asynchronous operation. + */ + public CompletableFuture createContext(TurnContext turnContext) { + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "TurnContext is required" + )); + } + + if (dialogState == null) { + // Note: This shouldn't ever trigger, as the _dialogState is set in the + // constructor + // and validated there. + return Async.completeExceptionally(new IllegalStateException( + "DialogSet.createContext(): DialogSet created with a null StatePropertyAccessor." + )); + } + + // Load/initialize dialog state + return dialogState.get(turnContext, DialogState::new) + .thenApply(state -> new DialogContext(this, turnContext, state)); + } + + /** + * Searches the current DialogSet for a Dialog by its ID. + * + * @param dialogId ID of the dialog to search for. + * @return The dialog if found; otherwise null + */ + public Dialog find(String dialogId) { + if (StringUtils.isEmpty(dialogId)) { + throw new IllegalArgumentException("DialogSet.find, dialogId is required"); + } + + return dialogs.get(dialogId); + } + + /** + * Returns a collection of Dialogs in this DialogSet. + * + * @return The Dialogs in this DialogSet. + */ + public Collection getDialogs() { + return dialogs.values(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java new file mode 100644 index 000000000..29d1fdb01 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains state information for the dialog stack. + */ +public class DialogState { + @JsonProperty(value = "dialogStack") + private List dialogStack = new ArrayList(); + + /** + * Initializes a new instance of the class with an empty stack. + */ + public DialogState() { + this(null); + } + + /** + * Initializes a new instance of the class. + * @param withDialogStack The state information to initialize the stack with. + */ + public DialogState(List withDialogStack) { + dialogStack = withDialogStack != null ? withDialogStack : new ArrayList(); + } + + /** + * Gets the state information for a dialog stack. + * @return State information for a dialog stack. + */ + public List getDialogStack() { + return dialogStack; + } + + /** + * Sets the state information for a dialog stack. + * @param withDialogStack State information for a dialog stack. + */ + public void setDialogStack(List withDialogStack) { + dialogStack = withDialogStack; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java new file mode 100644 index 000000000..484d01111 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnResult.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Result returned to the caller of one of the various stack manipulation methods. + */ +public class DialogTurnResult { + private DialogTurnStatus status; + private Object result; + private boolean parentEnded; + + /** + * Creates a DialogTurnResult with a status. + * @param withStatus The dialog status. + */ + public DialogTurnResult(DialogTurnStatus withStatus) { + this(withStatus, null); + } + + /** + * Creates a DialogTurnResult with a status and result. + * @param withStatus The dialog status. + * @param withResult The result. + */ + public DialogTurnResult(DialogTurnStatus withStatus, Object withResult) { + status = withStatus; + result = withResult; + } + + /** + * Gets the current status of the stack. + * @return The current status of the stack. + */ + public DialogTurnStatus getStatus() { + return status; + } + + /** + * Sets the current status of the stack. + * @param withStatus The current status of the stack. + */ + public void setStatus(DialogTurnStatus withStatus) { + status = withStatus; + } + + /** + * Gets or sets the result returned by a dialog that was just ended. + * + *

This will only be populated in certain cases: + *
- The bot calls `DialogContext.BeginDialogAsync()` to start a new dialog and the dialog + * ends immediately. + *
- The bot calls `DialogContext.ContinueDialogAsync()` and a dialog that was active ends.

+ * + *

In all cases where it's populated, will be `null`.

+ * @return The result returned by a dialog that was just ended. + */ + public Object getResult() { + return result; + } + + /** + * Sets the result returned by a dialog that was just ended. + * @param withResult The result returned by a dialog that was just ended. + */ + public void setResult(Object withResult) { + result = withResult; + } + + /** + * Indicates whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + * @return Whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + */ + public boolean hasParentEnded() { + return parentEnded; + } + + /** + * Sets whether a DialogCommand has ended its parent container and the parent should + * not perform any further processing. + * @param withParentEnded Whether a DialogCommand has ended its parent container and the + * parent should not perform any further processing. + */ + public void setParentEnded(boolean withParentEnded) { + parentEnded = withParentEnded; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java new file mode 100644 index 000000000..1a5a6a84d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogTurnStatus.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Result returned to the caller of one of the various stack manipulation methods. + */ +public enum DialogTurnStatus { + /// Indicates that there is currently nothing on the dialog stack. + EMPTY, + + /// Indicates that the dialog on top is waiting for a response from the user. + WAITING, + + /// Indicates that a dialog completed successfully, the result is available, and no child + /// dialogs to the current context are on the dialog stack. + COMPLETE, + + /// Indicates that the dialog was canceled, and no child + /// dialogs to the current context are on the dialog stack. + CANCELLED, + + /// Current dialog completed successfully, but turn should end. + COMPLETEANDWAIT, +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java new file mode 100644 index 000000000..750bd4cb3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogsComponentRegistration.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.bot.builder.ComponentRegistration; +import com.microsoft.bot.dialogs.memory.ComponentMemoryScopes; +import com.microsoft.bot.dialogs.memory.ComponentPathResolvers; +import com.microsoft.bot.dialogs.memory.PathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.PercentPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtAtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.HashPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.DollarPathResolver; +import com.microsoft.bot.dialogs.memory.scopes.ClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ConversationMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogContextMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.SettingsMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ThisMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.TurnMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.UserMemoryScope; + +/** + * Makes Dialogs components available to the system registering functionality. + */ +public class DialogsComponentRegistration extends ComponentRegistration + implements ComponentMemoryScopes, ComponentPathResolvers { + + /** + * Gets the Dialogs Path Resolvers. + */ + @Override + public Iterable getPathResolvers() { + List listToReturn = new ArrayList(); + listToReturn.add((PathResolver) new DollarPathResolver()); + listToReturn.add((PathResolver) new HashPathResolver()); + listToReturn.add((PathResolver) new AtAtPathResolver()); + listToReturn.add((PathResolver) new AtPathResolver()); + listToReturn.add((PathResolver) new PercentPathResolver()); + return listToReturn; + } + + /** + * Gets the Dialogs Memory Scopes. + */ + @Override + public Iterable getMemoryScopes() { + List listToReturn = new ArrayList(); + listToReturn.add(new TurnMemoryScope()); + listToReturn.add(new SettingsMemoryScope()); + listToReturn.add(new DialogMemoryScope()); + listToReturn.add(new DialogContextMemoryScope()); + listToReturn.add(new DialogClassMemoryScope()); + listToReturn.add(new ClassMemoryScope()); + listToReturn.add(new ThisMemoryScope()); + listToReturn.add(new ConversationMemoryScope()); + listToReturn.add(new UserMemoryScope()); + return listToReturn; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java deleted file mode 100644 index 366a69d0c..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialog.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.microsoft.bot.dialogs; - -// TODO: daveta remove this - not sure where this came from -/** - * Interface for all Dialog objects that can be added to a `DialogSet`. The dialog should generally - * be a singleton and added to a dialog set using `DialogSet.add()` at which point it will be - * assigned a unique ID. - */ -public interface IDialog -{ - /** - * Method called when a new dialog has been pushed onto the stack and is being activated. - * @param dc The dialog context for the current turn of conversation. - * @param dialogArgs (Optional) arguments that were passed to the dialog during `begin()` call that started the instance. - */ - //CompleteableFuture DialogBegin(DialogContext dc, IDictionary dialogArgs = null); - //CompleteableFuture DialogBegin(DialogContext dc, HashMap dialogArgs); - //CompleteableFuture DialogBegin(DialogContext dc); -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java deleted file mode 100644 index bf6c30de2..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/IDialogContinue.java +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.microsoft.bot.dialogs; - - -import java.util.concurrent.CompletableFuture; - -/** - * Interface Dialog objects that can be continued. - */ -public interface IDialogContinue extends IDialog -{ - /** - * Method called when an instance of the dialog is the "current" dialog and the - * user replies with a new activity. The dialog will generally continue to receive the users - * replies until it calls either `DialogSet.end()` or `DialogSet.begin()`. - * If this method is NOT implemented then the dialog will automatically be ended when the user replies. - * @param dc The dialog context for the current turn of conversation. - */ - CompletableFuture DialogContinue(DialogContext dc); -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java deleted file mode 100644 index 96db67810..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/MessageOptions.java +++ /dev/null @@ -1,57 +0,0 @@ -package com.microsoft.bot.dialogs; - -import com.microsoft.bot.schema.AttachmentLayoutTypes; -import com.microsoft.bot.schema.TextFormatTypes; - -/** - * Optional message properties that can be sent {@link Extensions.Say(BotToUser, String MessageOptions,)} - */ -public class MessageOptions -{ - public MessageOptions() - { - this.setTextFormat(TextFormatTypes.MARKDOWN.toString()); - this.setAttachmentLayout(AttachmentLayoutTypes.LIST.toString()); - // this.Attachments = new ArrayList(); - // this.Entities = new ArrayList(); - } - - /** - * Indicates whether the bot is accepting, expecting, or ignoring input - */ - //public string InputHint { get; set; } - - /** - * Format of text fields [plain|markdown] Default:markdown - */ - String textFormat; - public String getTextFormat() { - return this.textFormat; - } - public void setTextFormat(String textFormat) { - this.textFormat = textFormat; - } - - - - /** - * Hint for how to deal with multiple attachments: [list|carousel] Default:list - */ - String attachmentLayout; - public String getAttachmentLayout() { - return this.attachmentLayout; - } - public void setAttachmentLayout(String attachmentLayout) { - this.attachmentLayout = attachmentLayout; - } - - /** - * Attachments - */ - //public IList Attachments { get; set; } - - /** - * Collection of Entity objects, each of which contains metadata about this activity. Each Entity object is typed. - */ - //public IList Entities { get; set; } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java new file mode 100644 index 000000000..61e3c31e3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java @@ -0,0 +1,865 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.NullNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.schema.Serialization; +import java.lang.reflect.Array; +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import org.apache.commons.collections4.IteratorUtils; +import org.apache.commons.lang3.StringUtils; + +/** + * Generic Arraylist of Object. + */ +class Segments extends ArrayList { + + /** + * Returns the first item in the collection. + * @return the first object. + */ + public Object first() { + return get(0); + } + + /** + * Returns the last item in the collection. + * @return the last object. + */ + public Object last() { + return get(size() - 1); + } + + /** + * Gets the SegmentType at the specified index. + * @param index Index of the requested segment. + * @return The SegmentType of item at the requested index. + */ + public SegmentType getSegment(int index) { + return new SegmentType(get(index)); + } +} + +/** + * A class wraps an Object and can assist in determining if it's an integer. + */ +@SuppressWarnings("checkstyle:VisibilityModifier") +class SegmentType { + + public boolean isInt; + public int intValue; + public Segments segmentsValue; + public String stringValue; + + /** + * + * @param value The object to create a SegmentType for. + */ + SegmentType(Object value) { + try { + intValue = Integer.parseInt((String) value); + isInt = true; + } catch (NumberFormatException e) { + isInt = false; + } + + if (!isInt) { + if (value instanceof Segments) { + segmentsValue = (Segments) value; + } else { + stringValue = (String) value; + } + } + } +} + +/** + * Helper methods for working with dynamic json objects. + */ +public final class ObjectPath { + private ObjectPath() { } + + /** + * Does an Object have a subpath. + * @param obj Object. + * @param path path to evaluate. + * @return true if the path is there. + */ + public static boolean hasValue(Object obj, String path) { + return tryGetPathValue(obj, path, Object.class) != null; + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType Type of T + * @return value or default(T). + */ + @SuppressWarnings("checkstyle:InnerAssignment") + public static T getPathValue(Object obj, String path, Class valueType) { + T value; + if ((value = tryGetPathValue(obj, path, valueType)) != null) { + return value; + } + + throw new IllegalArgumentException(path); + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType type of T + * @param defaultValue default value to use if any part of the path is missing. + * @return value or default(T). + */ + @SuppressWarnings("checkstyle:InnerAssignment") + public static T getPathValue(Object obj, String path, Class valueType, T defaultValue) { + T value; + if ((value = tryGetPathValue(obj, path, valueType)) != null) { + return value; + } + + return defaultValue; + } + + /** + * Get the value for a path relative to an Object. + * @param type to return. + * @param obj Object to start with. + * @param path path to evaluate. + * @param valueType value for the path. + * @return true if successful. + */ + public static T tryGetPathValue(Object obj, String path, Class valueType) { + if (obj == null || path == null) { + return null; + } + + if (path.length() == 0) { + return mapValueTo(obj, valueType); + } + + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return null; + } + + Object result = resolveSegments(obj, segments); + if (result == null) { + return null; + } + + // look to see if it's ExpressionProperty and bind it if it is + // NOTE: this bit of duck typing keeps us from adding dependency between adaptiveExpressions and Dialogs. + /* + if (result.GetType().GetProperty("ExpressionText") != null) + { + var method = result.GetType().GetMethod("GetValue", new[] { typeof(Object) }); + if (method != null) + { + result = method.Invoke(result, new[] { obj }); + } + } + */ + + return mapValueTo(result, valueType); + } + + /** + * Given an Object evaluate a path to set the value. + * @param obj Object to start with. + * @param path path to evaluate. + * @param value value to store. + */ + public static void setPathValue(Object obj, String path, Object value) { + setPathValue(obj, path, value, true); + } + + /** + * Given an Object evaluate a path to set the value. + * @param obj Object to start with. + * @param path path to evaluate. + * @param value value to store. + * @param json if true, sets the value as primitive JSON Objects. + */ + public static void setPathValue(Object obj, String path, Object value, boolean json) { + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return; + } + + Object current = obj; + for (int i = 0; i < segments.size() - 1; i++) { + SegmentType segment = segments.getSegment(i); + Object next; + if (segment.isInt) { + if (((Segments) current).size() <= segment.intValue) { + // TODO make sure growBy is correct + // Expand array to index + int growBy = segment.intValue - ((Segments) current).size(); + ((ArrayNode) current).add(growBy); + } + next = ((ArrayNode) current).get(segment.intValue); + } else { + next = getObjectProperty(current, segment.stringValue); + if (next == null) { + // Create Object or array base on next segment + SegmentType nextSegment = new SegmentType(segments.get(i + 1)); + if (nextSegment.stringValue != null) { + setObjectSegment(current, segment.stringValue, JsonNodeFactory.instance.objectNode()); + } else { + setObjectSegment(current, segment.stringValue, JsonNodeFactory.instance.arrayNode()); + } + next = getObjectProperty(current, segment.stringValue); + } + } + + current = next; + } + + Object lastSegment = segments.last(); + setObjectSegment(current, lastSegment, value, json); + } + + /** + * Remove path from Object. + * @param obj Object to change. + * @param path Path to remove. + */ + public static void removePathValue(Object obj, String path) { + Segments segments = tryResolvePath(obj, path); + if (segments == null) { + return; + } + + Object current = obj; + for (int i = 0; i < segments.size() - 1; i++) { + Object segment = segments.get(i); + current = resolveSegment(current, segment); + if (current == null) { + return; + } + } + + if (current != null) { + Object lastSegment = segments.last(); + if (lastSegment instanceof String) { + // lastSegment is a field name + if (current instanceof Map) { + ((Map) current).remove((String) lastSegment); + } else { + ((ObjectNode) current).remove((String) lastSegment); + } + } else { + // lastSegment is an index + ((ArrayNode) current).set((int) lastSegment, null); + } + } + } + + /** + * Apply an action to all properties in an Object. + * @param obj Object to map against. + * @param action Action to take. + */ + public static void forEachProperty(Object obj, BiConsumer action) { + if (obj instanceof Map) { + ((Map) obj).forEach(action); + } else if (obj instanceof ObjectNode) { + ObjectNode node = (ObjectNode) obj; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + action.accept(field, node.findValue(field)); + } + } + } + + /** + * Get all properties in an Object. + * @param obj Object to enumerate property names. + * @return enumeration of property names on the Object if it is not a value type. + */ + public static Collection getProperties(Object obj) { + if (obj == null) { + return new ArrayList<>(); + } else if (obj instanceof Map) { + return ((Map) obj).keySet(); + } else if (obj instanceof JsonNode) { + return IteratorUtils.toList(((JsonNode) obj).fieldNames()); + } else { + List fields = new ArrayList<>(); + for (Field field : obj.getClass().getDeclaredFields()) { + fields.add(field.getName()); + } + + return fields; + } + } + + /** + * Detects if property exists on Object. + * @param obj Object. + * @param name name of the property. + * @return true if found. + */ + public static boolean containsProperty(Object obj, String name) { + if (obj == null) { + return false; + } + + if (obj instanceof Map) { + return ((Map) obj).containsKey(name); + } + + if (obj instanceof JsonNode) { + return ((JsonNode) obj).findValue(name) != null; + } + + for (Field field : obj.getClass().getDeclaredFields()) { + if (StringUtils.equalsIgnoreCase(field.getName(), name)) { + return true; + } + } + return false; + } + + /** + * Clone an Object. + * @param Type to clone. + * @param obj The Object. + * @return The Object as Json. + */ + public static T clone(T obj) { + return (T) Serialization.getAs(obj, obj.getClass()); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Object type. + * @param startObject Intial Object. + * @param overlayObject Overlay Object. + * @return merged Object. + */ + public static T merge(Object startObject, Object overlayObject) { + return (T) assign(startObject, overlayObject); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Object type. + * @param startObject Intial Object. + * @param overlayObject Overlay Object. + * @param type Type of T + * @return merged Object. + */ + public static T merge(Object startObject, Object overlayObject, Class type) { + return (T) assign(startObject, overlayObject, type); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The target type. + * @param startObject overlay Object of any type. + * @param overlayObject overlay Object of any type. + * @return merged Object. + */ + public static T assign(T startObject, Object overlayObject) { + // FIXME this won't work for null startObject + return (T) assign(startObject, overlayObject, startObject.getClass()); + } + + /** + * Equivalent to javascripts ObjectPath.Assign, creates a new Object from startObject + * overlaying any non-null values from the overlay Object. + * @param The Target type. + * @param startObject intial Object of any type. + * @param overlayObject overlay Object of any type. + * @param type type to output. + * @return merged Object. + */ + public static T assign(Object startObject, Object overlayObject, Class type) { + if (startObject != null && overlayObject != null) { + // make a deep clone JsonNode of the startObject + JsonNode merged = startObject instanceof JsonNode + ? (JsonNode) clone(startObject) + : Serialization.objectToTree(startObject); + + // get a JsonNode of the overlay Object + JsonNode overlay = overlayObject instanceof JsonNode + ? (JsonNode) overlayObject + : Serialization.objectToTree(overlayObject); + + merge(merged, overlay); + + return Serialization.treeToValue(merged, type); + } + + Object singleObject = startObject != null ? startObject : overlayObject; + if (singleObject != null) { + if (singleObject instanceof JsonNode) { + return Serialization.treeToValue((JsonNode) singleObject, type); + } + + return (T) singleObject; + } + + return null; +// TODO default object +// try { +// return singleObject.newInstance(); +// } catch (InstantiationException | IllegalAccessException e) { +// return null; +// } + } + + private static void merge(JsonNode startObject, JsonNode overlayObject) { + Set keySet = mergeKeys(startObject, overlayObject); + + for (String key : keySet) { + JsonNode targetValue = startObject.findValue(key); + JsonNode sourceValue = overlayObject.findValue(key); + + // skip empty overlay items + if (!isNull(sourceValue)) { + if (sourceValue instanceof ObjectNode) { + if (isNull(targetValue)) { + ((ObjectNode) startObject).set(key, clone(sourceValue)); + } else { + merge(targetValue, sourceValue); + } + } else { //if (targetValue instanceof NullNode) { + ((ObjectNode) startObject).set(key, clone(sourceValue)); + } + } + } + } + + private static boolean isNull(JsonNode node) { + return node == null || node instanceof NullNode; + } + + private static Set mergeKeys(JsonNode startObject, JsonNode overlayObject) { + Set keySet = new HashSet<>(); + Iterator> iter = startObject.fields(); + while (iter.hasNext()) { + Entry entry = iter.next(); + keySet.add(entry.getKey()); + } + + iter = overlayObject.fields(); + while (iter.hasNext()) { + Entry entry = iter.next(); + keySet.add(entry.getKey()); + } + + return keySet; + } + + /// + /// Convert a generic Object to a typed Object. + /// + /// type to convert to. + /// value to convert. + /// converted value. + /** + * Convert a generic Object to a typed Object. + * @param type to convert to. + * @param val value to convert. + * @param valueType Type of T + * @return converted value. + */ + public static T mapValueTo(Object val, Class valueType) { + if (val.getClass().equals(valueType)) { + return (T) val; + } + + if (val instanceof JsonNode) { + return Serialization.treeToValue((JsonNode) val, valueType); + } + + return Serialization.getAs(val, valueType); + + /* + if (val instanceof JValue) + { + return ((JValue)val).ToObject(); + } + + if (typeof(T) == typeof(Object)) + { + return (T)val; + } + + if (val is JArray) + { + return ((JArray)val).ToObject(); + } + + if (val is JObject) + { + return ((JObject)val).ToObject(); + } + + if (typeof(T) == typeof(JObject)) + { + return (T)(Object)JObject.FromObject(val); + } + + if (typeof(T) == typeof(JArray)) + { + return (T)(Object)JArray.FromObject(val); + } + + if (typeof(T) == typeof(JValue)) + { + return (T)(Object)JValue.FromObject(val); + } + + if (val is T) + { + return (T)val; + } + + return JsonConvert.DeserializeObject(JsonConvert.SerializeObject(val, _expressionCaseSettings)); + */ + } + + /** + * Given an root Object and property path, resolve to a constant if eval = true or a constant path otherwise. + * conversation[user.name][user.age] => ['conversation', 'joe', 32]. + * @param Type of T + * @param obj root Object. + * @param propertyPath property path to resolve. + * @return True if it was able to resolve all nested references. + */ + public static Segments tryResolvePath(Object obj, String propertyPath) { + return tryResolvePath(obj, propertyPath, false); + } + + /** + * Given an root Object and property path, resolve to a constant if eval = true or a constant path otherwise. + * conversation[user.name][user.age] => ['conversation', 'joe', 32]. + * @param Type of T + * @param obj root Object. + * @param propertyPath property path to resolve. + * @param eval True to evaluate resulting segments. + * @return True if it was able to resolve all nested references. + */ + public static Segments tryResolvePath(Object obj, String propertyPath, boolean eval) { + Segments soFar = new Segments(); + char first = propertyPath.length() > 0 ? propertyPath.charAt(0) : ' '; + if (first == '\'' || first == '"') { + if (!propertyPath.endsWith(String.valueOf(first))) { + return null; + } + + soFar.add(propertyPath.substring(1, propertyPath.length() - 1)); + } else if (isInt(propertyPath)) { + soFar.add(Integer.parseInt(propertyPath)); + } else { + int start = 0; + int i; + + // Scan path evaluating as we go + for (i = 0; i < propertyPath.length(); ++i) { + char ch = propertyPath.charAt(i); + if (ch == '.' || ch == '[') { + // emit + String segment = propertyPath.substring(start, i); + if (!StringUtils.isEmpty(segment)) { + soFar.add(segment); + } + start = i + 1; + } + + if (ch == '[') { + // Bracket expression + int nesting = 1; + while (++i < propertyPath.length()) { + ch = propertyPath.charAt(i); + if (ch == '[') { + ++nesting; + } else if (ch == ']') { + --nesting; + if (nesting == 0) { + break; + } + } + } + + if (nesting > 0) { + // Unbalanced brackets + return null; + } + + String expr = propertyPath.substring(start, i); + start = i + 1; + Segments indexer = tryResolvePath(obj, expr, true); + if (indexer == null || indexer.size() != 1) { + // Could not resolve bracket expression + return null; + } + + String result = mapValueTo(indexer.first(), String.class); + if (isInt(result)) { + soFar.add(Integer.parseInt(result)); + } else { + soFar.add(result); + } + } + } + + // emit + String segment = propertyPath.substring(start, i); + if (!StringUtils.isEmpty(segment)) { + soFar.add(segment); + } + start = i + 1; + + if (eval) { + Object result = resolveSegments(obj, soFar); + if (result == null) { + return null; + } + + soFar.clear(); + soFar.add(mapValueTo(result, Object.class)); + } + } + + return soFar; + } + + private static Object resolveSegment(Object current, Object segment) { + if (current != null) { + if (segment instanceof Integer) { + int index = (Integer) segment; + + if (current instanceof List) { + current = ((List) current).get(index); + } else { + current = Array.get(current, index); + } + } else { + current = getObjectProperty(current, (String) segment); + } + } + + return current; + } + + private static Object resolveSegments(Object current, Segments segments) { + Object result = current; + for (Object segment : segments) { + result = resolveSegment(result, segment); + if (result == null) { + return null; + } + } + + return result; + } + + /// + /// Get a property or array element from an Object. + /// + /// Object. + /// property or array segment to get relative to the Object. + /// the value or null if not found. + private static Object getObjectProperty(Object obj, String property) { + if (obj == null) { + return null; + } + + if (obj instanceof Map) { + Map dict = (Map) obj; + List> matches = dict.entrySet().stream() + .filter(key -> key.getKey().equalsIgnoreCase(property)) + .collect(Collectors.toList()); + + if (matches.size() > 0) { + return matches.get(0).getValue(); + } + + return null; + } + + if (obj instanceof JsonNode) { + JsonNode node = (JsonNode) obj; + Iterator fields = node.fieldNames(); + while (fields.hasNext()) { + String field = fields.next(); + if (field.equalsIgnoreCase(property)) { + return node.findValue(property); + } + } + return null; + } + + /* + //!!! not sure Java equiv + if (obj is JValue jval) + { + // in order to make things like "this.value.Length" work, when "this.value" is a String. + return getObjectProperty(jval.Value, property); + } + */ + + // reflection on Object + List matches = Arrays.stream(obj.getClass().getDeclaredFields()) + .filter(field -> field.getName().equalsIgnoreCase(property)) + .map(field -> { + try { + return field.get(obj); + } catch (IllegalAccessException e) { + return null; + } + }) + .collect(Collectors.toList()); + + if (matches.size() > 0) { + return matches.get(0); + } + return null; + } + + /// + /// Given an Object, set a property or array element on it with a value. + /// + /// Object to modify. + /// property or array segment to put the value in. + /// value to store. + /// if true, value will be normalized to JSON primitive Objects. + @SuppressWarnings("PMD.UnusedFormalParameter") + private static void setObjectSegment(Object obj, Object segment, Object value) { + setObjectSegment(obj, segment, value, true); + } + + @SuppressWarnings("PMD.EmptyCatchBlock") + private static void setObjectSegment(Object obj, Object segment, Object value, boolean json) { + Object normalizedValue = getNormalizedValue(value, json); + + // Json Array + if (segment instanceof Integer) { + ArrayNode jar = (ArrayNode) obj; + int index = (Integer) segment; + + if (index >= jar.size()) { + jar.add(index + 1 - jar.size()); + } + + jar.set(index, Serialization.objectToTree(normalizedValue)); + return; + } + + // Map + String property = (String) segment; + if (obj instanceof Map) { + Boolean wasSet = false; + Map dict = (Map) obj; + for (String key : dict.keySet()) { + if (key.equalsIgnoreCase(property)) { + wasSet = true; + dict.put(key, normalizedValue); + break; + } + } + if (!wasSet) { + dict.put(property, normalizedValue); + } + + return; + } + + // ObjectNode + if (obj instanceof ObjectNode) { + boolean wasSet = false; + ObjectNode node = (ObjectNode) obj; + Iterator fields = node.fieldNames(); + while (fields.hasNext()) { + String field = fields.next(); + if (field.equalsIgnoreCase(property)) { + wasSet = true; + node.set(property, Serialization.objectToTree(normalizedValue)); + break; + } + } + if (!wasSet) { + node.set(property, Serialization.objectToTree(normalizedValue)); + } + + return; + } + + // reflection + if (obj != null) { + for (Field f : obj.getClass().getDeclaredFields()) { + if (f.getName().equalsIgnoreCase(property)) { + try { + f.set(obj, normalizedValue); + } catch (IllegalAccessException ignore) { + } + } + } + } + } + + /// + /// Normalize value as json Objects. + /// + /// value to normalize. + /// normalize as json Objects. + /// normalized value. + private static Object getNormalizedValue(Object value, boolean json) { + Object val; + + if (json) { + //TODO revisit this (from dotnet) + if (value instanceof JsonNode) { + val = clone(value); + } else if (value == null) { + val = null; + } else { + val = clone(value); + } + } else { + val = value; + } + + return val; + } + + private static boolean isInt(String value) { + try { + Integer.parseInt(value); + return true; + } catch (NumberFormatException e) { + return false; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java new file mode 100644 index 000000000..e2e7bf03f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedState.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.HashMap; + +/** + * Represents the persisted data across turns. + */ +public class PersistedState { + + /** + * The user state. + */ + private HashMap userState; + + /** + * The converation state. + */ + private HashMap conversationState; + + /** + * Constructs a PersistedState object. + */ + public PersistedState() { + userState = new HashMap(); + conversationState = new HashMap(); + } + + /** + * Initializes a new instance of the PersistedState class. + * + * @param keys The persisted keys. + * @param data The data containing the state values. + */ + @SuppressWarnings("unchecked") + public PersistedState(PersistedStateKeys keys, HashMap data) { + if (data.containsKey(keys.getUserState())) { + userState = (HashMap) data.get(keys.getUserState()); + } + if (data.containsKey(keys.getConversationState())) { + userState = (HashMap) data.get(keys.getConversationState()); + } + } + + /** + * @return userState Gets the user profile data. + */ + public HashMap getUserState() { + return this.userState; + } + + /** + * @param withUserState Sets user profile data. + */ + public void setUserState(HashMap withUserState) { + this.userState = withUserState; + } + + /** + * @return conversationState Gets the dialog state data. + */ + public HashMap getConversationState() { + return this.conversationState; + } + + /** + * @param withConversationState Sets the dialog state data. + */ + public void setConversationState(HashMap withConversationState) { + this.conversationState = withConversationState; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java new file mode 100644 index 000000000..17c10440a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/PersistedStateKeys.java @@ -0,0 +1,48 @@ +// Licensed under the MIT License. +// Copyright (c) Microsoft Corporation. All rights reserved. + +package com.microsoft.bot.dialogs; + +/** + * These are the keys which are persisted. + */ +public class PersistedStateKeys { + /** + * The key for the user state. + */ + private String userState; + + /** + * The conversation state. + */ + private String conversationState; + + /** + * @return Gets the user state; + */ + public String getUserState() { + return this.userState; + } + + /** + * @param withUserState Sets the user state. + */ + public void setUserState(String withUserState) { + this.userState = withUserState; + } + + /** + * @return Gets the conversation state. + */ + public String getConversationState() { + return this.conversationState; + } + + /** + * @param withConversationState Sets the conversation state. + */ + public void setConversationState(String withConversationState) { + this.conversationState = withConversationState; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java new file mode 100644 index 000000000..5ad4a54bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ScopePath.java @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines paths for the available scopes. + */ +public final class ScopePath { + + private ScopePath() { + } + + /** + * User memory scope root path. + */ + public static final String USER = "user"; + + /** + * Conversation memory scope root path. + */ + public static final String CONVERSATION = "conversation"; + + /** + * Dialog memory scope root path. + */ + public static final String DIALOG = "dialog"; + + /** + * DialogClass memory scope root path. + */ + public static final String DIALOG_CLASS = "dialogclass"; + + /** + * DialogContext memory scope root path. + */ + public static final String DIALOG_CONTEXT = "dialogContext"; + + /** + * This memory scope root path. + */ + public static final String THIS = "this"; + + /** + * Class memory scope root path. + */ + public static final String CLASS = "class"; + + /** + * Settings memory scope root path. + */ + public static final String SETTINGS = "settings"; + + /** + * Turn memory scope root path. + */ + public static final String TURN = "turn"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java new file mode 100644 index 000000000..b4b69f417 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ThisPath.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path passed to the active dialog. + */ +public final class ThisPath { + + private ThisPath() { + } + + /** + * The options that were passed to the active dialog via options argument of + * BeginDialog. + */ + public static final String OPTIONS = "this.options"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java new file mode 100644 index 000000000..17d053f1c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/TurnPath.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +/** + * Defines path for avaiable turns. + */ +public final class TurnPath { + private TurnPath() { } + + /// The result from the last dialog that was called. + public static final String LAST_RESULT = "turn.lastresult"; + + /// The current activity for the turn. + public static final String ACTIVITY = "turn.activity"; + + /// The recognized result for the current turn. + public static final String RECOGNIZED = "turn.recognized"; + + /// Path to the top intent. + public static final String TOP_INTENT = "turn.recognized.intent"; + + /// Path to the top score. + public static final String TOP_SCORE = "turn.recognized.score"; + + /// Original text. + public static final String TEXT = "turn.recognized.text"; + + /// Original utterance split into unrecognized strings. + public static final String UNRECOGNIZED_TEXT = "turn.unrecognizedText"; + + /// Entities that were recognized from text. + public static final String RECOGNIZED_ENTITIES = "turn.recognizedEntities"; + + /// If true an interruption has occured. + public static final String INTERRUPTED = "turn.interrupted"; + + /// The current dialog event (set during event processing). + public static final String DIALOG_EVENT = "turn.dialogEvent"; + + /// Used to track that we don't end up in infinite loop of RepeatDialogs(). + public static final String REPEATED_IDS = "turn.repeatedIds"; + + /// This is a bool which if set means that the turncontext.activity has been consumed by + // some component in the system. + public static final String ACTIVITY_PROCESSED = "turn.activityProcessed"; + + /** + * Utility function to get just the property name without the memory scope prefix. + * @param property memory scope property path. + * @return name of the property without the prefix. + */ + public static String getPropertyName(String property) { + return property.replace("turn.", ""); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java new file mode 100644 index 000000000..aedde17ba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java @@ -0,0 +1,277 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.StringUtils; + +/** + * Dialog optimized for prompting a user with a series of questions. Waterfalls + * accept a stack of functions which will be executed in sequence. Each + * waterfall step can ask a question of the user and the user's response will be + * passed as an argument to the next waterfall step. + */ +public class WaterfallDialog extends Dialog { + + private final String persistedOptions = "options"; + private final String stepIndex = "stepIndex"; + private final String persistedValues = "values"; + private final String persistedInstanceId = "instanceId"; + + private final List steps; + + /** + * Initializes a new instance of the {@link waterfallDialog} class. + * + * @param dialogId The dialog ID. + * @param actions Optional actions to be defined by the caller. + */ + public WaterfallDialog(String dialogId, Collection actions) { + super(dialogId); + steps = actions != null ? new ArrayList(actions) : new ArrayList(); + } + + /** + * Gets a unique String which represents the version of this dialog. If the + * version changes between turns the dialog system will emit a DialogChanged + * event. + * + * @return Version will change when steps count changes (because dialog has no + * way of evaluating the content of the steps. + */ + public String getVersion() { + return String.format("%s:%d", getId(), steps.size()); + } + + /** + * Adds a new step to the waterfall. + * + * @param step Step to add. + * @return Waterfall dialog for fluent calls to `AddStep()`. + */ + public WaterfallDialog addStep(WaterfallStep step) { + if (step == null) { + throw new IllegalArgumentException("step cannot be null"); + } + steps.add(step); + return this; + } + + /** + * Called when the waterfall dialog is started and pushed onto the dialog stack. + * + * @param dc The + * @param options Optional, initial information to pass to the dialog. + * @return A CompletableFuture representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Initialize waterfall state + Map state = dc.getActiveDialog().getState(); + String instanceId = UUID.randomUUID().toString(); + state.put(persistedOptions, options); + state.put(persistedValues, new HashMap()); + state.put(persistedInstanceId, instanceId); + + Map properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallStart", properties); + getTelemetryClient().trackDialogView(getId(), null, null); + + // Run first step + return runStep(dc, 0, DialogReason.BEGIN_CALLED, null); + } + + /** + * Called when the waterfall dialog is _continued_, where it is the active + * dialog and the user replies with a new activity. + * + * @param dc The + * @return A CompletableFuture representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Don't do anything for non-message activities. + if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + return CompletableFuture.completedFuture(END_OF_TURN); + } + + // Run next step with the message text as the result. + return resumeDialog(dc, DialogReason.CONTINUE_CALLED); + } + + /** + * Called when a child waterfall dialog completed its turn, returning control to + * this dialog. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Increment step index and run step + Map state = dc.getActiveDialog().getState(); + int index = 0; + if (state.containsKey(stepIndex)) { + index = (int) state.get(stepIndex); + } + + return runStep(dc, index + 1, reason, result); + } + + /** + * Called when the dialog is ending. + * + * @param turnContext Context for the current turn of the conversation. + * @param instance The instance of the current dialog. + * @param reason The reason the dialog is ending. + * + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + if (reason == DialogReason.CANCEL_CALLED) { + HashMap state = new HashMap((Map) instance.getState()); + + // Create step context + int index = (int) state.get(stepIndex); + String stepName = waterfallStepName(index); + String instanceId = (String) state.get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("StepName", stepName); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallCancel", properties); + } else if (reason == DialogReason.END_CALLED) { + HashMap state = new HashMap((Map) instance.getState()); + String instanceId = (String) state.get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("InstanceId", instanceId); + getTelemetryClient().trackEvent("WaterfallComplete", properties); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Called when an individual waterfall step is being executed. + * + * @param stepContext Context for the waterfall step to execute. + * + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onStep(WaterfallStepContext stepContext) { + String stepName = waterfallStepName(stepContext.getIndex()); + String instanceId = (String) stepContext.getActiveDialog().getState().get(persistedInstanceId); + + HashMap properties = new HashMap(); + properties.put("DialogId", getId()); + properties.put("StepName", stepName); + properties.put("InstanceId", instanceId); + + getTelemetryClient().trackEvent("WaterfallStep", properties); + + return steps.get(stepContext.getIndex()).waterfallStep(stepContext); + } + + /** + * Excutes a step of the waterfall dialog. + * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @param index The index of the current waterfall step to execute. + * @param reason The reason the waterfall step is being executed. + * @param result Result returned by a dialog called in the previous waterfall step. + * + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture runStep(DialogContext dc, int index, + DialogReason reason, Object result) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (index < steps.size()) { + // Update persisted step index + Map state = (Map) dc.getActiveDialog().getState(); + + state.put(stepIndex, index); + + // Create step context + Object options = state.get(persistedOptions); + Map values = (Map) state.get(persistedValues); + WaterfallStepContext stepContext = + new WaterfallStepContext(this, dc, options, values, index, reason, result); + + // Execute step + return onStep(stepContext); + } + + // End of waterfall so just return any result to parent + return dc.endDialog(result); + } + + private String waterfallStepName(int index) { + // Log Waterfall Step event. Each event has a distinct name to hook up + // to the Application Insights funnel. + String stepName = steps.get(index).getClass().getSimpleName(); + + // Default stepname for lambdas + if (StringUtils.isAllBlank(stepName) || stepName.contains("<")) { + stepName = String.format("Step%0of%1", index + 1, steps.size()); + } + + return stepName; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java new file mode 100644 index 000000000..37266c05f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStep.java @@ -0,0 +1,23 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +/** + * A interface definition of a Waterfall step. This is implemented by + * application code. + */ +public interface WaterfallStep { + /** + * A interface definition of a Waterfall step. This is implemented by + * application code. + * + * @param stepContext The WaterfallStepContext for this waterfall dialog. + * + * @return A {@link CompletableFuture} of {@link DialogTurnResult} representing + * the asynchronous operation. + */ + CompletableFuture waterfallStep(WaterfallStepContext stepContext); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java new file mode 100644 index 000000000..91794a485 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallStepContext.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; + +/** + * Provides context for a step in a {@link waterfallDialog} . + * + * The {@link DialogContext#context} property contains the {@link TurnContext} + * for the current turn. + */ +public class WaterfallStepContext extends DialogContext { + + private final WaterfallDialog parentWaterfall; + private Boolean nextCalled; + + /** + * Initializes a new instance of the {@link WaterfallStepContext} class. + * + * @param parentWaterfall The parent of the waterfall dialog. + * @param dc The dialog's context. + * @param options Any options to call the waterfall dialog with. + * @param values A dictionary of values which will be persisted across all + * waterfall steps. + * @param index The index of the current waterfall to execute. + * @param reason The reason the waterfall step is being executed. + * @param result Results returned by a dialog called in the previous waterfall + * step. + */ + public WaterfallStepContext( + WaterfallDialog parentWaterfall, + DialogContext dc, + Object options, + Map values, + int index, + DialogReason reason, + Object result) { + super(dc.getDialogs(), dc, new DialogState(dc.getStack())); + this.parentWaterfall = parentWaterfall; + this.nextCalled = false; + this.setParent(dc.getParent()); + this.index = index; + this.options = options; + this.reason = reason; + this.result = result; + this.values = values; + } + + private int index; + + private Object options; + + private DialogReason reason; + + private Object result; + + private Map values; + + /** + * Gets the index of the current waterfall step being executed. + * @return returns the index value; + */ + public int getIndex() { + return this.index; + } + + /** + * Gets any options the waterfall dialog was called with. + * @return The options. + */ + public Object getOptions() { + return this.options; + } + + /** + * Gets the reason the waterfall step is being executed. + * @return The DialogReason + */ + public DialogReason getReason() { + return this.reason; + } + + /** + * Gets the result from the previous waterfall step. + * + * The result is often the return value of a child dialog that was started in the previous step + * of the waterfall. + * @return the Result value. + */ + public Object getResult() { + return this.result; + } + + /** + * Gets a dictionary of values which will be persisted across all waterfall actions. + * @return The Dictionary of values. + */ + public Map getValues() { + return this.values; + } + + /** + * Skips to the next step of the waterfall. + * + * @param result Optional, result to pass to the next step of the current waterfall + * dialog. + * @return A CompletableFuture that represents the work queued to execute. + * + * In the next step of the waterfall, the {@link result} property of the waterfall step context + * will contain the value of the . + */ + + public CompletableFuture next(Object result) { + // Ensure next hasn't been called + if (nextCalled) { + return Async.completeExceptionally(new IllegalStateException( + String.format("WaterfallStepContext.next(): method already called for dialog and step '%0 %1", + parentWaterfall.getId(), index) + )); + } + + // Trigger next step + nextCalled = true; + return parentWaterfall.resumeDialog(this, DialogReason.NEXT_CALLED, result); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java new file mode 100644 index 000000000..647bfd4bf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Channel.java @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Channels; +import org.apache.commons.lang3.StringUtils; + +/** + * Methods for determining channel specific functionality. + */ +public final class Channel { + private Channel() { } + + /** + * Determine if a number of Suggested Actions are supported by a Channel. + * + * @param channelId The Channel to check the if Suggested Actions are supported in. + * @return True if the Channel supports the buttonCnt total Suggested Actions, False if + * the Channel does not support that number of Suggested Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsSuggestedActions(String channelId) { + return supportsSuggestedActions(channelId, 100); + } + + /** + * Determine if a number of Suggested Actions are supported by a Channel. + * + * @param channelId The Channel to check the if Suggested Actions are supported in. + * @param buttonCnt The number of Suggested Actions to check for the Channel. + * @return True if the Channel supports the buttonCnt total Suggested Actions, False if + * the Channel does not support that number of Suggested Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsSuggestedActions(String channelId, int buttonCnt) { + switch (channelId) { + // https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies + case Channels.FACEBOOK: + case Channels.SKYPE: + return buttonCnt <= 10; + + // https://developers.line.biz/en/reference/messaging-api/#items-object + case Channels.LINE: + return buttonCnt <= 13; + + // https://dev.kik.com/#/docs/messaging#text-response-object + case Channels.KIK: + return buttonCnt <= 20; + + case Channels.TELEGRAM: + case Channels.EMULATOR: + case Channels.DIRECTLINE: + case Channels.DIRECTLINESPEECH: + case Channels.WEBCHAT: + return buttonCnt <= 100; + + default: + return false; + } + } + + /** + * Determine if a number of Card Actions are supported by a Channel. + * + * @param channelId The Channel to check if the Card Actions are supported in. + * @return True if the Channel supports the buttonCnt total Card Actions, False if the + * Channel does not support that number of Card Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsCardActions(String channelId) { + return supportsCardActions(channelId, 100); + } + + /** + * Determine if a number of Card Actions are supported by a Channel. + * + * @param channelId The Channel to check if the Card Actions are supported in. + * @param buttonCnt The number of Card Actions to check for the Channel. + * @return True if the Channel supports the buttonCnt total Card Actions, False if the + * Channel does not support that number of Card Actions. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static boolean supportsCardActions(String channelId, int buttonCnt) { + switch (channelId) { + case Channels.FACEBOOK: + case Channels.SKYPE: + case Channels.MSTEAMS: + return buttonCnt <= 3; + + case Channels.LINE: + return buttonCnt <= 99; + + case Channels.SLACK: + case Channels.EMULATOR: + case Channels.DIRECTLINE: + case Channels.DIRECTLINESPEECH: + case Channels.WEBCHAT: + case Channels.CORTANA: + return buttonCnt <= 100; + + default: + return false; + } + } + + /** + * Determine if a Channel has a Message Feed. + * @param channelId The Channel to check for Message Feed. + * @return True if the Channel has a Message Feed, False if it does not. + */ + public static boolean hasMessageFeed(String channelId) { + switch (channelId) { + case Channels.CORTANA: + return false; + + default: + return true; + } + } + + /** + * Maximum length allowed for Action Titles. + * + * @param channelId The Channel to determine Maximum Action Title Length. + * @return The total number of characters allowed for an Action Title on a specific Channel. + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static int maxActionTitleLength(String channelId) { + return 20; + } + + /** + * Get the Channel Id from the current Activity on the Turn Context. + * + * @param turnContext The Turn Context to retrieve the Activity's Channel Id from. + * @return The Channel Id from the Turn Context's Activity. + */ + public static String getChannelId(TurnContext turnContext) { + return StringUtils.isEmpty(turnContext.getActivity().getChannelId()) + ? null + : turnContext.getActivity().getChannelId(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java new file mode 100644 index 000000000..66321427d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.schema.CardAction; +import java.util.List; + +/** + * Represents a choice for a choice prompt. + */ +public class Choice { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "action") + private CardAction action; + + @JsonProperty(value = "synonyms") + private List synonyms; + + /** + * Creates a Choice. + */ + public Choice() { + this(null); + } + + /** + * Creates a Choice. + * @param withValue The value. + */ + public Choice(String withValue) { + value = withValue; + } + + /** + * Gets the value to return when selected. + * @return The value. + */ + public String getValue() { + return value; + } + + /** + * Sets the value to return when selected. + * @param withValue The value to return. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the action to use when rendering the choice as a suggested action or hero card. + * @return The action to use. + */ + public CardAction getAction() { + return action; + } + + /** + * Sets the action to use when rendering the choice as a suggested action or hero card. + * @param withAction The action to use. + */ + public void setAction(CardAction withAction) { + action = withAction; + } + + /** + * Gets the list of synonyms to recognize in addition to the value. This is optional. + * @return The list of synonyms. + */ + public List getSynonyms() { + return synonyms; + } + + /** + * Sets the list of synonyms to recognize in addition to the value. This is optional. + * @param withSynonyms The list of synonyms. + */ + public void setSynonyms(List withSynonyms) { + synonyms = withSynonyms; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java new file mode 100644 index 000000000..7e484021e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java @@ -0,0 +1,399 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.InputHints; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +/** + * Assists with formatting a message activity that contains a list of choices. + */ +public final class ChoiceFactory { + private ChoiceFactory() { + } + + /** + * Creates an Activity that includes a list of choices formatted based on the + * capabilities of a given channel. + * + * @param channelId A channel ID. The Connector.Channels class contains known + * channel IDs. + * @param list The list of choices to include. + * @param text The text of the message to send. Can be null. + * @return The created Activity + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static Activity forChannel(String channelId, List list, String text) { + return forChannel(channelId, list, text, null, null); + } + + /** + * Creates an Activity that includes a list of choices formatted based on the + * capabilities of a given channel. + * + * @param channelId A channel ID. The Connector.Channels class contains known + * channel IDs. + * @param list The list of choices to include. + * @param text The text of the message to send. Can be null. + * @param speak The text to be spoken by your bot on a speech-enabled + * channel. Can be null. + * @param options The formatting options to use when rendering as a list. If + * null, the default options are used. + * @return The created Activity + */ + @SuppressWarnings("checkstyle:MagicNumber") + public static Activity forChannel(String channelId, List list, String text, String speak, + ChoiceFactoryOptions options) { + if (list == null) { + list = new ArrayList<>(); + } + + // Find maximum title length + int maxTitleLength = 0; + for (Choice choice : list) { + int l = choice.getAction() != null && !StringUtils.isEmpty(choice.getAction().getTitle()) + ? choice.getAction().getTitle().length() + : choice.getValue().length(); + + if (l > maxTitleLength) { + maxTitleLength = l; + } + } + + // Determine list style + boolean supportsSuggestedActions = Channel.supportsSuggestedActions(channelId, list.size()); + boolean supportsCardActions = Channel.supportsCardActions(channelId, list.size()); + int maxActionTitleLength = Channel.maxActionTitleLength(channelId); + boolean longTitles = maxTitleLength > maxActionTitleLength; + + if (!longTitles && !supportsSuggestedActions && supportsCardActions) { + // SuggestedActions is the preferred approach, but for channels that don't + // support them (e.g. Teams, Cortana) we should use a HeroCard with CardActions + return heroCard(list, text, speak); + } + + if (!longTitles && supportsSuggestedActions) { + // We always prefer showing choices using suggested actions. If the titles are + // too long, however, + // we'll have to show them as a text list. + return suggestedAction(list, text, speak); + } + + if (!longTitles && list.size() <= 3) { + // If the titles are short and there are 3 or less choices we'll use an inline + // list. + return inline(list, text, speak, options); + } + + // Show a numbered list. + return list(list, text, speak, options); + } + + /** + * Creates an Activity that includes a list of choices formatted as an inline + * list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. Can be null. + * @return The created Activity. + */ + public static Activity inline(List choices, String text) { + return inline(choices, text, null, null); + } + + /** + * Creates an Activity that includes a list of choices formatted as an inline + * list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. Can be null. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * Cab be null. + * @param options The formatting options to use when rendering as a list. Can be + * null. + * @return The created Activity. + */ + public static Activity inline(List choices, String text, String speak, ChoiceFactoryOptions options) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // clone options with defaults applied if needed. + ChoiceFactoryOptions opt = new ChoiceFactoryOptions(options); + + // Format list of choices + String connector = ""; + StringBuilder txtBuilder; + if (StringUtils.isNotBlank(text)) { + txtBuilder = new StringBuilder(text).append(' '); + } else { + txtBuilder = new StringBuilder().append(' '); + } + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + String title = choice.getAction() != null && choice.getAction().getTitle() != null + ? choice.getAction().getTitle() + : choice.getValue(); + + txtBuilder.append(connector); + if (opt.getIncludeNumbers()) { + txtBuilder.append('(').append(index + 1).append(") "); + } + + txtBuilder.append(title); + if (index == choices.size() - 2) { + connector = index == 0 ? opt.getInlineOr() : opt.getInlineOrMore(); + } else { + connector = opt.getInlineSeparator(); + } + } + + // Return activity with choices as an inline list. + return MessageFactory.text(txtBuilder.toString(), speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of strings to include as Choices. + * @return The created Activity. + */ + public static Activity listFromStrings(List choices) { + return listFromStrings(choices, null, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of strings to include as Choices. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @param options The formatting options to use when rendering as a list. + * @return The created Activity. + */ + public static Activity listFromStrings(List choices, String text, String speak, + ChoiceFactoryOptions options) { + return list(toChoices(choices), text, speak, options); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity list(List choices) { + return list(choices, null, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity list(List choices, String text) { + return list(choices, text, null, null); + } + + /** + * Creates a message activity that includes a list of choices formatted as a + * numbered or bulleted list. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @param options The formatting options to use when rendering as a list. + * @return The created Activity. + */ + public static Activity list(List choices, String text, String speak, ChoiceFactoryOptions options) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // clone options with defaults applied if needed. + ChoiceFactoryOptions opt = new ChoiceFactoryOptions(options); + + boolean includeNumbers = opt.getIncludeNumbers(); + + // Format list of choices + String connector = ""; + StringBuilder txtBuilder = text == null ? new StringBuilder() : new StringBuilder(text).append("\n\n "); + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + + String title = choice.getAction() != null && !StringUtils.isEmpty(choice.getAction().getTitle()) + ? choice.getAction().getTitle() + : choice.getValue(); + + txtBuilder.append(connector); + if (includeNumbers) { + txtBuilder.append(index + 1).append(". "); + } else { + txtBuilder.append("- "); + } + + txtBuilder.append(title); + connector = "\n "; + } + + // Return activity with choices as a numbered list. + return MessageFactory.text(txtBuilder.toString(), speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of strings to include as actions. + * @return The created Activity. + */ + public static Activity suggestedActionFromStrings(List choices) { + return suggestedActionFromStrings(choices, null, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of strings to include as actions. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity suggestedActionFromStrings(List choices, String text, String speak) { + return suggestedAction(toChoices(choices), text, speak); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices) { + return suggestedAction(choices, null, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices, String text) { + return suggestedAction(choices, text, null); + } + + /** + * Creates an Activity that includes a list of card actions. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity suggestedAction(List choices, String text, String speak) { + // Return activity with choices as suggested actions + return MessageFactory.suggestedCardActions(extractActions(choices), text, speak, InputHints.EXPECTING_INPUT); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @return The created Activity. + */ + public static Activity heroCard(List choices) { + return heroCard(choices, null, null); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @return The created Activity. + */ + public static Activity heroCard(List choices, String text) { + return heroCard(choices, text, null); + } + + /** + * Creates an Activity with a HeroCard based on a list of Choices. + * + * @param choices The list of choices to include. + * @param text The text of the message to send. + * @param speak The text to be spoken by your bot on a speech-enabled channel. + * @return The created Activity. + */ + public static Activity heroCard(List choices, String text, String speak) { + HeroCard card = new HeroCard() { + { + setText(text); + setButtons(extractActions(choices)); + } + }; + + List attachments = new ArrayList() { + /** + * + */ + private static final long serialVersionUID = 1L; + + { + add(card.toAttachment()); + } + }; + + // Return activity with choices as HeroCard with buttons + return MessageFactory.attachment(attachments, null, speak, InputHints.EXPECTING_INPUT); + } + + /** + * Returns a list of strings as a list of Choices. + * + * @param choices The list of strings to convert. + * @return A List of Choices. + */ + public static List toChoices(List choices) { + return choices == null ? new ArrayList<>() : choices.stream().map(Choice::new).collect(Collectors.toList()); + } + + private static List extractActions(List choices) { + if (choices == null) { + choices = new ArrayList<>(); + } + + // Map choices to actions + return choices.stream().map(choice -> { + if (choice.getAction() != null) { + return choice.getAction(); + } + + return new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue(choice.getValue()); + setTitle(choice.getValue()); + } + }; + }).collect(Collectors.toList()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java new file mode 100644 index 000000000..62a20dbda --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryOptions.java @@ -0,0 +1,173 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import org.apache.commons.lang3.StringUtils; + +/** + * Contains formatting options for presenting a list of choices. + */ +public class ChoiceFactoryOptions { + public static final String DEFAULT_INLINE_SEPERATOR = ", "; + public static final String DEFAULT_INLINE_OR = " or "; + public static final String DEFAULT_INLINE_OR_MORE = ", or "; + public static final boolean DEFAULT_INCLUDE_NUMBERS = true; + + /** + * Creates default options. + */ + public ChoiceFactoryOptions() { + this(DEFAULT_INLINE_SEPERATOR, DEFAULT_INLINE_OR, DEFAULT_INLINE_OR_MORE); + } + + /** + * Clones another options object, and applies defaults if needed. + * + * @param options The options object to clone. + */ + public ChoiceFactoryOptions(ChoiceFactoryOptions options) { + this(); + + if (options != null) { + if (!StringUtils.isEmpty(options.getInlineSeparator())) { + setInlineSeparator(options.getInlineSeparator()); + } + + if (!StringUtils.isEmpty(options.getInlineOr())) { + setInlineOr(options.getInlineOr()); + } + + if (!StringUtils.isEmpty(options.getInlineOrMore())) { + setInlineOrMore(options.getInlineOrMore()); + } + + setIncludeNumbers(options.getIncludeNumbers()); + } + } + + /** + * Creates options with the specified formatting values. + * @param withInlineSeparator The inline seperator value. + * @param withInlineOr The inline or value. + * @param withInlineOrMore The inline or more value. + */ + public ChoiceFactoryOptions( + String withInlineSeparator, + String withInlineOr, + String withInlineOrMore + ) { + this(withInlineSeparator, withInlineOr, withInlineOrMore, DEFAULT_INCLUDE_NUMBERS); + } + + /** + * Initializes a new instance of the class. + * Refer to the code in teh ConfirmPrompt for an example of usage. + * @param withInlineSeparator The inline seperator value. + * @param withInlineOr The inline or value. + * @param withInlineOrMore The inline or more value. + * @param withIncludeNumbers Flag indicating whether to include numbers as a choice. + */ + public ChoiceFactoryOptions( + String withInlineSeparator, + String withInlineOr, + String withInlineOrMore, + boolean withIncludeNumbers + ) { + inlineSeparator = withInlineSeparator; + inlineOr = withInlineOr; + inlineOrMore = withInlineOrMore; + includeNumbers = withIncludeNumbers; + } + + @JsonProperty(value = "inlineSeparator") + private String inlineSeparator; + + @JsonProperty(value = "inlineOr") + private String inlineOr; + + @JsonProperty(value = "inlineOrMore") + private String inlineOrMore; + + @JsonProperty(value = "includeNumbers") + private Boolean includeNumbers; + + /** + * Gets the character used to separate individual choices when there are more than 2 choices. + * The default value is `", "`. This is optional. + * + * @return The seperator. + */ + public String getInlineSeparator() { + return inlineSeparator; + } + + /** + * Sets the character used to separate individual choices when there are more than 2 choices. + * @param withSeperator The seperator. + */ + public void setInlineSeparator(String withSeperator) { + inlineSeparator = withSeperator; + } + + /** + * Gets the separator inserted between the choices when their are only 2 choices. The default + * value is `" or "`. This is optional. + * + * @return The separator inserted between the choices when their are only 2 choices. + */ + public String getInlineOr() { + return inlineOr; + } + + /** + * Sets the separator inserted between the choices when their are only 2 choices. + * + * @param withInlineOr The separator inserted between the choices when their are only 2 choices. + */ + public void setInlineOr(String withInlineOr) { + this.inlineOr = withInlineOr; + } + + /** + * Gets the separator inserted between the last 2 choices when their are more than 2 choices. + * The default value is `", or "`. This is optional. + * + * @return The separator inserted between the last 2 choices when their are more than 2 choices. + */ + public String getInlineOrMore() { + return inlineOrMore; + } + + /** + * Sets the separator inserted between the last 2 choices when their are more than 2 choices. + * + * @param withInlineOrMore The separator inserted between the last 2 choices when their + * are more than 2 choices. + */ + public void setInlineOrMore(String withInlineOrMore) { + this.inlineOrMore = withInlineOrMore; + } + + /** + * Gets a value indicating whether an inline and list style choices will be prefixed + * with the index of the choice; as in "1. choice". If false, the list style will use a + * bulleted list instead. The default value is true. + * + * @return If false, the list style will use a bulleted list. + */ + public Boolean getIncludeNumbers() { + return includeNumbers; + } + + /** + * Sets the value indicating whether an inline and list style choices will be prefixed + * with the index of the choice. + * + * @param withIncludeNumbers If false, the list style will use a bulleted list instead. + */ + public void setIncludeNumbers(Boolean withIncludeNumbers) { + this.includeNumbers = withIncludeNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java new file mode 100644 index 000000000..8ae3e5d0a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java @@ -0,0 +1,141 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.recognizers.text.IModel; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import java.util.Locale; +import java.util.stream.Collectors; +import com.microsoft.recognizers.text.number.NumberRecognizer; + +/** + * Contains methods for matching user input against a list of choices. + */ +public final class ChoiceRecognizers { + private ChoiceRecognizers() { } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoicesFromStrings(String utterance, List choices) { + return recognizeChoicesFromStrings(utterance, choices, null); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoicesFromStrings( + String utterance, + List choices, + FindChoicesOptions options + ) { + return recognizeChoices(utterance, choices.stream().map(Choice::new) + .collect(Collectors.toList()), options); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoices(String utterance, List choices) { + return recognizeChoices(utterance, choices, null); + } + + /** + * Matches user input against a list of choices. + * @param utterance The input. + * @param choices The list of string choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> recognizeChoices( + String utterance, + List choices, + FindChoicesOptions options + ) { + // Try finding choices by text search first + // - We only want to use a single strategy for returning results to avoid issues where utterances + // like the "the third one" or "the red one" or "the first division book" would miss-recognize as + // a numerical index or ordinal as well. + String locale = options != null ? options.getLocale() : Locale.ENGLISH.getDisplayName(); + List> matched = Find.findChoices(utterance, choices, options); + if (matched.size() == 0) { + List> matches = new ArrayList<>(); + if (options == null || options.isRecognizeOrdinals()) { + // Next try finding by ordinal + matches = recognizeNumbers(utterance, new NumberRecognizer(locale).getOrdinalModel(locale, true)); + for (ModelResult match : matches) { + matchChoiceByIndex(choices, matched, match); + } + } + + if (matches.size() == 0 && (options == null || options.isRecognizeNumbers())) { + // Then try by numerical index + matches = recognizeNumbers(utterance, new NumberRecognizer(locale).getNumberModel(locale, true)); + for (ModelResult match : matches) { + matchChoiceByIndex(choices, matched, match); + } + } + + // Sort any found matches by their position within the utterance. + // - The results from findChoices() are already properly sorted so we just need this + // for ordinal & numerical lookups. + matched.sort(Comparator.comparingInt(ModelResult::getStart)); + } + + return matched; + } + + private static void matchChoiceByIndex( + List list, + List> matched, + ModelResult match + ) { + try { + // converts Resolution Values containing "end" (e.g. utterance "last") in numeric values. + String value = match.getResolution().getValue().replace("end", Integer.toString(list.size())); + int index = Integer.parseInt(value) - 1; + if (index >= 0 && index < list.size()) { + Choice choice = list.get(index); + matched.add(new ModelResult() {{ + setStart(match.getStart()); + setEnd(match.getEnd()); + setTypeName("choice"); + setText(match.getText()); + setResolution(new FoundChoice() {{ + setValue(choice.getValue()); + setIndex(index); + setScore(1.0f); + }}); + }}); + } + } catch (Throwable ignored) { + // noop here, as in dotnet/node repos + } + } + + private static List> recognizeNumbers(String utterance, IModel model) { + List result = model.parse(utterance == null ? "" : utterance); + return result.stream().map(r -> + new ModelResult() {{ + setStart(r.start); + setEnd(r.end - 1); // bug in 1.0-SNAPSHOT, should not have to decrement + setText(r.text); + setResolution(new FoundChoice() {{ + setValue(r.resolution.get("value").toString()); + }}); + }}).collect(Collectors.toList()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java new file mode 100644 index 000000000..c638003f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import org.apache.commons.lang3.StringUtils; + +/** + * Contains methods for matching user input against a list of choices. + */ +public final class Find { + private Find() { } + + /** + * Matches user input against a list of strings. + * @param utterance The input. + * @param choices The list of choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoicesFromStrings( + String utterance, + List choices + ) { + return findChoicesFromStrings(utterance, choices, null); + } + + /** + * Matches user input against a list of strings. + * @param utterance The input. + * @param choices The list of choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoicesFromStrings( + String utterance, + List choices, + FindChoicesOptions options + ) { + if (choices == null) { + throw new IllegalArgumentException("choices argument is missing"); + } + + return findChoices(utterance, choices.stream().map(Choice::new) + .collect(Collectors.toList()), options); + } + + /** + * Matches user input against a list of Choices. + * @param utterance The input. + * @param choices The list of choices. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoices(String utterance, List choices) { + return findChoices(utterance, choices, null); + } + + /** + * Matches user input against a list of Choices. + * @param utterance The input. + * @param choices The list of choices. + * @param options Optional, options to control the recognition strategy. + * @return A list of found choices, sorted by most relevant first. + */ + public static List> findChoices( + String utterance, + List choices, + FindChoicesOptions options + ) { + if (choices == null) { + throw new IllegalArgumentException("choices argument is missing"); + } + + FindChoicesOptions opt = options != null ? options : new FindChoicesOptions(); + + // Build up full list of synonyms to search over. + // - Each entry in the list contains the index of the choice it belongs to which will later be + // used to map the search results back to their choice. + List synonyms = new ArrayList<>(); + + for (int index = 0; index < choices.size(); index++) { + Choice choice = choices.get(index); + + if (!opt.isNoValue()) { + synonyms.add(new SortedValue(choice.getValue(), index)); + } + + if (choice.getAction() != null && choice.getAction().getTitle() != null && !opt.isNoAction()) { + synonyms.add(new SortedValue(choice.getAction().getTitle(), index)); + } + + if (choice.getSynonyms() != null) { + for (String synonym : choice.getSynonyms()) { + synonyms.add(new SortedValue(synonym, index)); + } + } + } + + // Find synonyms in utterance and map back to their choices + return findValues(utterance, synonyms, options).stream().map(v -> { + Choice choice = choices.get(v.getResolution().getIndex()); + + return new ModelResult() {{ + setStart(v.getStart()); + setEnd(v.getEnd()); + setTypeName("choice"); + setText(v.getText()); + setResolution(new FoundChoice() {{ + setValue(choice.getValue()); + setIndex(v.getResolution().getIndex()); + setScore(v.getResolution().getScore()); + setSynonym(v.getResolution().getValue()); + }}); + }}; + }).collect(Collectors.toList()); + } + + /** + * This method is internal and should not be used. + * @param utterance The input. + * @param values The values. + * @return A list of found values. + */ + static List> findValues(String utterance, List values) { + return findValues(utterance, values, null); + } + + /** + * This method is internal and should not be used. + * @param utterance The input. + * @param values The values. + * @param options The options for the search. + * @return A list of found values. + */ + static List> findValues( + String utterance, + List values, + FindValuesOptions options + ) { + // Sort values in descending order by length so that the longest value is searched over first. + List list = new ArrayList<>(values); + list.sort((a, b) -> b.getValue().length() - a.getValue().length()); + + // Search for each value within the utterance. + List> matches = new ArrayList<>(); + FindValuesOptions opt = options != null ? options : new FindValuesOptions(); + TokenizerFunction tokenizer = opt.getTokenizer() != null ? opt.getTokenizer() : new Tokenizer(); + List tokens = tokenizer.tokenize(utterance, opt.getLocale()); + int maxDistance = opt.getMaxTokenDistance(); + + for (SortedValue entry : list) { + // Find all matches for a value + // - To match "last one" in "the last time I chose the last one" we need + // to re-search the String starting from the end of the previous match. + // - The start & end position returned for the match are token positions. + int startPos = 0; + List searchedTokens = tokenizer.tokenize(entry.getValue().trim(), opt.getLocale()); + while (startPos < tokens.size()) { + ModelResult match = matchValue( + tokens, + maxDistance, + opt, + entry.getIndex(), + entry.getValue(), + searchedTokens, + startPos + ); + if (match != null) { + startPos = match.getEnd() + 1; + matches.add(match); + } else { + break; + } + } + } + + // Sort matches by score descending + matches.sort((a, b) -> Float.compare(b.getResolution().getScore(), a.getResolution().getScore())); + + // Filter out duplicate matching indexes and overlapping characters. + // - The start & end positions are token positions and need to be translated to + // character positions before returning. We also need to populate the "text" + // field as well. + List> results = new ArrayList<>(); + Set foundIndexes = new HashSet<>(); + Set usedTokens = new HashSet<>(); + + for (ModelResult match : matches) { + // Apply filters + boolean add = !foundIndexes.contains(match.getResolution().getIndex()); + for (int i = match.getStart(); i <= match.getEnd(); i++) { + if (usedTokens.contains(i)) { + add = false; + break; + } + } + + // Add to results + if (add) { + // Update filter info + foundIndexes.add(match.getResolution().getIndex()); + + for (int i = match.getStart(); i <= match.getEnd(); i++) { + usedTokens.add(i); + } + + // Translate start & end and populate text field + match.setStart(tokens.get(match.getStart()).getStart()); + match.setEnd(tokens.get(match.getEnd()).getEnd()); + match.setText(utterance.substring(match.getStart(), match.getEnd() + 1)); + results.add(match); + } + } + + // Return the results sorted by position in the utterance + results.sort((a, b) -> a.getStart() - b.getStart()); + return results; + } + + private static int indexOfToken(List tokens, Token token, int startPos) { + for (int i = startPos; i < tokens.size(); i++) { + if (StringUtils.equalsIgnoreCase(tokens.get(i).getNormalized(), token.getNormalized())) { + return i; + } + } + + return -1; + } + + private static ModelResult matchValue( + List sourceTokens, + int maxDistance, + FindValuesOptions options, + int index, + String value, + List searchedTokens, + int startPos + ) { + // Match value to utterance and calculate total deviation. + // - The tokens are matched in order so "second last" will match in + // "the second from last one" but not in "the last from the second one". + // - The total deviation is a count of the number of tokens skipped in the + // match so for the example above the number of tokens matched would be + // 2 and the total deviation would be 1. + int matched = 0; + int totalDeviation = 0; + int start = -1; + int end = -1; + for (Token token : searchedTokens) { + // Find the position of the token in the utterance. + int pos = indexOfToken(sourceTokens, token, startPos); + if (pos >= 0) { + // Calculate the distance between the current tokens position and the + // previous tokens distance. + int distance = matched > 0 ? pos - startPos : 0; + if (distance <= maxDistance) { + // Update count of tokens matched and move start pointer to search + // for next token after the current token. + matched++; + totalDeviation += distance; + startPos = pos + 1; + + // Update start & end position that will track the span of the utterance + // that's matched. + if (start < 0) { + start = pos; + } + + end = pos; + } + } + } + + // Calculate score and format result + // - The start & end positions and the results text field will be corrected by the caller. + ModelResult result = null; + + if (matched > 0 && (matched == searchedTokens.size() || options.getAllowPartialMatches())) { + // Percentage of tokens matched. If matching "second last" in + // "the second from last one" the completeness would be 1.0 since + // all tokens were found. + int completeness = matched / searchedTokens.size(); + + // Accuracy of the match. The accuracy is reduced by additional tokens + // occurring in the value that weren't in the utterance. So an utterance + // of "second last" matched against a value of "second from last" would + // result in an accuracy of 0.5. + float accuracy = (float) matched / (matched + totalDeviation); + + // The final score is simply the completeness multiplied by the accuracy. + float score = completeness * accuracy; + + // Format result + result = new ModelResult<>(); + result.setStart(start); + result.setEnd(end); + result.setTypeName("value"); + result.setResolution(new FoundValue() {{ + setValue(value); + setIndex(index); + setScore(score); + }}); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java new file mode 100644 index 000000000..ff62e7296 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindChoicesOptions.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Contains options to control how input is matched against a list of choices. + */ +public class FindChoicesOptions extends FindValuesOptions { + @JsonProperty(value = "noValue") + private boolean noValue; + + @JsonProperty(value = "noAction") + private boolean noAction; + + @JsonProperty(value = "recognizeNumbers") + private boolean recognizeNumbers = true; + + @JsonProperty(value = "recognizeOrdinals") + private boolean recognizeOrdinals; + + /** + * Indicates whether the choices value will NOT be search over. The default is false. + * @return true if the choices value will NOT be search over. + */ + public boolean isNoValue() { + return noValue; + } + + /** + * Sets whether the choices value will NOT be search over. + * @param withNoValue true if the choices value will NOT be search over. + */ + public void setNoValue(boolean withNoValue) { + noValue = withNoValue; + } + + /** + * Indicates whether the title of the choices action will NOT be searched over. The default + * is false. + * @return true if the title of the choices action will NOT be searched over. + */ + public boolean isNoAction() { + return noAction; + } + + /** + * Sets whether the title of the choices action will NOT be searched over. + * @param withNoAction true if the title of the choices action will NOT be searched over. + */ + public void setNoAction(boolean withNoAction) { + noAction = withNoAction; + } + + /** + * Indicates whether the recognizer should check for Numbers using the NumberRecognizer's + * NumberModel. + * @return Default is true. If false, the Number Model will not be used to check the + * utterance for numbers. + */ + public boolean isRecognizeNumbers() { + return recognizeNumbers; + } + + /** + * Set whether the recognizer should check for Numbers using the NumberRecognizer's + * NumberModel. + * @param withRecognizeNumbers Default is true. If false, the Number Model will not be + * used to check the utterance for numbers. + */ + public void setRecognizeNumbers(boolean withRecognizeNumbers) { + recognizeNumbers = withRecognizeNumbers; + } + + /** + * Indicates whether the recognizer should check for Ordinal Numbers using the NumberRecognizer's + * OrdinalModel. + * @return Default is true. If false, the Ordinal Model will not be used to check the + * utterance for ordinal numbers. + */ + public boolean isRecognizeOrdinals() { + return recognizeOrdinals; + } + + /** + * Sets whether the recognizer should check for Ordinal Numbers using the NumberRecognizer's + * OrdinalModel. + * @param withRecognizeOrdinals If false, the Ordinal Model will not be used to check the + * utterance for ordinal numbers. + */ + public void setRecognizeOrdinals(boolean withRecognizeOrdinals) { + recognizeOrdinals = withRecognizeOrdinals; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java new file mode 100644 index 000000000..d45e88816 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FindValuesOptions.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Locale; + +/** + * Contains options used to control how choices are recognized in a users utterance. + */ +public class FindValuesOptions { + @JsonProperty(value = "allowPartialMatches") + private boolean allowPartialMatches; + + @JsonProperty(value = "locale") + private String locale = Locale.ENGLISH.getDisplayName(); + + @JsonProperty(value = "maxTokenDistance") + private int maxTokenDistance = 2; + + @JsonProperty(value = "tokenizer") + private TokenizerFunction tokenizer; + + /** + * Gets value indicating whether only some of the tokens in a value need to exist to be + * considered. + * @return true if only some of the tokens in a value need to exist to be considered; + * otherwise false. + */ + public boolean getAllowPartialMatches() { + return allowPartialMatches; + } + + /** + * Sets value indicating whether only some of the tokens in a value need to exist to be + * considered. + * @param withAllowPartialMatches true if only some of the tokens in a value need to exist + * to be considered; otherwise false. + */ + public void setAllowPartialMatches(boolean withAllowPartialMatches) { + allowPartialMatches = withAllowPartialMatches; + } + + /** + * Gets the locale/culture code of the utterance. The default is `en-US`. This is optional. + * @return The locale/culture code of the utterance. + */ + public String getLocale() { + return locale; + } + + /** + * Sets the locale/culture code of the utterance. The default is `en-US`. This is optional. + * @param withLocale The locale/culture code of the utterance. + */ + public void setLocale(String withLocale) { + locale = withLocale; + } + + /** + * Gets the maximum tokens allowed between two matched tokens in the utterance. So with + * a max distance of 2 the value "second last" would match the utterance "second from the last" + * but it wouldn't match "Wait a second. That's not the last one is it?". + * The default value is "2". + * @return The maximum tokens allowed between two matched tokens in the utterance. + */ + public int getMaxTokenDistance() { + return maxTokenDistance; + } + + /** + * Gets the maximum tokens allowed between two matched tokens in the utterance. So with + * a max distance of 2 the value "second last" would match the utterance "second from the last" + * but it wouldn't match "Wait a second. That's not the last one is it?". + * The default value is "2". + * @param withMaxTokenDistance The maximum tokens allowed between two matched tokens in the + * utterance. + */ + public void setMaxTokenDistance(int withMaxTokenDistance) { + maxTokenDistance = withMaxTokenDistance; + } + + /** + * Gets the tokenizer to use when parsing the utterance and values being recognized. + * @return The tokenizer to use when parsing the utterance and values being recognized. + */ + public TokenizerFunction getTokenizer() { + return tokenizer; + } + + /** + * Sets the tokenizer to use when parsing the utterance and values being recognized. + * @param withTokenizer The tokenizer to use when parsing the utterance and values being + * recognized. + */ + public void setTokenizer(TokenizerFunction withTokenizer) { + tokenizer = withTokenizer; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java new file mode 100644 index 000000000..f1e8351f0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundChoice.java @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Represents a result from matching user input against a list of choices. + */ +public class FoundChoice { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + @JsonProperty(value = "score") + private float score; + + @JsonProperty(value = "synonym") + private String synonym; + + /** + * Gets the value that was matched. + * @return The value that was matched. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that was matched. + * @param withValue The value that was matched. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the index of the value that was matched. + * @return The index of the value that was matched. + */ + public int getIndex() { + return index; + } + + /** + * Sets the index of the value that was matched. + * @param withIndex The index of the value that was matched. + */ + public void setIndex(int withIndex) { + index = withIndex; + } + + /** + * Gets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @return The accuracy with which the value matched the specified portion of the utterance. + * A value of 1.0 would indicate a perfect match. + */ + public float getScore() { + return score; + } + + /** + * Sets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @param withScore The accuracy with which the value matched the specified portion of the + * utterance. A value of 1.0 would indicate a perfect match. + */ + public void setScore(float withScore) { + score = withScore; + } + + /** + * Gets the synonym that was matched. This is optional. + * @return The synonym that was matched. + */ + public String getSynonym() { + return synonym; + } + + /** + * Sets the synonym that was matched. This is optional. + * @param withSynonym The synonym that was matched. + */ + public void setSynonym(String withSynonym) { + synonym = withSynonym; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java new file mode 100644 index 000000000..9bd3430bf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/FoundValue.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * This class is internal and should not be used. + * Please use FoundChoice instead. + */ +class FoundValue { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + @JsonProperty(value = "score") + private float score; + + /** + * Gets the value that was matched. + * @return The value that was matched. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that was matched. + * @param withValue The value that was matched. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the index of the value that was matched. + * @return The index of the value that was matched. + */ + public int getIndex() { + return index; + } + + /** + * Sets the index of the value that was matched. + * @param withIndex The index of the value that was matched. + */ + public void setIndex(int withIndex) { + index = withIndex; + } + + /** + * Gets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @return The accuracy with which the value matched the specified portion of the utterance. + * A value of 1.0 would indicate a perfect match. + */ + public float getScore() { + return score; + } + + /** + * Sets the accuracy with which the value matched the specified portion of the utterance. A + * value of 1.0 would indicate a perfect match. + * @param withScore The accuracy with which the value matched the specified portion of the + * utterance. A value of 1.0 would indicate a perfect match. + */ + public void setScore(float withScore) { + score = withScore; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java new file mode 100644 index 000000000..fbc5316c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ListStyle.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Controls the way that choices for a `ChoicePrompt` or yes/no options for a `ConfirmPrompt` are + * presented to a user. + */ +public enum ListStyle { + /// Don't include any choices for prompt. + NONE, + + /// Automatically select the appropriate style for the current channel. + AUTO, + + /// Add choices to prompt as an inline list. + INLINE, + + /// Add choices to prompt as a numbered list. + LIST, + + /// Add choices to prompt as suggested actions. + SUGGESTED_ACTION, + + /// Add choices to prompt as a HeroCard with buttons. + HEROCARD +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java new file mode 100644 index 000000000..ff04b5c28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ModelResult.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Contains recognition result information. + * + * @param The type of object recognized. + */ +public class ModelResult { + private String text; + private int start; + private int end; + private String typeName; + private T resolution; + + /** + * Gets the substring of the input that was recognized. + * + * @return The substring of the input that was recognized. + */ + public String getText() { + return text; + } + + /** + * Sets the substring of the input that was recognized. + * + * @param withText The substring of the input that was recognized. + */ + public void setText(String withText) { + text = withText; + } + + /** + * Gets the start character position of the recognized substring. + * @return The start character position of the recognized substring. + */ + public int getStart() { + return start; + } + + /** + * Sets the start character position of the recognized substring. + * @param withStart The start character position of the recognized substring. + */ + public void setStart(int withStart) { + start = withStart; + } + + /** + * Gets the end character position of the recognized substring. + * @return The end character position of the recognized substring. + */ + public int getEnd() { + return end; + } + + /** + * Starts the end character position of the recognized substring. + * @param withEnd The end character position of the recognized substring. + */ + public void setEnd(int withEnd) { + end = withEnd; + } + + /** + * Gets the type of entity that was recognized. + * @return The type of entity that was recognized. + */ + public String getTypeName() { + return typeName; + } + + /** + * Sets the type of entity that was recognized. + * @param withTypeName The type of entity that was recognized. + */ + public void setTypeName(String withTypeName) { + typeName = withTypeName; + } + + /** + * Gets the recognized object. + * @return The recognized object. + */ + public T getResolution() { + return resolution; + } + + /** + * Sets the recognized object. + * @param withResolution The recognized object. + */ + public void setResolution(T withResolution) { + resolution = withResolution; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java new file mode 100644 index 000000000..7915ddb05 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/SortedValue.java @@ -0,0 +1,59 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A value that can be sorted and still refer to its original position with a source array. + */ +public class SortedValue { + @JsonProperty(value = "value") + private String value; + + @JsonProperty(value = "index") + private int index; + + /** + * Creates a sort value. + * @param withValue The value that will be sorted. + * @param withIndex The values original position within its unsorted array. + */ + public SortedValue(String withValue, int withIndex) { + value = withValue; + index = withIndex; + } + + /** + * Gets the value that will be sorted. + * @return The value that will be sorted. + */ + public String getValue() { + return value; + } + + /** + * Sets the value that will be sorted. + * @param withValue The value that will be sorted. + */ + public void setValue(String withValue) { + value = withValue; + } + + /** + * Gets the values original position within its unsorted array. + * @return The values original position within its unsorted array. + */ + public int getIndex() { + return index; + } + + /** + * Sets the values original position within its unsorted array. + * @param withIndex The values original position within its unsorted array. + */ + public void setIndex(int withIndex) { + index = withIndex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java new file mode 100644 index 000000000..02431564f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Token.java @@ -0,0 +1,92 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +/** + * Represents an individual token, such as a word in an input string. + */ +public class Token { + private String text; + private int start; + private int end; + private String normalized; + + /** + * Gets the original text of the token. + * + * @return The original text of the token. + */ + public String getText() { + return text; + } + + /** + * Sets the original text of the token. + * + * @param withText The original text of the token. + */ + public void setText(String withText) { + text = withText; + } + + /** + * Appends a string to the text value. + * @param withText The text to append. + */ + public void appendText(String withText) { + if (text != null) { + text += withText; + } else { + text = withText; + } + } + + /** + * Gets the index of the first character of the token within the input. + * @return The index of the first character of the token. + */ + public int getStart() { + return start; + } + + /** + * Sets the index of the first character of the token within the input. + * @param withStart The index of the first character of the token. + */ + public void setStart(int withStart) { + start = withStart; + } + + /** + * Gets the index of the last character of the token within the input. + * @return The index of the last character of the token. + */ + public int getEnd() { + return end; + } + + /** + * Starts the index of the last character of the token within the input. + * @param withEnd The index of the last character of the token. + */ + public void setEnd(int withEnd) { + end = withEnd; + } + + /** + * Gets the normalized version of the token. + * @return A normalized version of the token. + */ + public String getNormalized() { + return normalized; + } + + /** + * Sets the normalized version of the token. + * @param withNormalized A normalized version of the token. + */ + public void setNormalized(String withNormalized) { + normalized = withNormalized; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java new file mode 100644 index 000000000..e7f6c4258 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Tokenizer.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.ArrayList; +import java.util.List; + +/** + * Provides the default Tokenizer implementation. + */ +public class Tokenizer implements TokenizerFunction { + + /** + * Simple tokenizer that breaks on spaces and punctuation. The only normalization + * done is to lowercase. + * @param text The input text. + * @param locale Optional, identifies the locale of the input text. + * @return The list of the found Token objects. + */ + @SuppressWarnings("checkstyle:MagicNumber") + @Override + public List tokenize(String text, String locale) { + List tokens = new ArrayList<>(); + Token token = null; + + int length = text == null ? 0 : text.length(); + int i = 0; + + while (i < length) { + int codePoint = text.codePointAt(i); + + String chr = new String(Character.toChars(codePoint)); + + if (isBreakingChar(codePoint)) { + appendToken(tokens, token, i - 1); + token = null; + } else if (codePoint > 0xFFFF) { + appendToken(tokens, token, i - 1); + token = null; + + Token t = new Token(); + t.setStart(i); + t.setEnd(i + chr.length() - 1); + t.setText(chr); + t.setNormalized(chr); + + tokens.add(t); + } else if (token == null) { + token = new Token(); + token.setStart(i); + token.setText(chr); + } else { + token.appendText(chr); + } + + i += chr.length(); + } + + appendToken(tokens, token, length - 1); + return tokens; + } + + private void appendToken(List tokens, Token token, int end) { + if (token != null) { + token.setEnd(end); + token.setNormalized(token.getText().toLowerCase()); + tokens.add(token); + } + } + + @SuppressWarnings("checkstyle:MagicNumber") + private static boolean isBreakingChar(int codePoint) { + return isBetween(codePoint, 0x0000, 0x002F) + || isBetween(codePoint, 0x003A, 0x0040) + || isBetween(codePoint, 0x005B, 0x0060) + || isBetween(codePoint, 0x007B, 0x00BF) + || isBetween(codePoint, 0x02B9, 0x036F) + || isBetween(codePoint, 0x2000, 0x2BFF) + || isBetween(codePoint, 0x2E00, 0x2E7F); + } + + private static boolean isBetween(int value, int from, int to) { + return value >= from && value <= to; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java new file mode 100644 index 000000000..3fb3c459d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/TokenizerFunction.java @@ -0,0 +1,21 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.List; + +/** + * Represents a callback method that can break a string into its component tokens. + */ +@FunctionalInterface +public interface TokenizerFunction { + + /** + * The callback method that can break a string into its component tokens. + * @param text The input text. + * @param locale Optional, identifies the locale of the input text. + * @return The list of the found Token objects. + */ + List tokenize(String text, String locale); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java new file mode 100644 index 000000000..7420d4796 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.choices; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java new file mode 100644 index 000000000..f7e1fe0b8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentMemoryScopes.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +/** + * Defines Component Memory Scopes interface for enumerating memory scopes. + */ +public interface ComponentMemoryScopes { + + /** + * Gets the memory scopes. + * + * @return A reference with the memory scopes. + */ + Iterable getMemoryScopes(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java new file mode 100644 index 000000000..057a90629 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/ComponentPathResolvers.java @@ -0,0 +1,15 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; +/** + * Interface for declaring path resolvers in the memory system. + */ +public interface ComponentPathResolvers { + /** + * Return enumeration of pathresolvers. + * + * @return collection of PathResolvers. + */ + Iterable getPathResolvers(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java new file mode 100644 index 000000000..aab062cee --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java @@ -0,0 +1,834 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.AbstractMap.SimpleEntry; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.ComponentRegistration; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogPath; +import com.microsoft.bot.dialogs.DialogsComponentRegistration; +import com.microsoft.bot.dialogs.ObjectPath; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.schema.ResultPair; + +import org.apache.commons.lang3.StringUtils; + +/** + * The DialogStateManager manages memory scopes and pathresolvers MemoryScopes + * are named root level Objects, which can exist either in the dialogcontext or + * off of turn state PathResolvers allow for shortcut behavior for mapping + * things like $foo -> dialog.foo. + */ +public class DialogStateManager implements Map { + + /** + * Information for tracking when path was last modified. + */ + private final String pathTracker = "dialog._tracker.paths"; + + private static final char[] SEPARATORS = {',', '['}; + + private final DialogContext dialogContext; + private int version; + + /** + * Initializes a new instance of the + * {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class. + * + * @param dc The dialog context for the current turn of the + * conversation. + * @param configuration Configuration for the dialog state manager. + */ + public DialogStateManager(DialogContext dc, DialogStateManagerConfiguration configuration) { + ComponentRegistration.add(new DialogsComponentRegistration()); + + if (dc != null) { + dialogContext = dc; + } else { + throw new IllegalArgumentException("dc cannot be null."); + } + + if (configuration != null) { + this.configuration = configuration; + } else { + this.configuration = dc.getContext().getTurnState().get(DialogStateManagerConfiguration.class.getName()); + } + + if (this.configuration == null) { + this.configuration = new DialogStateManagerConfiguration(); + + Iterable components = ComponentRegistration.getComponents(); + + components.forEach((component) -> { + if (component instanceof ComponentMemoryScopes) { + ((ComponentMemoryScopes) component).getMemoryScopes().forEach((scope) -> { + this.configuration.getMemoryScopes().add(scope); + }); + } + if (component instanceof ComponentPathResolvers) { + ((ComponentPathResolvers) component).getPathResolvers().forEach((pathResolver) -> { + this.configuration.getPathResolvers().add(pathResolver); + }); + } + }); + } + // cache for any other new dialogStatemanager instances in this turn. + dc.getContext().getTurnState().replace(this.configuration); + } + + private DialogStateManagerConfiguration configuration; + + /** + * Sets the configured path resolvers and memory scopes for the dialog. + * + * @return The DialogStateManagerConfiguration. + */ + public DialogStateManagerConfiguration getConfiguration() { + return configuration; + } + + /** + * Sets the configured path resolvers and memory scopes for the dialog. + * + * @param withDialogStateManagerConfiguration The configuration to set. + */ + public void setConfiguration(DialogStateManagerConfiguration withDialogStateManagerConfiguration) { + this.configuration = withDialogStateManagerConfiguration; + } + + /** + * Gets a value indicating whether the dialog state manager is read-only. + * + * @return true + */ + public Boolean getIsReadOnly() { + return true; + } + + /** + * Gets the elements with the specified key. + * + * @param key Key to get or set the element. + * @return The element with the specified key. + */ + public Object getElement(String key) { + // return GetValue(key); + return null; + } + + /** + * Sets the elements with the specified key. + * + * @param key Key to get or set the element. + * @param element The element to store with the provided key. + */ + public void setElement(String key, Object element) { + if (key.indexOf(SEPARATORS[0]) == -1 && key.indexOf(SEPARATORS[1]) == -1) { + MemoryScope scope = getMemoryScope(key); + if (scope != null) { + ObjectMapper mapper = new ObjectMapper(); + try { + scope.setMemory(dialogContext, mapper.writeValueAsString(element)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } else { + throw new IllegalArgumentException(); + } + } + } + + /** + * Get MemoryScope by name. + * + * @param name Name of scope. + * @return A memory scope. + */ + public MemoryScope getMemoryScope(String name) { + if (name == null) { + throw new IllegalArgumentException("name cannot be null."); + } + return configuration.getMemoryScopes().stream().filter((scope) -> scope.getName().equalsIgnoreCase(name)) + .findFirst().get(); + } + + /** + * Version help caller to identify the updates and decide cache or not. + * + * @return Current version + */ + public String version() { + return Integer.toString(version); + } + + /** + * ResolveMemoryScope will find the MemoryScope for and return the remaining + * path. + * + * @param path Incoming path to resolve to scope and remaining path. + * @param remainingPath Remaining subpath in scope. + * @return The memory scope. + */ + public MemoryScope resolveMemoryScope(String path, StringBuilder remainingPath) { + String scope = path; + int sepIndex = -1; + int dot = StringUtils.indexOfIgnoreCase(path, "."); + int openSquareBracket = StringUtils.indexOfIgnoreCase(path, "["); + + if (dot > 0 && openSquareBracket > 0) { + sepIndex = Math.min(dot, openSquareBracket); + } else if (dot > 0) { + sepIndex = dot; + } else if (openSquareBracket > 0) { + sepIndex = openSquareBracket; + } + + if (sepIndex > 0) { + scope = path.substring(0, sepIndex); + MemoryScope memoryScope = getMemoryScope(scope); + if (memoryScope != null) { + remainingPath.append(path.substring(sepIndex + 1)); + return memoryScope; + } + } + + MemoryScope resultScope = getMemoryScope(scope); + if (resultScope == null) { + throw new IllegalArgumentException(getBadScopeMessage(path)); + } else { + return resultScope; + } + } + + /** + * Transform the path using the registered PathTransformers. + * + * @param path Path to transform. + * @return The transformed path. + */ + public String transformPath(String path) { + List resolvers = configuration.getPathResolvers(); + + for (PathResolver resolver : resolvers) { + path = resolver.transformPath(path); + } + + return path; + } + + /** + * Get the value from memory using path expression (NOTE: This always returns + * clone of value). + * + * @param the value type to return. + * @param path >path expression to use. + * @param clsType the Type that is being requested as a result + * @return ResultPair with boolean and requested type TypeT as a result + */ + public ResultPair tryGetValue(String path, Class clsType) { + TypeT instance = null; + + if (path == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + path = transformPath(path); + + MemoryScope memoryScope = null; + StringBuilder remainingPath = new StringBuilder(); + + try { + memoryScope = resolveMemoryScope(path, remainingPath); + } catch (Exception err) { + // Trace.TraceError(err.Message); + return new ResultPair<>(false, instance); + } + + if (memoryScope == null) { + return new ResultPair<>(false, instance); + } + + if (remainingPath.length() == 0) { + Object memory = memoryScope.getMemory(dialogContext); + if (memory == null) { + return new ResultPair<>(false, instance); + } + + instance = (TypeT) ObjectPath.mapValueTo(memory, clsType); + + return new ResultPair<>(true, instance); + } + + // HACK to support .First() retrieval on turn.recognized.entities.foo, + // replace with Expressions + // once expression ship + final String first = ".FIRST()"; + int iFirst = path.toUpperCase(Locale.US).lastIndexOf(first); + if (iFirst >= 0) { + remainingPath = new StringBuilder(path.substring(iFirst + first.length())); + path = path.substring(0, iFirst); + ResultPair getResult = tryGetFirstNestedValue(new AtomicReference(path), this); + if (getResult.result()) { + if (StringUtils.isEmpty(remainingPath.toString())) { + instance = (TypeT) ObjectPath.mapValueTo(getResult.getRight(), clsType); + return new ResultPair<>(true, instance); + } + instance = (TypeT) ObjectPath.tryGetPathValue(getResult.getRight(), remainingPath.toString(), clsType); + + return new ResultPair<>(true, instance); + } + + return new ResultPair<>(false, instance); + } + + instance = (TypeT) ObjectPath.tryGetPathValue(this, path, clsType); + + return new ResultPair<>(instance != null, instance); + } + + /** + * Get the value from memory using path expression (NOTE: This always returns + * clone of value). + * + * @param The value type to return. + * @param pathExpression Path expression to use. + * @param defaultValue Default value to return if there is none found. + * @param clsType Type of value that is being requested as a return. + * @return Result or the default value if the path is not valid. + */ + public T getValue(String pathExpression, T defaultValue, Class clsType) { + if (pathExpression == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, clsType); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a int value from memory using a path expression. + * + * @param pathExpression Path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return Value or default value if path is not valid. + */ + public int getIntValue(String pathExpression, int defaultValue) { + if (pathExpression == null) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, Integer.class); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a boolean value from memory using a path expression. + * + * @param pathExpression Path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return Value or default value if path is not valid. + */ + public Boolean getBoolValue(String pathExpression, Boolean defaultValue) { + if (pathExpression == null || StringUtils.isEmpty(pathExpression)) { + throw new IllegalArgumentException("path cannot be null"); + } + + ResultPair result = tryGetValue(pathExpression, Boolean.class); + if (result.result()) { + return result.value(); + } else { + return defaultValue; + } + } + + /** + * Get a String value from memory using a path expression. + * + * @param pathExpression The path expression. + * @param defaultValue Default value if the value doesn't exist. + * @return String or default value if path is not valid. + */ + public String getStringValue(String pathExpression, String defaultValue) { + return getValue(pathExpression, defaultValue, String.class); + } + + /** + * Set memory to value. + * + * @param path Path to memory. + * @param value Object to set. + */ + public void setValue(String path, Object value) { + if (value instanceof CompletableFuture) { + throw new IllegalArgumentException( + String.format("%s = You can't pass an unresolved CompletableFuture to SetValue", path)); + } + + if (path == null || StringUtils.isEmpty(path)) { + throw new IllegalArgumentException("path cannot be null"); + } + + if (value != null) { + ObjectMapper mapper = new ObjectMapper(); + value = mapper.valueToTree(value); + } + + path = transformPath(path); + if (trackChange(path, value)) { + ObjectPath.setPathValue(this, path, value); + } + + // Every set will increase version + version++; + } + + /** + * Remove property from memory. + * + * @param path Path to remove the leaf property. + */ + public void removeValue(String path) { + if (!StringUtils.isNotBlank(path)) { + throw new IllegalArgumentException("Path cannot be null"); + } + + path = transformPath(path); + if (trackChange(path, null)) { + ObjectPath.removePathValue(this, path); + } + } + + /** + * Gets all memoryscopes suitable for logging. + * + * @return JsonNode that which represents all memory scopes. + */ + public JsonNode getMemorySnapshot() { + ObjectMapper mapper = new ObjectMapper(); + ObjectNode result = mapper.createObjectNode(); + + List scopes = configuration.getMemoryScopes().stream().filter((x) -> x.getIncludeInSnapshot()) + .collect(Collectors.toList()); + for (MemoryScope scope : scopes) { + Object memory = scope.getMemory(dialogContext); + if (memory != null) { + try { + result.put(scope.getName(), mapper.writeValueAsString(memory)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + } + return result; + } + + /** + * Load all of the scopes. + * + * @return A Completed Future. + */ + public CompletableFuture loadAllScopesAsync() { + configuration.getMemoryScopes().forEach((scope) -> { + scope.load(dialogContext, false).join(); + }); + return CompletableFuture.completedFuture(null); + } + + /** + * Save all changes for all scopes. + * + * @return Completed Future + */ + public CompletableFuture saveAllChanges() { + configuration.getMemoryScopes().forEach((memoryScope) -> { + memoryScope.saveChanges(dialogContext, false).join(); + }); + return CompletableFuture.completedFuture(null); + } + + /** + * Delete the memory for a scope. + * + * @param name name of the scope + * @return Completed CompletableFuture + */ + public CompletableFuture deleteScopesMemory(String name) { + // Make a copy here that is final so it can be used in lamdba expression below + final String uCaseName = name.toUpperCase(); + MemoryScope scope = configuration.getMemoryScopes().stream().filter((s) -> { + return s.getName().toUpperCase() == uCaseName; + }).findFirst().get(); + if (scope != null) { + scope.delete(dialogContext).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Adds an element to the dialog state manager. + * + * @param key Key of the element to add. + * @param value Value of the element to add. + */ + public void add(String key, Object value) { + throw new UnsupportedOperationException(); + } + + /** + * Determines whether the dialog state manager contains an element with the + * specified key. + * + * @param key The key to locate in the dialog state manager. + * @return true if the dialog state manager contains an element with the key; + * otherwise, false. + */ + public Boolean containsKey(String key) { + for (MemoryScope scope : configuration.getMemoryScopes()) { + if (scope.getName().toUpperCase().equals(key.toUpperCase())) { + return true; + } + } + return false; + } + + /** + * Removes the element with the specified key from the dialog state manager. + * + * @param key The key of the element to remove. + * @return true if the element is succesfully removed; otherwise, false. + */ + public Boolean remove(String key) { + throw new UnsupportedOperationException(); + } + + /** + * Gets the value associated with the specified key. + * + * @param key The key whose value to get. + * @param value When this method returns, the value associated with the + * specified key, if the key is found; otherwise, the default value + * for the type of the value parameter. + * @return true if the dialog state manager contains an element with the + * specified key; + */ + public ResultPair tryGetValue(String key, Object value) { + return tryGetValue(key, Object.class); + } + + /** + * Adds an item to the dialog state manager. + * + * @param item The SimpleEntry with the key and Object of the item to add. + */ + public void add(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Removes all items from the dialog state manager. + */ + public void clear() { + throw new UnsupportedOperationException(); + } + + /** + * Determines whether the dialog state manager contains a specific value. + * + * @param item The of the item to locate. + * @return True if item is found in the dialog state manager; otherwise,false + */ + public Boolean contains(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Copies the elements of the dialog state manager to an array starting at a + * particular index. + * + * @param array The one-dimensional array that is the destination of the + * elements copiedfrom the dialog state manager. The array + * must have zero-based indexing. + * @param arrayIndex The zero-based index in array at which copying begins. + */ + public void copyTo(SimpleEntry[] array, int arrayIndex) { + for (MemoryScope scope : configuration.getMemoryScopes()) { + array[arrayIndex++] = new SimpleEntry(scope.getName(), scope.getMemory(dialogContext)); + } + } + + /// + /// Removes the first occurrence of a specific Object from the dialog state + /// manager. + /// + /// The Object to remove from the dialog state + /// manager. + /// true if the item was successfully removed from the dialog + /// state manager; + /// otherwise, false. + /// This method is not supported. + /** + * Removes the first occurrence of a specific Object from the dialog state + * manager. + * + * @param item The Object to remove from the dialog state manager. + * @return true if the item was successfully removed from the dialog state + * manager otherwise false + */ + public boolean remove(SimpleEntry item) { + throw new UnsupportedOperationException(); + } + + /** + * Returns an Iterator that iterates through the collection. + * + * @return An Iterator that can be used to iterate through the collection. + */ + public Iterable> getEnumerator() { + List> resultList = new ArrayList>(); + for (MemoryScope scope : configuration.getMemoryScopes()) { + resultList.add(new SimpleEntry(scope.getName(), scope.getMemory(dialogContext))); + } + return resultList; + } + + /** + * Track when specific paths are changed. + * + * @param paths Paths to track. + * @return Normalized paths to pass to AnyPathChanged/>. + */ + public List trackPaths(Iterable paths) { + List allPaths = new ArrayList(); + for (String path : allPaths) { + String tpath = transformPath(path); + // Track any path that resolves to a constant path + Object resolved = ObjectPath.tryResolvePath(this, tpath); + if (resolved != null) { + String npath = String.join("_", resolved.toString()); + setValue(pathTracker + "." + npath, 0); + allPaths.add(npath); + } + } + return allPaths; + } + + /** + * Check to see if any path has changed since watermark. + * + * @param counter Time counter to compare to. + * @param paths Paths from Trackpaths to check. + * @return True if any path has changed since counter. + */ + public Boolean anyPathChanged(int counter, Iterable paths) { + Boolean found = false; + if (paths != null) { + for (String path : paths) { + int resultValue = getValue(pathTracker + "." + path, -1, Integer.class); + if (resultValue != -1 && resultValue > counter) { + found = true; + break; + } + } + } + return found; + } + + @SuppressWarnings("PMD.UnusedFormalParameter") + private static ResultPair tryGetFirstNestedValue(AtomicReference remainingPath, Object memory) { + ArrayNode array = new ArrayNode(JsonNodeFactory.instance); + Object value; + array = ObjectPath.tryGetPathValue(memory, remainingPath.get(), ArrayNode.class); + + if (array != null && array.size() > 0) { + JsonNode firstNode = array.get(0); + if (firstNode instanceof ArrayNode) { + if (firstNode.size() > 0) { + JsonNode secondNode = firstNode.get(0); + value = ObjectPath.mapValueTo(secondNode, Object.class); + return new ResultPair(true, value); + } + return new ResultPair(false, null); + } + value = ObjectPath.mapValueTo(firstNode, Object.class); + return new ResultPair(true, value); + } + return new ResultPair(false, null); + } + + private String getBadScopeMessage(String path) { + StringBuilder errorMessage = new StringBuilder(path); + errorMessage.append(" does not match memory scopes:["); + List scopeNames = new ArrayList(); + List scopes = configuration.getMemoryScopes(); + scopes.forEach((sc) -> { + scopeNames.add(sc.getName()); + }); + errorMessage.append(String.join(",", scopeNames)); + errorMessage.append("]"); + return errorMessage.toString(); + } + + private Boolean trackChange(String path, Object value) { + Boolean hasPath = false; + ArrayList segments = ObjectPath.tryResolvePath(this, path, false); + if (segments != null) { + String root = segments.size() > 1 ? (String) segments.get(1) : new String(); + + // Skip _* as first scope, i.e. _adaptive, _tracker, ... + if (!root.startsWith("_")) { + List stringSegments = segments.stream().map(Object -> Objects.toString(Object, null)) + .collect(Collectors.toList()); + + // Convert to a simple path with _ between segments + String pathName = String.join("_", stringSegments); + String trackedPath = String.format("%s.%s", pathTracker, pathName); + Integer counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + /** + * + */ + ResultPair result = tryGetValue(trackedPath, Integer.class); + if (result.result()) { + if (counter == null) { + counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + } + setValue(trackedPath, counter); + } + if (value instanceof Map) { + final int count = counter; + ((Map) value).forEach((key, val) -> { + checkChildren(key, val, trackedPath, count); + }); + } else if (value instanceof ObjectNode) { + ObjectNode node = (ObjectNode) value; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + checkChildren(field, node.findValue(field), trackedPath, counter); + } + } + } + hasPath = true; + } + return hasPath; + } + + private void checkChildren(String property, Object instance, String path, Integer counter) { + // Add new child segment + String trackedPath = path + "_" + property.toLowerCase(); + ResultPair pathCheck = tryGetValue(trackedPath, Integer.class); + if (pathCheck.result()) { + if (counter == null) { + counter = getValue(DialogPath.EVENTCOUNTER, 0, Integer.class); + } + setValue(trackedPath, counter); + } + + if (instance instanceof Map) { + final int count = counter; + ((Map) instance).forEach((key, value) -> { + checkChildren(key, value, trackedPath, count); + }); + } else if (instance instanceof ObjectNode) { + ObjectNode node = (ObjectNode) instance; + Iterator fields = node.fieldNames(); + + while (fields.hasNext()) { + String field = fields.next(); + checkChildren(field, node.findValue(field), trackedPath, counter); + } + } + + } + + @Override + public final int size() { + return configuration.getMemoryScopes().size(); + } + + @Override + public final boolean isEmpty() { + return size() == 0; + } + + @Override + public final boolean containsKey(Object key) { + return false; + } + + @Override + public final boolean containsValue(Object value) { + return false; + } + + @Override + public final Object get(Object key) { + return tryGetValue(key.toString(), Object.class).value(); + } + + @Override + public final Object put(String key, Object value) { + return null; + } + + @Override + public final Object remove(Object key) { + return null; + } + + @Override + public final void putAll(Map m) { + } + + + @Override + public final Set keySet() { + return configuration.getMemoryScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet()); + } + + @Override + public final Collection values() { + return configuration.getMemoryScopes().stream().map(scope -> scope.getMemory(dialogContext)) + .collect(Collectors.toSet()); + } + + @Override + public final Set> entrySet() { + Set> resultSet = new HashSet>(); + configuration.getMemoryScopes().forEach((scope) -> { + resultSet.add(new AbstractMap.SimpleEntry(scope.getName(), scope.getMemory(dialogContext))); + }); + + return resultSet; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java new file mode 100644 index 000000000..0d39d3547 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManagerConfiguration.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; + +import java.util.ArrayList; +import java.util.List; + +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +/** + * Configures the path resolvers and memory scopes for the dialog state manager. + */ +public class DialogStateManagerConfiguration { + + private List pathResolvers = new ArrayList(); + + private List memoryScopes = new ArrayList(); + + + /** + * @return List Returns the list of PathResolvers. + */ + public List getPathResolvers() { + return this.pathResolvers; + } + + + /** + * @param withPathResolvers Set the list of PathResolvers. + */ + public void setPathResolvers(List withPathResolvers) { + this.pathResolvers = withPathResolvers; + } + + + /** + * @return List Returns the list of MemoryScopes. + */ + public List getMemoryScopes() { + return this.memoryScopes; + } + + + /** + * @param withMemoryScopes Set the list of MemoryScopes. + */ + public void setMemoryScopes(List withMemoryScopes) { + this.memoryScopes = withMemoryScopes; + } + + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java new file mode 100644 index 000000000..8a7c6c7e8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolver.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory; +/** + * Defines Path Resolver interface for transforming paths. + */ +public interface PathResolver { + /** + * + * @param path path to inspect. + * @return transformed path. + */ + String transformPath(String path); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java new file mode 100644 index 000000000..7d3e4f99f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +import com.microsoft.bot.dialogs.memory.PathResolver; + +/** + * Maps aliasXXX -> path.xxx ($foo => dialog.foo). + */ +public class AliasPathResolver implements PathResolver { + + private final String postfix; + private final String prefix; + + /** + * + * @param alias Alias name. + * @param prefix Prefix name. + * @param postfix Postfix name. + */ + public AliasPathResolver(String alias, String prefix, String postfix) { + if (alias == null) { + throw new IllegalArgumentException("alias cannot be null"); + } + + if (prefix == null) { + throw new IllegalArgumentException("prefix cannot be null."); + } + + this.prefix = prefix.trim(); + + setAlias(alias.trim()); + + if (postfix == null) { + this.postfix = ""; + } else { + this.postfix = postfix; + } + } + + /** + * @return Gets the alias name. + */ + public String getAlias() { + return this.alias; + } + + /** + * @param alias Sets the alias name. + */ + private void setAlias(String alias) { + this.alias = alias; + } + + /** + * The alias name. + */ + private String alias; + + /** + * @param path Path to transform. + * @return The transformed path. + */ + public String transformPath(String path) { + if (path == null) { + throw new IllegalArgumentException("path cannot be null."); + } + + path = path.trim(); + if (path.startsWith(getAlias()) && path.length() > getAlias().length() + && isPathChar(path.charAt(getAlias().length()))) { + // here we only deals with trailing alias, alias in middle be handled in further + // breakdown + // $xxx -> path.xxx + return prefix + path.substring(getAlias().length()) + postfix; + } + + return path; + } + + /** + * + * @param ch Character to verify. + * @return true if the character is valid for a path; otherwise, false. + */ + protected Boolean isPathChar(char ch) { + return Character.isLetter(ch) || ch == '_'; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java new file mode 100644 index 000000000..df00ffa20 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps @@ => turn.recognized.entitites.xxx array. + */ +public class AtAtPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the AtAtPathResolver class. + */ + public AtAtPathResolver() { + super("@@", "turn.recognized.entities.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java new file mode 100644 index 000000000..170597619 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps @@ => turn.recognized.entitites.xxx array. + */ +public class AtPathResolver extends AliasPathResolver { + + private final String prefix = "turn.recognized.entities."; + + private static final char[] DELIMS = {'.', '[' }; + + /** + * Initializes a new instance of the AtPathResolver class. + */ + public AtPathResolver() { + super("@", "", null); + } + + /** + * Transforms the path. + * + * @param path Path to transform. + * @return The transformed path. + */ + @Override + public String transformPath(String path) { + if (path == null) { + throw new IllegalArgumentException("path cannot be null."); + } + + path = path.trim(); + if (path.startsWith("@") && path.length() > 1 && isPathChar(path.charAt(1))) { + int end = 0; + int endperiod = path.indexOf(DELIMS[0]); + int endbracket = path.indexOf(DELIMS[1]); + if (endperiod == -1 && endbracket == -1) { + end = path.length(); + } else { + end = Math.max(endperiod, endbracket); + } + + String property = path.substring(1, end); + String suffix = path.substring(end); + path = prefix + property + ".first()" + suffix; + } + + return path; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java new file mode 100644 index 000000000..ae455db19 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Resolve $xxx. + */ +public class DollarPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the DollarPathResolver class. + */ + public DollarPathResolver() { + super("$", "dialog.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java new file mode 100644 index 000000000..0f01b3a0c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps #xxx => turn.recognized.intents.xxx. + */ +public class HashPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the HashPathResolver class. + */ + public HashPathResolver() { + super("#", "turn.recognized.intents.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java new file mode 100644 index 000000000..6822bfff7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java @@ -0,0 +1,17 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.pathresolvers; + +/** + * Maps %xxx => settings.xxx (aka activeDialog.Instance.xxx). + */ +public class PercentPathResolver extends AliasPathResolver { + + /** + * Initializes a new instance of the PercentPathResolver class. + */ + public PercentPathResolver() { + super("%", "class.", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java new file mode 100644 index 000000000..649aa0346 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory.pathresolvers; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java new file mode 100644 index 000000000..c4737916c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java new file mode 100644 index 000000000..fbf2224cf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/BotStateMemoryScope.java @@ -0,0 +1,91 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.BotState.CachedBotState; +import com.microsoft.bot.dialogs.DialogContext; + +/** + * BotStateMemoryScope represents a BotState scoped memory. + * + * @param The BotState type. + */ +public class BotStateMemoryScope extends MemoryScope { + + private Class type; + + /** + * Initializes a new instance of the TurnMemoryScope class. + * + * @param type The Type of T that is being created. + * @param name Name of the property. + */ + public BotStateMemoryScope(Class type, String name) { + super(name, true); + this.type = type; + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + T botState = getBotState(dialogContext); + if (botState != null) { + CachedBotState cachedState = botState.getCachedState(dialogContext.getContext()); + return cachedState.getState(); + } else { + return null; + } + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You cannot replace the root BotState Object."); + } + + /** + * + */ + @Override + public CompletableFuture load(DialogContext dialogContext, Boolean force) { + T botState = getBotState(dialogContext); + + if (botState != null) { + return botState.load(dialogContext.getContext(), force); + } else { + return CompletableFuture.completedFuture(null); + } + } + + /** + * @param dialogContext + * @param force + * @return A future that represents the + */ + @Override + public CompletableFuture saveChanges(DialogContext dialogContext, Boolean force) { + T botState = getBotState(dialogContext); + + if (botState != null) { + return botState.saveChanges(dialogContext.getContext(), force); + } else { + return CompletableFuture.completedFuture(null); + } + } + + private T getBotState(DialogContext dialogContext) { + return dialogContext.getContext().getTurnState().get(type); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java new file mode 100644 index 000000000..9df3664d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ClassMemoryScope.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ClassMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public ClassMemoryScope() { + super(ScopePath.CLASS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + // if active dialog is a container dialog then "dialog" binds to it. + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog != null) { + return new ReadOnlyObject(dialog); + } + } + return null; +} + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the class scope."); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java new file mode 100644 index 000000000..4c076515b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ConversationMemoryScope.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ConversationMemoryScope extends BotStateMemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public ConversationMemoryScope() { + super(ConversationState.class, ScopePath.CONVERSATION); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java new file mode 100644 index 000000000..aee1efad4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogClassMemoryScope.java @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContainer; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * Initializes a new instance of the DialogClassMemoryScope class. + */ +public class DialogClassMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the DialogClassMemoryScope class. + */ + public DialogClassMemoryScope() { + super(ScopePath.DIALOG_CLASS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + // if active dialog is a container dialog then "dialogclass" binds to it. + if (dialogContext.getActiveDialog() != null) { + Object dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer) { + return new ReadOnlyObject(dialog); + } + } + + // Otherwise we always bind to parent, or if there is no parent the active + // dialog + if (dialogContext.getParent() != null && dialogContext.getParent().getActiveDialog() != null) { + return new ReadOnlyObject(dialogContext.findDialog(dialogContext.getParent().getActiveDialog().getId())); + } else if (dialogContext.getActiveDialog() != null) { + return new ReadOnlyObject(dialogContext.findDialog(dialogContext.getActiveDialog().getId())); + } else { + return null; + } + + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the dialogclass scope"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java new file mode 100644 index 000000000..ab2ddac4a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Optional; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.ScopePath; + +import org.json.JSONArray; +import org.json.JSONObject; + +/** + * DialogContextMemoryScope maps "dialogcontext" -> properties. + */ +public class DialogContextMemoryScope extends MemoryScope { + + private final String stackKey = "stack"; + + private final String activeDialogKey = "activeDialog"; + + private final String parentKey = "parent"; + + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public DialogContextMemoryScope() { + super(ScopePath.DIALOG_CONTEXT, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + JSONObject memory = new JSONObject(); + JSONArray stack = new JSONArray(); + DialogContext currentDc = dialogContext; + + // go to leaf node + while (currentDc.getChild() != null) { + currentDc = currentDc.getChild(); + } + + while (currentDc != null) { + // (PORTERS NOTE: javascript stack is reversed with top of stack on end) + currentDc.getStack().forEach(item -> { + if (item.getId().startsWith("ActionScope[")) { + stack.put(item.getId()); + } + + }); + + currentDc = currentDc.getParent(); + } + + // top of stack is stack[0]. + memory.put(stackKey, stack); + memory.put(activeDialogKey, Optional.ofNullable(dialogContext) + .map(DialogContext::getActiveDialog) + .map(DialogInstance::getId) + .orElse(null)); + memory.put(parentKey, Optional.ofNullable(dialogContext) + .map(DialogContext::getParent) + .map(DialogContext::getActiveDialog) + .map(DialogInstance::getId) + .orElse(null)); + return memory; + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the dialogcontext scope"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java new file mode 100644 index 000000000..24c1c0f28 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogMemoryScope.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Map; + +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContainer; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * DialogMemoryScope maps "dialog" -> dc.Parent?.ActiveDialog.State ?? ActiveDialog.State. + */ +public class DialogMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public DialogMemoryScope() { + super(ScopePath.DIALOG, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer) { + return dialogContext.getActiveDialog().getState(); + } + } + + if (dialogContext.getParent() != null) { + if (dialogContext.getParent().getActiveDialog() != null) { + return dialogContext.getParent().getActiveDialog().getState(); + } + } else if (dialogContext.getActiveDialog() != null) { + return dialogContext.getActiveDialog().getStackIndex(); + } + return null; + } + + /** + * Changes the backing object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (memory == null) { + throw new IllegalArgumentException("memory cannot be null."); + } + + if (!(memory instanceof Map)) { + throw new IllegalArgumentException("memory must be of type Map."); + } + + // if active dialog is a container dialog then "dialog" binds to it + if (dialogContext.getActiveDialog() != null) { + Dialog dialog = dialogContext.findDialog(dialogContext.getActiveDialog().getId()); + if (dialog instanceof DialogContainer && memory instanceof Map) { + dialogContext.getActiveDialog().getState().putAll((Map) memory); + return; + } + } else if (dialogContext.getParent().getActiveDialog() != null) { + dialogContext.getParent().getActiveDialog().getState().putAll((Map) memory); + return; + } + + throw new IllegalStateException( + "Cannot set DialogMemoryScope. There is no active dialog dialog or parent dialog in the context"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java new file mode 100644 index 000000000..eb3ede696 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/MemoryScope.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContext; +import java.util.concurrent.CompletableFuture; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public abstract class MemoryScope { + /** + * Initializes a new instance of the class. + * + * @param name Name of the scope. + * @param includeInSnapshot Value indicating whether this memory should be included in snapshot. + */ + public MemoryScope(String name, Boolean includeInSnapshot) { + this.includeInSnapshot = includeInSnapshot; + this.name = name; + } + + /** + * Name of the scope. + */ + private String name; + + /** + * Value indicating whether this memory should be included in snapshot. + */ + private Boolean includeInSnapshot; + + /** + * @return String Gets the name of the scope. + */ + public String getName() { + return this.name; + } + + /** + * @param withName Sets the name of the scope. + */ + public void setName(String withName) { + this.name = withName; + } + + /** + * @return Boolean Returns the value indicating whether this memory should be included in snapshot. + */ + public Boolean getIncludeInSnapshot() { + return this.includeInSnapshot; + } + + + /** + * @param withIncludeInSnapshot Sets the value indicating whether this memory should be included in snapshot. + */ + public void setIncludeInSnapshot(Boolean withIncludeInSnapshot) { + this.includeInSnapshot = withIncludeInSnapshot; + } + + /** + * Get the backing memory for this scope. + * + * @param dialogContext The DialogContext to get from the memory store. + * @return Object The memory for this scope. + */ + public abstract Object getMemory(DialogContext dialogContext); + + /** + * Changes the backing object for the memory scope. + * + * @param dialogContext The DialogContext to set in memory store. + * @param memory The memory to set the DialogContext to. + */ + public abstract void setMemory(DialogContext dialogContext, Object memory); + + /** + * Populates the state cache for this from the storage layer. + * + * @param dialogContext The dialog context object for this turn. + * @param force True to overwrite any existing state cache or false to load state from storage only + * if the cache doesn't already exist. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture load(DialogContext dialogContext, Boolean force) { + return CompletableFuture.completedFuture(null); + } + + + /** + * Writes the state cache for this to the storage layer. + * + * @param dialogContext The dialog context Object for this turn. + * @param force True to save the state cache to storage. or false to save state to storage only + * if a property in the cache has changed. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture saveChanges(DialogContext dialogContext, Boolean force) { + return CompletableFuture.completedFuture(null); + } + + /** + * Deletes any state in storage and the cache for this. + * + * @param dialogContext The dialog context Object for this turn. + * @return CompletableFuture A future that represents the work queued to execute. + */ + public CompletableFuture delete(DialogContext dialogContext) { + return CompletableFuture.completedFuture(null); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java new file mode 100644 index 000000000..429ff88b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ReadOnlyObject.java @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Dictionary; +import java.util.Enumeration; + +import com.microsoft.bot.dialogs.ObjectPath; + +/** + * ReadOnlyObject is a wrapper around any Object to prevent setting of + * properties on the Object. + */ +public class ReadOnlyObject extends Dictionary { + + private final String notSupported = "This Object is final."; + + private Object obj; + + /** + * + * @param obj Object to wrap. Any expression properties on it will be evaluated + * using the dc. + */ + public ReadOnlyObject(Object obj) { + this.obj = obj; + } + + /** + * @return The number of items. + */ + @Override + public int size() { + return ObjectPath.getProperties(obj).size(); + } + + /** + * @return The number of items. + */ + public int count() { + return size(); + } + + /** + * + */ + @Override + public boolean isEmpty() { + return false; + } + + /** + * + */ + @Override + public Enumeration keys() { + return Collections.enumeration(ObjectPath.getProperties(obj)); + } + + /** + * + */ + @Override + public Enumeration elements() { + Enumeration keys = this.keys(); + ArrayList elements = new ArrayList(); + while (keys.hasMoreElements()) { + String key = keys.nextElement(); + elements.add(getValue(key)); + } + return Collections.enumeration(elements); + } + + /** + * + * @return The values. + */ + public Enumeration values() { + return elements(); + } + + /** + * + */ + @Override + public Object get(Object key) { + + if (!(key instanceof String)) { + throw new IllegalArgumentException("key is required and must be a String type."); + } + + return ObjectPath.tryGetPathValue(obj, (String) key, Object.class); + } + + /** + * + */ + @Override + public Object put(String key, Object value) { + throw new UnsupportedOperationException(notSupported); + } + + /** + * + */ + @Override + public Object remove(Object key) { + throw new UnsupportedOperationException(notSupported); + } + + /** + * Get a value based on a key. + * + * @param name Key of the value. + * @return The value associated with the provided key. + */ + public Object getValue(String name) { + Object value = ObjectPath.tryGetPathValue(obj, name, Object.class); + if (value != null) { + return new ReadOnlyObject(value); + } else { + return null; + } + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java new file mode 100644 index 000000000..81f05b6c4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/SettingsMemoryScope.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.Properties; +import java.util.TreeMap; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; +import com.microsoft.bot.integration.Configuration; + +/** + * TurnMemoryScope represents memory scoped to the current turn. + */ +public class SettingsMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public SettingsMemoryScope() { + super(ScopePath.SETTINGS, false); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + Object returnValue; + + returnValue = dialogContext.getContext().getTurnState().get(ScopePath.TURN); + if (returnValue == null) { + Configuration configuration = dialogContext.getContext().getTurnState().get(Configuration.class); + if (configuration != null) { + returnValue = loadSettings(configuration); + dialogContext.getContext().getTurnState().add(ScopePath.SETTINGS, returnValue); + } + } + return returnValue; + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You cannot set the memory for a final memory scope"); + } + + /** + * Loads the settings from configuration. + * + * @param configuration The configuration to load Settings from. + * @return The collection of settings. + */ + protected static TreeMap loadSettings(Configuration configuration) { + TreeMap settings = new TreeMap(String.CASE_INSENSITIVE_ORDER); + + if (configuration != null) { + Properties properties = configuration.getProperties(); + properties.forEach((k, v) -> { + settings.put((String) k, v); + }); + } + + return settings; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java new file mode 100644 index 000000000..5eb375d4a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/ThisMemoryScope.java @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class ThisMemoryScope extends MemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public ThisMemoryScope() { + super(ScopePath.THIS, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getActiveDialog() != null) { + return dialogContext.getActiveDialog().getState(); + } else { + return null; + } + + } + + /** + * Changes the backing Object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + throw new UnsupportedOperationException("You can't modify the class scope."); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java new file mode 100644 index 000000000..2b96d9138 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import java.util.TreeMap; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * TurnMemoryScope represents memory scoped to the current turn. + */ +public class TurnMemoryScope extends MemoryScope { + /** + * Initializes a new instance of the TurnMemoryScope class. + */ + public TurnMemoryScope() { + super(ScopePath.TURN, true); + } + + /** + * Get the backing memory for this scope. + */ + @Override + public final Object getMemory(DialogContext dialogContext) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + Object returnValue; + + returnValue = dialogContext.getContext().getTurnState().get(ScopePath.TURN); + if (returnValue == null) { + returnValue = new TreeMap(String.CASE_INSENSITIVE_ORDER); + dialogContext.getContext().getTurnState().add(ScopePath.TURN, returnValue); + } + + return returnValue; + } + + /** + * Changes the backing object for the memory scope. + */ + @Override + public final void setMemory(DialogContext dialogContext, Object memory) { + if (dialogContext == null) { + throw new IllegalArgumentException("dialogContext cannot be null."); + } + + if (dialogContext.getContext().getTurnState().containsKey(ScopePath.TURN)) { + dialogContext.getContext().getTurnState().replace(ScopePath.TURN, memory); + } else { + dialogContext.getContext().getTurnState().add(ScopePath.TURN, memory); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java new file mode 100644 index 000000000..b5ae8806c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/UserMemoryScope.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.memory.scopes; + +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.ScopePath; + +/** + * MemoryScope represents a named memory scope abstract class. + */ +public class UserMemoryScope extends BotStateMemoryScope { + /** + * DialogMemoryScope maps "this" -> dc.ActiveDialog.State. + */ + public UserMemoryScope() { + super(UserState.class, ScopePath.USER); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java new file mode 100644 index 000000000..715f1bb43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.memory.scopes; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java new file mode 100644 index 000000000..33acff2b2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java new file mode 100644 index 000000000..6c30dae1a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java @@ -0,0 +1,290 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; + +/** + * Defines the core behavior of a prompt dialog that waits for an activity to be + * received. + * + * This prompt requires a validator be passed in and is useful when waiting for + * non-message activities like an event to be received.The validator can ignore + * received activities until the expected activity type is received. + */ +public class ActivityPrompt extends Dialog { + + private final String persistedOptions = "options"; + private final String persistedState = "state"; + + private final PromptValidator validator; + + /** + * Initializes a new instance of the {@link ActivityPrompt} class. Called from + * constructors in derived classes to initialize the {@link ActivityPrompt} + * class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator A {@link PromptValidator{Activity}} that contains validation + * for this prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public ActivityPrompt(String dialogId, PromptValidator validator) { + super(dialogId); + if (StringUtils.isEmpty(dialogId)) { + throw new IllegalArgumentException("dialogId cannot be empty"); + } + + if (validator == null) { + throw new IllegalArgumentException("validator cannot be null"); + } + + this.validator = validator; + } + + /** + * Called when a prompt dialog is pushed onto the dialog stack and is being activated. + * + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being started. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the prompt is still active after the + * turn has been processed by the prompt. + */ + + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (!(options instanceof PromptOptions)) { + return Async.completeExceptionally(new IllegalArgumentException( + "Prompt options are required for Prompt dialogs" + )); + } + + // Ensure prompts have input hint set + // For Java this code isn't necessary as InputHint is an enumeration, so it's can't be not set to something. + // PromptOptions opt = (PromptOptions) options; + // if (opt.getPrompt() != null && StringUtils.isBlank(opt.getPrompt().getInputHint().toString())) { + // opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // } + + // if (opt.getRetryPrompt() != null && StringUtils.isBlank(opt.getRetryPrompt().getInputHint().toString())) { + // opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // } + + // Initialize prompt state + Map state = dc.getActiveDialog().getState(); + state.put(persistedOptions, options); + + Map persistedStateMap = new HashMap(); + persistedStateMap.put(Prompt.ATTEMPTCOUNTKEY, 0); + state.put(persistedState, persistedStateMap); + + // Send initial prompt + onPrompt(dc.getContext(), (Map) state.get(persistedState), + (PromptOptions) state.get(persistedOptions), false); + + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog is the active dialog and the user replied with a + * new activity. + * + * @param dc The dialog context for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * prompt generally continues to receive the user's replies until it + * accepts the user's reply as valid input for the prompt. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Perform base recognition + DialogInstance instance = dc.getActiveDialog(); + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); + + state.put(Prompt.ATTEMPTCOUNTKEY, (int) state.get(Prompt.ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + boolean isValid = false; + if (validator != null) { + PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), + recognized, state, options); + isValid = validator.promptValidator(promptContext).join(); + } else if (recognized.getSucceeded()) { + isValid = true; + } + + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } + + onPrompt(dc.getContext(), state, options, true); + + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog resumes being the active dialog on the dialog + * stack, such as when the previous active dialog on the stack completes. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason An enum indicating why the dialog resumed. + * @param result Optional, value returned from the previous dialog on the stack. + * The type of the value returned is dependent on the previous + * dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + // Prompts are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the prompt receiving an unexpected + // call to + // dialogResume() when the pushed on dialog ends. + // To avoid the prompt prematurely ending we need to implement this method and + // simply re-prompt the user. + repromptDialog(dc.getContext(), dc.getActiveDialog()); + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog has been requested to re-prompt the user for + * input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param instance The instance of the dialog on the stack. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + onPrompt(turnContext, state, options, false); + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options) { + onPrompt(turnContext, state, options, false).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry A {@link Boolean} representing if the prompt is a retry. + * + * @return A {@link CompletableFuture} representing the result of the asynchronous + * operation. + */ + protected CompletableFuture onPrompt( + TurnContext turnContext, + Map state, + PromptOptions options, + Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * When overridden in a derived class, attempts to recognize the incoming activity. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + PromptRecognizerResult result = new PromptRecognizerResult(); + result.setSucceeded(true); + result.setValue(turnContext.getActivity()); + + return CompletableFuture.completedFuture(result); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java new file mode 100644 index 000000000..13fc79dbb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; + +/** + * Prompts a user to upload attachments, like images. + */ +public class AttachmentPrompt extends Prompt> { + + /** + * Initializes a new instance of the {@link AttachmentPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public AttachmentPrompt(String dialogId) { + this(dialogId, null); + } + + /** + * Initializes a new instance of the {@link AttachmentPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{T}} that contains additional, + * custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public AttachmentPrompt(String dialogId, PromptValidator> validator) { + super(dialogId, validator); + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture>> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult> result = new PromptRecognizerResult>(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity message = turnContext.getActivity(); + if (message.getAttachments() != null && message.getAttachments().size() > 0) { + result.setSucceeded(true); + result.setValue(message.getAttachments()); + } + } + + return CompletableFuture.completedFuture(result); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java deleted file mode 100644 index 53b32c613..000000000 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Choice.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. -package com.microsoft.bot.dialogs.prompts; - -import com.microsoft.bot.schema.CardAction; - -import java.util.ArrayList; - -public class Choice -{ - /** - * Value to return when selected. - */ - String _value; - public void setValue(String value) { - this._value = value; - } - public String getValue() { - return this._value; - } - - /** - * (Optional) action to use when rendering the choice as a suggested action. - */ - CardAction _action; - public CardAction getAction() { - return this._action; - } - public void setAction(CardAction action) { - this._action = action; - } - - /** - * (Optional) list of synonyms to recognize in addition to the value. - */ - ArrayList _synonyms; - public ArrayList getSynonyms() { - return _synonyms; - } - public void setSynonyms(ArrayList synonyms) { - this._synonyms = synonyms; - } -} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java new file mode 100644 index 000000000..533ce5f58 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java @@ -0,0 +1,311 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ChoiceRecognizers; +import com.microsoft.bot.dialogs.choices.FindChoicesOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.dialogs.choices.ModelResult; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user to select from a list of choices. + */ +public class ChoicePrompt extends Prompt { + + /** + * A dictionary of Default Choices based on {@link GetSupportedCultures} . Can + * be replaced by user using the constructor that contains choiceDefaults. + */ + private Map choiceDefaults; + + private ListStyle style; + private String defaultLocale; + private FindChoicesOptions recognizerOptions; + private ChoiceFactoryOptions choiceOptions; + + /** + * Initializes a new instance of the {@link ChoicePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public ChoicePrompt(String dialogId, PromptValidator validator, String defaultLocale) { + super(dialogId, validator); + + choiceDefaults = new HashMap(); + for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { + choiceDefaults.put(model.getLocale(), new ChoiceFactoryOptions() { + { + setInlineSeparator(model.getSeparator()); + setInlineOr(model.getInlineOr()); + setInlineOrMore(model.getInlineOrMore()); + setIncludeNumbers(true); + } + } + ); + } + + this.style = ListStyle.AUTO; + this.defaultLocale = defaultLocale; + } + + /** + * Initializes a new instance of the {@link ChoicePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that contains + * additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is a 2, 3, or 4 character + * ISO 639 code + * that represents a language or language family. + * @param choiceDefaults Overrides the dictionary of Bot Framework SDK-supported + * _choiceDefaults (for prompt localization). Must be passed in to each ConfirmPrompt that + * needs the custom choice defaults. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. If the {@link Activity#locale} of the + * {@link DialogContext} .{@link DialogContext#context} .{@link ITurnContext#activity} is + * specified, then that local is used to determine language specific behavior; otherwise the + * {@link defaultLocale} is used. US-English is the used if no language or default locale is + * available, or if the language or locale is not otherwise supported. + */ + public ChoicePrompt(String dialogId, Map choiceDefaults, + PromptValidator validator, String defaultLocale) { + this(dialogId, validator, defaultLocale); + + this.choiceDefaults = choiceDefaults; + } + + /** + * Gets the style to use when presenting the prompt to the user. + * + * @return The style to use when presenting the prompt to the user. + */ + public ListStyle getStyle() { + return this.style; + } + + /** + * Sets the style to use when presenting the prompt to the user. + * + * @param style The style to use when presenting the prompt to the user. + */ + public void setStyle(ListStyle style) { + this.style = style; + } + + /** + * Sets or sets the default locale used to determine language-specific behavior + * of the prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Gets or sets additional options passed to the underlying + * {@link ChoiceRecognizers#recognizeChoices(String, IList{Choice}, + * FindChoicesOptions)} method. + * + * @return Options to control the recognition strategy. + */ + public FindChoicesOptions getRecognizerOptions() { + return this.recognizerOptions; + } + + /** + * Gets or sets additional options passed to the underlying + * {@link ChoiceRecognizers#recognizeChoices(String, IList{Choice}, + * FindChoicesOptions)} method. + * + * @param recognizerOptions Options to control the recognition strategy. + */ + public void setRecognizerOptions(FindChoicesOptions recognizerOptions) { + this.recognizerOptions = recognizerOptions; + } + + /** + * Gets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @return Additional options for presenting the set of choices. + */ + public ChoiceFactoryOptions getChoiceOptions() { + return this.choiceOptions; + } + + /** + * Sets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @param choiceOptions Additional options for presenting the set of choices. + */ + public void setChoiceOptions(ChoiceFactoryOptions choiceOptions) { + this.choiceOptions = choiceOptions; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + String culture = determineCulture(turnContext.getActivity()); + + // Format prompt to send + Activity prompt; + + List choices = options.getChoices() != null ? options.getChoices() : new ArrayList(); + String channelId = turnContext.getActivity().getChannelId(); + ChoiceFactoryOptions choiceOpts = getChoiceOptions() != null + ? getChoiceOptions() : choiceDefaults.get(culture); + ListStyle choiceStyle = options.getStyle() != null ? options.getStyle() : style; + + if (isRetry && options.getRetryPrompt() != null) { + prompt = appendChoices(options.getRetryPrompt(), channelId, choices, choiceStyle, choiceOpts); + } else { + prompt = appendChoices(options.getPrompt(), channelId, choices, choiceStyle, choiceOpts); + } + + // Send prompt + turnContext.sendActivity(prompt).join(); + + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + List choices = options.getChoices() != null ? options.getChoices() : new ArrayList(); + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity activity = turnContext.getActivity(); + String utterance = activity.getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + FindChoicesOptions opt = recognizerOptions != null ? recognizerOptions : new FindChoicesOptions(); + opt.setLocale(determineCulture(activity, opt)); + List> results = ChoiceRecognizers.recognizeChoices(utterance, choices, opt); + if (results != null && results.size() > 0) { + result.setSucceeded(true); + result.setValue(results.get(0).getResolution()); + } + } + return CompletableFuture.completedFuture(result); + } + + private String determineCulture(Activity activity) { + return determineCulture(activity, null); + } + + private String determineCulture(Activity activity, FindChoicesOptions opt) { + + String locale; + if (activity.getLocale() != null) { + locale = activity.getLocale(); + } else if (opt != null) { + locale = opt.getLocale(); + } else if (defaultLocale != null) { + locale = defaultLocale; + } else { + locale = PromptCultureModels.ENGLISH_CULTURE; + } + + String culture = PromptCultureModels.mapToNearestLanguage(locale); + if (StringUtils.isBlank(culture) || !choiceDefaults.containsKey(culture)) { + culture = PromptCultureModels.ENGLISH_CULTURE; + } + return culture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java new file mode 100644 index 000000000..2a317d17a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -0,0 +1,353 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ChoiceRecognizers; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.dialogs.choices.ModelResult; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.choice.ChoiceRecognizer; + +import org.apache.commons.lang3.StringUtils; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +/** + * Prompts a user to confirm something with a yes/no response. + */ +public class ConfirmPrompt extends Prompt { + + /** + * A dictionary of Default Choices based on {@link GetSupportedCultures} . Can + * be replaced by user using the constructor that contains choiceDefaults. + */ + private Map> choiceDefaults; + private ListStyle style; + private String defaultLocale; + private ChoiceFactoryOptions choiceOptions; + private Pair confirmChoices; + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + */ + public ConfirmPrompt(String dialogId) { + this(dialogId, null, null); + } + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public ConfirmPrompt(String dialogId, PromptValidator validator, String defaultLocale) { + super(dialogId, validator); + + choiceDefaults = new HashMap>(); + for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { + Choice yesChoice = new Choice(model.getYesInLanguage()); + Choice noChoice = new Choice(model.getNoInLanguage()); + ChoiceFactoryOptions factoryOptions = new ChoiceFactoryOptions() { + { + setInlineSeparator(model.getSeparator()); + setInlineOr(model.getInlineOr()); + setInlineOrMore(model.getInlineOrMore()); + setIncludeNumbers(true); + } + }; + choiceDefaults.put(model.getLocale(), new Triplet(yesChoice, + noChoice, + factoryOptions)); + } + + this.style = ListStyle.AUTO; + this.defaultLocale = defaultLocale; + } + + /** + * Initializes a new instance of the {@link ConfirmPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * @param choiceDefaults Overrides the dictionary of Bot Framework SDK-supported + * _choiceDefaults (for prompt localization). Must be + * passed in to each ConfirmPrompt that needs the custom + * choice defaults. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} + * .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not + * otherwise supported. + */ + public ConfirmPrompt(String dialogId, Map> choiceDefaults, + PromptValidator validator, String defaultLocale) { + this(dialogId, validator, defaultLocale); + this.choiceDefaults = choiceDefaults; + } + + + /** + * Gets the style to use when presenting the prompt to the user. + * + * @return The style to use when presenting the prompt to the user. + */ + public ListStyle getStyle() { + return this.style; + } + + /** + * Sets the style to use when presenting the prompt to the user. + * + * @param style The style to use when presenting the prompt to the user. + */ + public void setStyle(ListStyle style) { + this.style = style; + } + + /** + * Sets or sets the default locale used to determine language-specific behavior + * of the prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Gets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @return Additional options for presenting the set of choices. + */ + public ChoiceFactoryOptions getChoiceOptions() { + return this.choiceOptions; + } + + /** + * Sets additional options passed to the {@link ChoiceFactory} and used to tweak the + * style of choices rendered to the user. + * @param choiceOptions Additional options for presenting the set of choices. + */ + public void setChoiceOptions(ChoiceFactoryOptions choiceOptions) { + this.choiceOptions = choiceOptions; + } + + /** + * Gets the yes and no {@link Choice} for the prompt. + * @return The yes and no {@link Choice} for the prompt. + */ + public Pair getConfirmChoices() { + return this.confirmChoices; + } + + /** + * Sets the yes and no {@link Choice} for the prompt. + * @param confirmChoices The yes and no {@link Choice} for the prompt. + */ + public void setConfirmChoices(Pair confirmChoices) { + this.confirmChoices = confirmChoices; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + // Format prompt to send + Activity prompt; + String channelId = turnContext.getActivity().getChannelId(); + String culture = determineCulture(turnContext.getActivity()); + Triplet defaults = choiceDefaults.get(culture); + ChoiceFactoryOptions localChoiceOptions = getChoiceOptions() != null ? getChoiceOptions() + : defaults.getValue2(); + List choices = new ArrayList(Arrays.asList(defaults.getValue0(), + defaults.getValue1())); + + ListStyle localStyle = options.getStyle() != null ? options.getStyle() : getStyle(); + if (isRetry && options.getRetryPrompt() != null) { + prompt = appendChoices(options.getRetryPrompt(), channelId, + choices, localStyle, localChoiceOptions); + } else { + prompt = appendChoices(options.getPrompt(), channelId, choices, + localStyle, localChoiceOptions); + } + + // Send prompt + turnContext.sendActivity(prompt).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + // Recognize utterance + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isBlank(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = determineCulture(turnContext.getActivity()); + List results = + ChoiceRecognizer.recognizeBoolean(utterance, culture); + if (results.size() > 0) { + com.microsoft.recognizers.text.ModelResult first = results.get(0); + Boolean value = (Boolean) first.resolution.get("value"); + if (value != null) { + result.setSucceeded(true); + result.setValue(value); + } + } else { + // First check whether the prompt was sent to the user with numbers - + // if it was we should recognize numbers + Triplet defaults = choiceDefaults.get(culture); + ChoiceFactoryOptions choiceOpts = choiceOptions != null ? choiceOptions : defaults.getValue2(); + + // This logic reflects the fact that IncludeNumbers is nullable and True is the default + // set in Inline style + if (choiceOpts.getIncludeNumbers() == null + || choiceOpts.getIncludeNumbers() != null && choiceOpts.getIncludeNumbers()) { + // The text may be a number in which case we will interpret that as a choice. + Pair confirmedChoices = confirmChoices != null ? confirmChoices + : new Pair(defaults.getValue0(), defaults.getValue1()); + ArrayList choices = new ArrayList() { + { + add(confirmedChoices.getValue0()); + add(confirmedChoices.getValue1()); + } + }; + + List> secondAttemptResults = + ChoiceRecognizers.recognizeChoices(utterance, choices); + if (secondAttemptResults.size() > 0) { + result.setSucceeded(true); + result.setValue(secondAttemptResults.get(0).getResolution().getIndex() == 0); + } + } + } + } + + return CompletableFuture.completedFuture(result); + } + + private String determineCulture(Activity activity) { + + String locale; + if (activity.getLocale() != null) { + locale = activity.getLocale(); + } else if (defaultLocale != null) { + locale = defaultLocale; + } else { + locale = PromptCultureModels.ENGLISH_CULTURE; + } + + String culture = PromptCultureModels.mapToNearestLanguage(locale); + if (StringUtils.isBlank(culture) || !choiceDefaults.containsKey(culture)) { + culture = PromptCultureModels.ENGLISH_CULTURE; + } + return culture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java new file mode 100644 index 000000000..9e971c486 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java @@ -0,0 +1,186 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user for a date-time value. + */ +public class DateTimePrompt extends Prompt> { + + private String defaultLocale; + + /** + * Initializes a new instance of the {@link DateTimePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator.FoundChoice} that + * contains additional, custom validation for this prompt. + * @param defaultLocale Optional, the default locale used to determine + * language-specific behavior of the prompt. The locale is + * a 2, 3, or 4 character ISO 639 code that represents a + * language or language family. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which + * the prompt is added. If the {@link Activity#locale} of + * the {@link DialogContext} .{@link DialogContext#context} + * .{@link ITurnContext#activity} is specified, then that + * local is used to determine language specific behavior; + * otherwise the {@link defaultLocale} is used. US-English + * is the used if no language or default locale is + * available, or if the language or locale is not otherwise + * supported. + */ + public DateTimePrompt(String dialogId, PromptValidator> validator, String defaultLocale) { + super(dialogId, validator); + this.defaultLocale = defaultLocale; + } + + /** + * Gets the default locale used to determine language-specific behavior of the + * prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input as a date-time value. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture>> + onRecognize(TurnContext turnContext, Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult> result = + new PromptRecognizerResult>(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = turnContext.getActivity().getLocale() != null ? turnContext.getActivity().getLocale() + : defaultLocale != null ? defaultLocale : PromptCultureModels.ENGLISH_CULTURE; + LocalDateTime refTime = turnContext.getActivity().getLocalTimestamp() != null + ? turnContext.getActivity().getLocalTimestamp().toLocalDateTime() : null; + List results = + DateTimeRecognizer.recognizeDateTime(utterance, culture, DateTimeOptions.None, true, refTime); + if (results.size() > 0) { + // Return list of resolutions from first match + result.setSucceeded(true); + result.setValue(new ArrayList()); + List> values = (List>) results.get(0).resolution.get("values"); + for (Map mapEntry : values) { + result.getValue().add(readResolution(mapEntry)); + } + } + } + + return CompletableFuture.completedFuture(result); + } + + private static DateTimeResolution readResolution(Map resolution) { + DateTimeResolution result = new DateTimeResolution(); + + if (resolution.containsKey("timex")) { + result.setTimex(resolution.get("timex")); + } + + if (resolution.containsKey("value")) { + result.setValue(resolution.get("value")); + } + + if (resolution.containsKey("start")) { + result.setStart(resolution.get("start")); + } + + if (resolution.containsKey("end")) { + result.setEnd(resolution.get("end")); + } + return result; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java new file mode 100644 index 000000000..156904b32 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimeResolution.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * A date-time value, as recognized by the {@link DateTimePrompt} . + * + * A value can represent a date, a time, a date and time, or a range of any of + * these. The representation of the value is determined by the locale used to + * parse the input. + */ + +public class DateTimeResolution { + + private String value; + private String start; + private String end; + private String timex; + + /** + * Gets a human-readable representation of the value, for a non-range result. + * + * @return A human-readable representation of the value, for a non-range result. + */ + public String getValue() { + return this.value; + } + + /** + * Sets a human-readable representation of the value, for a non-range result. + * + * @param value A human-readable representation of the value, for a non-range + * result. + */ + public void setValue(String value) { + this.value = value; + } + + /** + * Gets a human-readable representation of the start value, for a range result. + * + * @return A human-readable representation of the start value, for a range + * result. + */ + public String getStart() { + return this.start; + } + + /** + * Sets a human-readable representation of the start value, for a range result. + * + * @param start A human-readable representation of the start value, for a range + * result. + */ + public void setStart(String start) { + this.start = start; + } + + /** + * Gets a human-readable represntation of the end value, for a range result. + * + * @return A human-readable representation of the end value, for a range result. + */ + public String getEnd() { + return this.end; + } + + /** + * Sets a human-readable represntation of the end value, for a range result. + * + * @param end A human-readable representation of the end value, for a range + * result. + */ + public void setEnd(String end) { + this.end = end; + } + + /** + * Gets the value in TIMEX format. The TIMEX format that follows the ISO 8601 + * standard. + * + * @return A TIMEX representation of the value. + */ + public String getTimex() { + return this.timex; + } + + /** + * Sets the value in TIMEX format. The TIMEX format that follows the ISO 8601 + * standard. + * + * @param timex A TIMEX representation of the value. + */ + public void setTimex(String timex) { + this.timex = timex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java new file mode 100644 index 000000000..2446328f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java @@ -0,0 +1,252 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.number.NumberRecognizer; +import com.microsoft.recognizers.text.numberwithunit.NumberWithUnitRecognizer; + +import org.apache.commons.lang3.StringUtils; + +/** + * Prompts a user to enter a number. + * + * The number prompt currently supports these types: {@link float} , {@link int} + * , {@link long} , {@link double} , and {@link decimal} . + * @param numeric type for this prompt, which can be int, long, double, or float. + */ +public class NumberPrompt extends Prompt { + + private String defaultLocale; + private final Class classOfNumber; + + /** + * Initializes a new instance of the {@link NumberPrompt{T}} class. + * + * @param dialogId Unique ID of the dialog within its parent + * {@link DialogSet} or {@link ComponentDialog} . + * @param classOfNumber Type of used to determine within the class what type was created for. This is required + * due to type erasure in Java not allowing checking the type of during runtime. + * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for . + */ + public NumberPrompt(String dialogId, Class classOfNumber) + throws UnsupportedDataTypeException { + this(dialogId, null, null, classOfNumber); + } + + /** + * Initializes a new instance of the {@link NumberPrompt{T}} class. + * + * @param dialogId Unique ID of the dialog within its parent + * {@link DialogSet} or {@link ComponentDialog} . + * @param validator Validator that will be called each time the user + * responds to the prompt. + * @param defaultLocale Locale to use. + * @param classOfNumber Type of used to determine within the class what type was created for. This is required + * due to type erasure in Java not allowing checking the type of during runtime. + * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for . + */ + public NumberPrompt(String dialogId, PromptValidator validator, String defaultLocale, Class classOfNumber) + throws UnsupportedDataTypeException { + + super(dialogId, validator); + this.defaultLocale = defaultLocale; + this.classOfNumber = classOfNumber; + + if (!(classOfNumber.getSimpleName().equals("Long") || classOfNumber.getSimpleName().equals("Integer") + || classOfNumber.getSimpleName().equals("Float") || classOfNumber.getSimpleName().equals("Double"))) { + throw new UnsupportedDataTypeException(String.format("NumberPrompt: Type argument %s is not supported", + classOfNumber.getSimpleName())); + } + } + + /** + * Gets the default locale used to determine language-specific behavior of the + * prompt. + * + * @return The default locale used to determine language-specific behavior of + * the prompt. + */ + public String getDefaultLocale() { + return this.defaultLocale; + } + + /** + * Sets the default locale used to determine language-specific behavior of the + * prompt. + * + * @param defaultLocale The default locale used to determine language-specific + * behavior of the prompt. + */ + public void setDefaultLocale(String defaultLocale) { + this.defaultLocale = defaultLocale; + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance + * on the stack is prompting the user for input; otherwise, + * false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the + * recognition attempt. + */ + @Override + @SuppressWarnings("PMD") + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + String utterance = turnContext.getActivity().getText(); + if (StringUtils.isEmpty(utterance)) { + return CompletableFuture.completedFuture(result); + } + + String culture = turnContext.getActivity().getLocale() != null ? turnContext.getActivity().getLocale() + : defaultLocale != null ? defaultLocale : PromptCultureModels.ENGLISH_CULTURE; + List results = recognizeNumberWithUnit(utterance, culture); + if (results != null && results.size() > 0) { + // Try to parse value based on type + String text = ""; + + // Try to parse value based on type + Object valueResolution = results.get(0).resolution.get("value"); + if (valueResolution != null) { + text = (String) valueResolution; + } + + if (classOfNumber.getSimpleName().equals("Float")) { + try { + Float value = Float.parseFloat(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Integer")) { + try { + Integer value = Integer.parseInt(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Long")) { + try { + Long value = Long.parseLong(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } else if (classOfNumber.getSimpleName().equals("Double")) { + try { + Double value = Double.parseDouble(text); + result.setSucceeded(true); + result.setValue((T) (Object) value); + + } catch (NumberFormatException numberFormatException) { + } + } + } + } + return CompletableFuture.completedFuture(result); + } + + private static List recognizeNumberWithUnit(String utterance, String culture) { + List number = NumberRecognizer.recognizeNumber(utterance, culture); + + if (number.size() > 0) { + // Result when it matches with a number recognizer + return number; + } else { + List result; + // Analyze every option for numberWithUnit + result = NumberWithUnitRecognizer.recognizeCurrency(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeAge(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeTemperature(utterance, culture); + if (result.size() > 0) { + return result; + } + + result = NumberWithUnitRecognizer.recognizeDimension(utterance, culture); + if (result.size() > 0) { + return result; + } + + return null; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java new file mode 100644 index 000000000..b4130d560 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -0,0 +1,382 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogEvent; +import com.microsoft.bot.dialogs.DialogEvents; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.InputHints; + +import org.apache.commons.lang3.StringUtils; + +/** + * Defines the core behavior of prompt dialogs. + * + * When the prompt ends, it should return a Object that represents the value + * that was prompted for. Use {@link DialogSet#add(Dialog)} or + * {@link ComponentDialog#addDialog(Dialog)} to add a prompt to a dialog set or + * component dialog, respectively. Use + * {@link DialogContext#prompt(String, PromptOptions)} or + * {@link DialogContext#beginDialog(String, Object)} to start the prompt. If you + * start a prompt from a {@link WaterfallStep} in a {@link WaterfallDialog} , + * then the prompt result will be available in the next step of the waterfall. + * + * @param Type the prompt is created for. + */ +public abstract class Prompt extends Dialog { + + public static final String ATTEMPTCOUNTKEY = "AttemptCount"; + + private final String persistedOptions = "options"; + private final String persistedState = "state"; + private final PromptValidator validator; + + /** + * Initializes a new instance of the {@link Prompt{T}} class. Called from + * constructors in derived classes to initialize the {@link Prompt{T}} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{T}} that contains + * additional, custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public Prompt(String dialogId, PromptValidator validator) { + super(dialogId); + if (StringUtils.isBlank(dialogId)) { + throw new IllegalArgumentException("dialogId cannot be null"); + } + this.validator = validator; + } + + /** + * Called when a prompt dialog is pushed onto the dialog stack and is being + * activated. + * + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being + * started. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the prompt is + * still active after the turn has been processed by the prompt. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (!(options instanceof PromptOptions)) { + return Async.completeExceptionally(new IllegalArgumentException( + "Prompt options are required for Prompt dialogs" + )); + } + + // Ensure prompts have input hint set + PromptOptions opt = (PromptOptions) options; + + if (opt.getPrompt() != null && (opt.getPrompt().getInputHint() == null + || StringUtils.isEmpty(opt.getPrompt().getInputHint().toString()))) { + opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + } + + if (opt.getRetryPrompt() != null && (opt.getRetryPrompt().getInputHint() == null + || StringUtils.isEmpty(opt.getRetryPrompt().getInputHint().toString()))) { + opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + } + + + // Initialize prompt state + Map state = dc.getActiveDialog().getState(); + state.put(persistedOptions, opt); + + HashMap pState = new HashMap(); + pState.put(ATTEMPTCOUNTKEY, 0); + state.put(persistedState, pState); + + // Send initial prompt + onPrompt(dc.getContext(), + (Map) state.get(persistedState), + (PromptOptions) state.get(persistedOptions), + false); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog is the active dialog and the user replied with a + * new activity. + * + * @param dc The dialog context for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * prompt generally continues to receive the user's replies until it + * accepts the user's reply as valid input for the prompt. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Don't do anything for non-message activities + if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + // Perform base recognition + DialogInstance instance = dc.getActiveDialog(); + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); + + state.put(ATTEMPTCOUNTKEY, (int) state.get(ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + Boolean isValid = false; + if (validator != null) { + PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), + recognized, state, options); + isValid = validator.promptValidator(promptContext).join(); + } else if (recognized.getSucceeded()) { + isValid = true; + } + + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } + + if (!dc.getContext().getResponded()) { + onPrompt(dc.getContext(), state, options, true); + } + + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog resumes being the active dialog on the dialog + * stack, such as when the previous active dialog on the stack completes. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason An enum indicating why the dialog resumed. + * @param result Optional, value returned from the previous dialog on the stack. + * The type of the value returned is dependent on the previous + * dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + + // Prompts are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the prompt receiving an unexpected + // call to + // dialogResume() when the pushed on dialog ends. + // To avoid the prompt prematurely ending we need to implement this method and + // simply re-prompt the user. + repromptDialog(dc.getContext(), dc.getActiveDialog()).join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + + /** + * Called when a prompt dialog has been requested to re-prompt the user for + * input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param instance The instance of the dialog on the stack. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + Map state = (Map) instance.getState().get(persistedState); + PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + onPrompt(turnContext, state, options, false).join(); + return CompletableFuture.completedFuture(null); + } + + /** + * Called before an event is bubbled to its parent. + * + * This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing. + * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * + * @return Whether the event is handled by the current dialog and further processing + * should stop. + */ + @Override + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + if (e.getName() == DialogEvents.ACTIVITY_RECEIVED + && dc.getContext().getActivity().getType() == ActivityTypes.MESSAGE) { + // Perform base recognition + Map state = dc.getActiveDialog().getState(); + PromptRecognizerResult recognized = onRecognize(dc.getContext(), + (Map) state.get(persistedState), (PromptOptions) state.get(persistedOptions)).join(); + return CompletableFuture.completedFuture(recognized.getSucceeded()); + } + + return CompletableFuture.completedFuture(false); + } + + /** + * When overridden in a derived class, prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance + * is on the stack is prompting the user for input; + * otherwise, false. Determines whether + * {@link PromptOptions#prompt} or + * {@link PromptOptions#retryPrompt} should be used. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + protected abstract CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry); + + /** + * When overridden in a derived class, attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the + * recognition attempt. + */ + protected abstract CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options); + + /** + * When overridden in a derived class, appends choices to the activity when the + * user is prompted for input. + * + * @param prompt The activity to append the choices to. + * @param channelId The ID of the user's channel. + * @param choices The choices to append. + * @param style Indicates how the choices should be presented to the user. + * @param options The formatting options to use when presenting the choices. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result contains the updated activity. + */ + protected Activity appendChoices(Activity prompt, String channelId, List choices, + ListStyle style, ChoiceFactoryOptions options) { + // Get base prompt text (if any) + String text = ""; + if (prompt != null && prompt.getText() != null && StringUtils.isNotBlank(prompt.getText())) { + text = prompt.getText(); + } + + // Create temporary msg + Activity msg; + switch (style) { + case INLINE: + msg = ChoiceFactory.inline(choices, text, null, options); + break; + + case LIST: + msg = ChoiceFactory.list(choices, text, null, options); + break; + + case SUGGESTED_ACTION: + msg = ChoiceFactory.suggestedAction(choices, text); + break; + + case HEROCARD: + msg = ChoiceFactory.heroCard(choices, text); + break; + + case NONE: + msg = Activity.createMessageActivity(); + msg.setText(text); + break; + + default: + msg = ChoiceFactory.forChannel(channelId, choices, text, null, options); + break; + } + + // Update prompt with text, actions and attachments + if (prompt != null) { + // clone the prompt the set in the options (note ActivityEx has Properties so this is the safest mechanism) + //prompt = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(prompt)); + prompt = Activity.clone(prompt); + + prompt.setText(msg.getText()); + + if (msg.getSuggestedActions() != null && msg.getSuggestedActions().getActions() != null + && msg.getSuggestedActions().getActions().size() > 0) { + prompt.setSuggestedActions(msg.getSuggestedActions()); + } + + if (msg.getAttachments() != null && msg.getAttachments().size() > 0) { + if (prompt.getAttachments() == null) { + prompt.setAttachments(msg.getAttachments()); + } else { + List allAttachments = prompt.getAttachments(); + prompt.getAttachments().addAll(msg.getAttachments()); + prompt.setAttachments(allAttachments); + } + } + + return prompt; + } + + msg.setInputHint(InputHints.EXPECTING_INPUT); + return msg; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java new file mode 100644 index 000000000..cbd369b8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModel.java @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * Culture model used in Choice and Confirm Prompts. + */ +public class PromptCultureModel { + + private String locale; + private String separator; + private String inlineOr; + private String inlineOrMore; + private String yesInLanguage; + private String noInLanguage; + + /** + * Creates a PromptCultureModel. + */ + public PromptCultureModel() { + + } + + /** + * Gets Culture Model's Locale. + * + * @return Ex: Locale. Example: "en-US". + */ + public String getLocale() { + return this.locale; + } + + /** + * Sets Culture Model's Locale. + * + * @param locale Ex: Locale. Example: "en-US". + */ + public void setLocale(String locale) { + this.locale = locale; + } + + /** + * GetsCulture Model's Inline Separator. + * + * @return Example: ", ". + */ + public String getSeparator() { + return this.separator; + } + + /** + * Sets Culture Model's Inline Separator. + * + * @param separator Example: ", ". + */ + public void setSeparator(String separator) { + this.separator = separator; + } + + /** + * Gets Culture Model's InlineOr. + * + * @return Example: " or ". + */ + public String getInlineOr() { + return this.inlineOr; + } + + /** + * Sets Culture Model's InlineOr. + * + * @param inlineOr Example: " or ". + */ + public void setInlineOr(String inlineOr) { + this.inlineOr = inlineOr; + } + + /** + * Gets Culture Model's InlineOrMore. + * + * @return Example: ", or ". + */ + public String getInlineOrMore() { + return this.inlineOrMore; + } + + /** + * Sets Culture Model's InlineOrMore. + * + * @param inlineOrMore Example: ", or ". + */ + public void setInlineOrMore(String inlineOrMore) { + this.inlineOrMore = inlineOrMore; + } + + /** + * Gets Equivalent of "Yes" in Culture Model's Language. + * + * @return Example: "Yes". + */ + public String getYesInLanguage() { + return this.yesInLanguage; + } + + /** + * Sets Equivalent of "Yes" in Culture Model's Language. + * + * @param yesInLanguage Example: "Yes". + */ + public void setYesInLanguage(String yesInLanguage) { + this.yesInLanguage = yesInLanguage; + } + + /** + * Gets Equivalent of "No" in Culture Model's Language. + * + * @return Example: "No". + */ + public String getNoInLanguage() { + return this.noInLanguage; + } + + /** + * Sets Equivalent of "No" in Culture Model's Language. + * + * @param noInLanguage Example: "No". + */ + public void setNoInLanguage(String noInLanguage) { + this.noInLanguage = noInLanguage; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java new file mode 100644 index 000000000..edb733220 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptCultureModels.java @@ -0,0 +1,308 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Class container for currently-supported Culture Models in Confirm and Choice + * Prompt. + */ +public final class PromptCultureModels { + + private PromptCultureModels() { + + } + + public static final String BULGARIAN_CULTURE = "bg-bg"; + public static final String CHINESE_CULTURE = "zh-cn"; + public static final String DUTCH_CULTURE = "nl-nl"; + public static final String ENGLISH_CULTURE = "en-us"; + public static final String FRENCH_CULTURE = "fr-fr"; + public static final String GERMAN_CULTURE = "de-de"; + public static final String HINDI_CULTURE = "hi-in"; + public static final String ITALIAN_CULTURE = "it-it"; + public static final String JAPANESE_CULTURE = "ja-jp"; + public static final String KOREAN_CULTURE = "ko-kr"; + public static final String PORTUGUESE_CULTURE = "pt-br"; + public static final String SPANISH_CULTURE = "es-es"; + public static final String SWEDISH_CULTURE = "sv-se"; + public static final String TURKISH_CULTURE = "tr-tr"; + + /** + * Gets the bulgarian prompt culture model. + */ + public static final PromptCultureModel BULGARIAN = new PromptCultureModel() { + { + setInlineOr(" или "); + setInlineOrMore(", или "); + setLocale(BULGARIAN_CULTURE); + setNoInLanguage("Не"); + setSeparator(", "); + setYesInLanguage("да"); + } + }; + + public static final PromptCultureModel CHINESE = new PromptCultureModel() { + { + setInlineOr(" 要么 "); + setInlineOrMore(", 要么 "); + setLocale(CHINESE_CULTURE); + setNoInLanguage("不"); + setSeparator(", "); + setYesInLanguage("是的"); + } + }; + + /** + * Gets the dutch prompt culture model. + */ + public static final PromptCultureModel DUTCH = new PromptCultureModel() { + { + setInlineOr(" of "); + setInlineOrMore(", of "); + setLocale(DUTCH_CULTURE); + setNoInLanguage("Nee"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the english prompt culture model. + */ + public static final PromptCultureModel ENGLISH = new PromptCultureModel() { + { + setInlineOr(" or "); + setInlineOrMore(", or "); + setLocale(ENGLISH_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Yes"); + } + }; + + /** + * Gets the french prompt culture model. + */ + public static final PromptCultureModel FRENCH = new PromptCultureModel() { + { + setInlineOr(" ou "); + setInlineOrMore(", ou "); + setLocale(FRENCH_CULTURE); + setNoInLanguage("Non"); + setSeparator(", "); + setYesInLanguage("Oui"); + } + }; + + /** + * Gets the german prompt culture model. + */ + public static final PromptCultureModel GERMAN = new PromptCultureModel() { + { + setInlineOr(" oder "); + setInlineOrMore(", oder "); + setLocale(GERMAN_CULTURE); + setNoInLanguage("Nein"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the hindi prompt culture model. + */ + public static final PromptCultureModel HINDI = new PromptCultureModel() { + { + setInlineOr(" या "); + setInlineOrMore(", या "); + setLocale(HINDI_CULTURE); + setNoInLanguage("नहीं"); + setSeparator(", "); + setYesInLanguage("हां"); + } + }; + + /** + * Gets the italian prompt culture model. + */ + public static final PromptCultureModel ITALIAN = new PromptCultureModel() { + { + setInlineOr(" o "); + setInlineOrMore(" o "); + setLocale(ITALIAN_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Si"); + } + }; + + /** + * Gets the japanese prompt culture model. + */ + public static final PromptCultureModel JAPANESE = new PromptCultureModel() { + { + setInlineOr(" または "); + setInlineOrMore("、 または "); + setLocale(JAPANESE_CULTURE); + setNoInLanguage("いいえ"); + setSeparator("、 "); + setYesInLanguage("はい"); + } + }; + + /** + * Gets the korean prompt culture model. + */ + public static final PromptCultureModel KOREAN = new PromptCultureModel() { + { + setInlineOr(" 또는 "); + setInlineOrMore(" 또는 "); + setLocale(KOREAN_CULTURE); + setNoInLanguage("아니"); + setSeparator(", "); + setYesInLanguage("예"); + } + }; + + /** + * Gets the portuguese prompt culture model. + */ + public static final PromptCultureModel PORTUGUESE = new PromptCultureModel() { + { + setInlineOr(" ou "); + setInlineOrMore(", ou "); + setLocale(PORTUGUESE_CULTURE); + setNoInLanguage("Não"); + setSeparator(", "); + setYesInLanguage("Sim"); + } + }; + + /** + * Gets the spanish prompt culture model. + */ + public static final PromptCultureModel SPANISH = new PromptCultureModel() { + { + setInlineOr(" o "); + setInlineOrMore(", o "); + setLocale(SPANISH_CULTURE); + setNoInLanguage("No"); + setSeparator(", "); + setYesInLanguage("Sí"); + } + }; + + /** + * Gets the swedish prompt culture model. + */ + public static final PromptCultureModel SWEDISH = new PromptCultureModel() { + { + setInlineOr(" eller "); + setInlineOrMore(" eller "); + setLocale(SWEDISH_CULTURE); + setNoInLanguage("Nej"); + setSeparator(", "); + setYesInLanguage("Ja"); + } + }; + + /** + * Gets the turkish prompt culture model. + */ + public static final PromptCultureModel TURKISH = new PromptCultureModel() { + { + setInlineOr(" veya "); + setInlineOrMore(" veya "); + setLocale(TURKISH_CULTURE); + setNoInLanguage("Hayır"); + setSeparator(", "); + setYesInLanguage("Evet"); + } + }; + + private static PromptCultureModel[] promptCultureModelArray = + { + BULGARIAN, + CHINESE, + DUTCH, + ENGLISH, + FRENCH, + GERMAN, + HINDI, + ITALIAN, + JAPANESE, + KOREAN, + PORTUGUESE, + SPANISH, + SWEDISH, + TURKISH + }; + + /** + * Gets a list of the supported culture models. + * + * @return Array of {@link PromptCultureModel} with the supported cultures. + */ + public static PromptCultureModel[] getSupportedCultures() { + return promptCultureModelArray; + } + + private static final List SUPPORTED_LOCALES = Arrays.stream(getSupportedCultures()) + .map(x -> x.getLocale()).collect(Collectors.toList()); + + // private static List supportedlocales; + + // static { + // supportedlocales = new ArrayList(); + // PromptCultureModel[] cultures = getSupportedCultures(); + // for (PromptCultureModel promptCultureModel : cultures) { + // supportedlocales.add(promptCultureModel.getLocale()); + // } + // } + + /** + * Use Recognizers-Text to normalize various potential setLocale strings to a standard. + * + * This is mostly a copy/paste from + * https://github.com/microsoft/Recognizers-Text/blob/master/.NET/Microsoft.Recognizers.Text/C + * lture.cs#L66 This doesn't directly use Recognizers-Text's MapToNearestLanguage because if + * they add language support before we do, it will break our prompts. + * + * @param cultureCode Represents setLocale. Examples: "en-US, en-us, EN". + * + * @return Normalized setLocale. + */ + public static String mapToNearestLanguage(String cultureCode) { + cultureCode = cultureCode.toLowerCase(); + final String cCode = cultureCode; + + if (SUPPORTED_LOCALES.stream().allMatch(o -> o != cCode)) { + // Handle cases like EnglishOthers with cultureCode "en-*" + List fallbackCultureCodes = SUPPORTED_LOCALES.stream() + .filter(o -> o.endsWith("*") + && cCode.startsWith(o.split("-")[0])) + .collect(Collectors.toList()); + + if (fallbackCultureCodes.size() == 1) { + return fallbackCultureCodes.get(0); + } + + //If there is no cultureCode like "-*", map only the prefix + //For example, "es-mx" will be mapped to "es-es" + fallbackCultureCodes = SUPPORTED_LOCALES.stream() + .filter(o -> cCode.startsWith(o.split("-")[0])) + .collect(Collectors.toList()); + + if (fallbackCultureCodes.size() > 0) { + return fallbackCultureCodes.get(0); + } + } + + return cultureCode; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java new file mode 100644 index 000000000..33ec403e1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptOptions.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import java.util.List; + + +/** + * Contains settings to pass to a {@link com.} when the prompt is started. + */ +public class PromptOptions { + + private Activity prompt; + + private Activity retryPrompt; + + private List choices; + + private ListStyle style; + + private Object validations; + + + /** + * @return Activity + */ + public Activity getPrompt() { + return this.prompt; + } + + + /** + * @param withPrompt value to set the Prompt property to + */ + public void setPrompt(Activity withPrompt) { + this.prompt = withPrompt; + } + + + /** + * @return Activity + */ + public Activity getRetryPrompt() { + return this.retryPrompt; + } + + + /** + * @param withRetryPrompt value to set the Retry property to + */ + public void setRetryPrompt(Activity withRetryPrompt) { + this.retryPrompt = withRetryPrompt; + } + + + /** + * @return List + */ + public List getChoices() { + return this.choices; + } + + + /** + * @param withChoices value to set the Choices property to + */ + public void setChoices(List withChoices) { + this.choices = withChoices; + } + + + /** + * @return ListStyle + */ + public ListStyle getStyle() { + return this.style; + } + + + /** + * @param withStyle value to set the Style property to + */ + public void setStyle(ListStyle withStyle) { + this.style = withStyle; + } + + + /** + * @return Object + */ + public Object getValidations() { + return this.validations; + } + + + /** + * @param withValidations value to set the Validations property to + */ + public void setValidations(Object withValidations) { + this.validations = withValidations; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java new file mode 100644 index 000000000..ea9c6391c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptRecognizerResult.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +/** + * Contains the result returned by the recognition method of a {@link Prompt{T}} + * . + * + * @param The type of value the prompt returns. + */ +public class PromptRecognizerResult { + + private T value; + private Boolean succeeded; + private Boolean allowInterruption = false; + + /** + * Initializes a new instance of the {@link PromptRecognizerResult{T}} class. + */ + public PromptRecognizerResult() { + succeeded = false; + } + + /** + * Gets the recognition value. + * + * @return The recognition value. + */ + public T getValue() { + return this.value; + } + + /** + * Sets the recogntion value. + * + * @param value Value to set the recognition value to. + */ + public void setValue(T value) { + this.value = value; + } + + /** + * Gets a value indicating whether the recognition attempt succeeded. + * + * @return True if the recognition attempt succeeded; otherwise, false. + */ + public Boolean getSucceeded() { + return this.succeeded; + } + + /** + * Sets a value indicating whether the recognition attempt succeeded. + * + * @param succeeded True if the recognition attempt succeeded; otherwise, false. + */ + + public void setSucceeded(Boolean succeeded) { + this.succeeded = succeeded; + } + + /** + * Gets a value indicating whether flag indicating whether or not parent dialogs + * should be allowed to interrupt the prompt. + * + * @return The default value is `false`. + */ + public Boolean getAllowInterruption() { + return this.allowInterruption; + } + + /** + * Sets a value indicating whether flag indicating whether or not parent dialogs + * should be allowed to interrupt the prompt. + * + * @param allowInterruption The default value is `false`. + */ + public void setAllowInterruption(Boolean allowInterruption) { + this.allowInterruption = allowInterruption; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java new file mode 100644 index 000000000..8fe211cbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidator.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +/** + * The interface definition for custom prompt validators. Implement this + * function to add custom validation to a prompt. + * + * @param Type the PromptValidator is created for. + */ + +public interface PromptValidator { + + /** + * The delegate definition for custom prompt validators. Implement this function to add custom + * validation to a prompt. + * + * @param promptContext The prompt validation context. + * + * @return A {@link CompletableFuture} of bool representing the asynchronous operation + * indicating validation success or failure. + */ + CompletableFuture promptValidator(PromptValidatorContext promptContext); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java new file mode 100644 index 000000000..57b95abb4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/PromptValidatorContext.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Map; + +import com.microsoft.bot.builder.TurnContext; + +/** + * Contains context information for a {@link PromptValidator{T}} . + * + * @param Type for this Context + */ + +public class PromptValidatorContext { + + private Map state; + private PromptOptions options; + private TurnContext context; + private PromptRecognizerResult recognized; + + /** + * Create a PromptValidatorContext Instance. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * @param recognized The recognition results from the prompt's recognition + * attempt. + * @param state State for the associated prompt instance. + * @param options The prompt options used for this recognition attempt. + */ + public PromptValidatorContext(TurnContext turnContext, PromptRecognizerResult recognized, + Map state, PromptOptions options) { + this.context = turnContext; + this.options = options; + this.recognized = recognized; + this.state = state; + } + + /** + * Gets state for the associated prompt instance. + * + * @return State for the associated prompt instance. + */ + public Map getState() { + return this.state; + } + + /** + * Gets the {@link PromptOptions} used for this recognition attempt. + * + * @return The prompt options used for this recognition attempt. + */ + public PromptOptions getOptions() { + return this.options; + } + + /** + * Gets the {@link TurnContext} for the current turn of conversation with the + * user. + * + * @return Context for the current turn of conversation with the user. + */ + public TurnContext getContext() { + return this.context; + } + + /** + * Gets the {@link PromptRecognizerResult{T}} returned from the prompt's + * recognition attempt. + * + * @return The recognition results from the prompt's recognition attempt. + */ + public PromptRecognizerResult getRecognized() { + return this.recognized; + } + + /** + * Gets the number of times this instance of the prompt has been executed. + * + * This count is set when the prompt is added to the dialog stack. + * + * @return the attempt count. + */ + public int getAttemptCount() { + if (!state.containsKey(Prompt.ATTEMPTCOUNTKEY)) { + return 0; + } + + return (int) state.get(Prompt.ATTEMPTCOUNTKEY); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java new file mode 100644 index 000000000..a26c9b2e5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogEvent; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +/** + * Prompts the user for text input. + */ +public class TextPrompt extends Prompt { + + /** + * Initializes a new instance of the {@link TextPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public TextPrompt(String dialogId) { + this(dialogId, null); + } + + /** + * Initializes a new instance of the {@link TextPrompt} class. + * + * @param dialogId The ID to assign to this prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that contains + * additional, custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the {@link DialogSet} or + * {@link ComponentDialog} to which the prompt is added. + */ + public TextPrompt(String dialogId, PromptValidator validator) { + super(dialogId, validator); + } + + /** + * Prompts the user for input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry true if this is the first time this prompt dialog instance on the + * stack is prompting the user for input; otherwise, false. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + if (options == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "options cannot be null" + )); + } + + if (isRetry && options.getRetryPrompt() != null) { + turnContext.sendActivity(options.getRetryPrompt()).join(); + } else if (options.getPrompt() != null) { + turnContext.sendActivity(options.getPrompt()).join(); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Attempts to recognize the user's input. + * + * @param turnContext Context for the current turn of conversation with the user. + * @param state Contains state for the current instance of the prompt on the + * dialog stack. + * @param options A prompt options Object constructed from the options initially + * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result describes the result of the recognition attempt. + */ + @Override + protected CompletableFuture> onRecognize(TurnContext turnContext, + Map state, PromptOptions options) { + + if (turnContext == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "turnContext cannot be null" + )); + } + + PromptRecognizerResult result = new PromptRecognizerResult(); + if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + Activity message = turnContext.getActivity(); + if (message.getText() != null) { + result.setSucceeded(true); + result.setValue(message.getText()); + } + } + + return CompletableFuture.completedFuture(result); + } + + /** + * Called before an event is bubbled to its parent. + * + * This is a good place to perform interception of an event as returning `true` will prevent + * any further bubbling of the event to the dialogs parents and will also prevent any child + * dialogs from performing their default processing. + * + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. + * + * @return Whether the event is handled by the current dialog and further processing + * should stop. + */ + @Override + protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { + return CompletableFuture.completedFuture(false); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java new file mode 100644 index 000000000..9db4e7b18 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.dialogs.prompts; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java new file mode 100644 index 000000000..d5e0a77a4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Culture.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text; + +import java.util.Arrays; + +public class Culture { + public static final String English = "en-us"; + public static final String Chinese = "zh-cn"; + public static final String Spanish = "es-es"; + public static final String Portuguese = "pt-br"; + public static final String French = "fr-fr"; + public static final String German = "de-de"; + public static final String Japanese = "ja-jp"; + public static final String Dutch = "nl-nl"; + public static final String Italian = "it-it"; + + public static final Culture[] SupportedCultures = new Culture[]{ + new Culture("English", English), + new Culture("Chinese", Chinese), + new Culture("Spanish", Spanish), + new Culture("Portuguese", Portuguese), + new Culture("French", French), + new Culture("German", German), + new Culture("Japanese", Japanese), + new Culture("Dutch", Dutch), + new Culture("Italian", Italian), + }; + + public final String cultureName; + public final String cultureCode; + + public Culture(String cultureName, String cultureCode) { + this.cultureName = cultureName; + this.cultureCode = cultureCode; + } + + public static String[] getSupportedCultureCodes() { + return Arrays.stream(SupportedCultures) + .map(c -> c.cultureCode) + .toArray(String[]::new); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java new file mode 100644 index 000000000..fb3bd8949 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/CultureInfo.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text; + +public class CultureInfo { + + public final String cultureCode; + + public CultureInfo(String cultureCode) { + this.cultureCode = cultureCode; + } + + public static CultureInfo getCultureInfo(String cultureCode) { + return new CultureInfo(cultureCode); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java new file mode 100644 index 000000000..bc8931903 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtendedModelResult.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text; + +import java.util.SortedMap; + +public class ExtendedModelResult extends ModelResult { + // Parameter Key + public static final String ParentTextKey = "parentText"; + + public final String parentText; + + public ExtendedModelResult(String text, int start, int end, String typeName, SortedMap resolution, String parentText) { + super(text, start, end, typeName, resolution); + this.parentText = parentText; + } + + public ExtendedModelResult(ModelResult result, String parentText) { + this(result.text, result.start, result.end, result.typeName, result.resolution, parentText); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java new file mode 100644 index 000000000..4cdae8445 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ExtractResult.java @@ -0,0 +1,103 @@ +package com.microsoft.recognizers.text; + +public class ExtractResult { + + private Integer start; + private Integer length; + private Object data; + private String type; + private String text; + private Metadata metadata; + + public ExtractResult() { + this(null, null, null, null); + } + + public ExtractResult(Integer start, Integer length, String text, String type) { + this(start, length, text, type, null, null); + } + + public ExtractResult(Integer start, Integer length, String text, String type, Object data, Metadata metadata) { + this.start = start; + this.length = length; + this.text = text; + this.type = type; + this.data = data; + this.metadata = metadata; + } + + public ExtractResult(Integer start, Integer length, String text, String type, Object data) { + this.start = start; + this.length = length; + this.text = text; + this.type = type; + this.data = data; + this.metadata = null; + } + + private boolean isOverlap(ExtractResult er1, ExtractResult er2) { + return !(er1.getStart() >= er2.getStart() + er2.getLength()) && + !(er2.getStart() >= er1.getStart() + er1.getLength()); + } + + public boolean isOverlap(ExtractResult er) { + return isOverlap(this, er); + } + + private boolean isCover(ExtractResult er1, ExtractResult er2) { + return ((er2.getStart() < er1.getStart()) && ((er2.getStart() + er2.getLength()) >= (er1.getStart() + er1.getLength()))) || + ((er2.getStart() <= er1.getStart()) && ((er2.getStart() + er2.getLength()) > (er1.getStart() + er1.getLength()))); + } + + public boolean isCover(ExtractResult er) { + return isCover(this, er); + } + + public Integer getStart() { + return start; + } + + public void setStart(Integer start) { + this.start = start; + } + + public Integer getLength() { + return length; + } + + public void setLength(Integer length) { + this.length = length; + } + + public Object getData() { + return data; + } + + public void setData(Object data) { + this.data = data; + } + + public String getType() { + return type; + } + + public void setType(String type) { + this.type = type; + } + + public String getText() { + return text; + } + + public void setText(String text) { + this.text = text; + } + + public Metadata getMetadata() { + return metadata; + } + + public void setMetadata(Metadata metadata) { + this.metadata = metadata; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java new file mode 100644 index 000000000..35a5e1bea --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IExtractor.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text; + +import java.util.List; + +public interface IExtractor { + List extract(String input); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java new file mode 100644 index 000000000..cc529689a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IModel.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text; + +import java.util.List; + +public interface IModel { + String getModelTypeName(); + + List parse(String query); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java new file mode 100644 index 000000000..45c7ce76a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/IParser.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text; + +public interface IParser { + ParseResult parse(ExtractResult extractResult); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java new file mode 100644 index 000000000..9e89af535 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Metadata.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text; + +public class Metadata { + // For cases like "from 2014 to 2018", the period end "2018" could be inclusive or exclusive + // For extraction, we only mark this flag to avoid future duplicate judgment, whether to include the period end or not is not determined in the extraction step + private boolean possiblyIncludePeriodEnd = false; + + // For cases like "2015年以前" (usually regards as "before 2015" in English), "5天以前" + // (usually regards as "5 days ago" in English) in Chinese, we need to decide whether this is a "Date with Mode" or "Duration with Before and After". + // We use this flag to avoid duplicate judgment both in the Extraction step and Parse step. + // Currently, this flag is only used in Chinese DateTime as other languages don't have this ambiguity cases. + private boolean isDurationWithBeforeAndAfter = false; + + private boolean isHoliday = false; + + public boolean getIsHoliday() { + return isHoliday; + } + + public void setIsHoliday(boolean isHoliday) { + this.isHoliday = isHoliday; + } + + private boolean hasMod = false; + + public boolean getHasMod() { + return hasMod; + } + + public void setHasMod(boolean hasMod) { + this.hasMod = hasMod; + } + + public boolean getIsPossiblyIncludePeriodEnd() { + return possiblyIncludePeriodEnd; + } + + public void setPossiblyIncludePeriodEnd(boolean possiblyIncludePeriodEnd) { + this.possiblyIncludePeriodEnd = possiblyIncludePeriodEnd; + } + + public boolean getIsDurationWithBeforeAndAfter() { + return isDurationWithBeforeAndAfter; + } + + public void setDurationWithBeforeAndAfter(boolean durationWithBeforeAndAfter) { + isDurationWithBeforeAndAfter = durationWithBeforeAndAfter; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java new file mode 100644 index 000000000..ef2ec6370 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelFactory.java @@ -0,0 +1,81 @@ +package com.microsoft.recognizers.text; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.lang.reflect.Type; +import java.util.HashMap; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +public class ModelFactory extends HashMap, Function> { + + // cacheKey: (string culture, Type modelType, string modelOptions) + private static ConcurrentHashMap, IModel> cache = new ConcurrentHashMap, IModel>(); + + private static final String fallbackCulture = Culture.English; + + public T getModel(Class modelType, String culture, boolean fallbackToDefaultCulture, TModelOptions options) throws IllegalArgumentException { + IModel model = this.getModel(modelType, culture, options); + if (model != null) { + return (T)model; + } + + if (fallbackToDefaultCulture) { + model = this.getModel(modelType, fallbackCulture, options); + if (model != null) { + return (T)model; + } + } + + throw new IllegalArgumentException( + String.format("Could not find Model with the specified configuration: %s, %s", culture, modelType.getTypeName())); + } + + private IModel getModel(Type modelType, String culture, TModelOptions options) { + if (StringUtility.isNullOrEmpty(culture)) { + return null; + } + + // Look in cache + Triplet cacheKey = new Triplet<>(culture.toLowerCase(), modelType, options.toString()); + if (cache.containsKey(cacheKey)) { + return cache.get(cacheKey); + } + + // Use Factory to create instance + Pair key = generateKey(culture, modelType); + if (this.containsKey(key)) { + Function factoryMethod = this.get(key); + IModel model = factoryMethod.apply(options); + + // Store in cache + cache.put(cacheKey, model); + return model; + } + + return null; + } + + public void initializeModels(String targetCulture, TModelOptions options) { + this.keySet().stream() + .filter(key -> StringUtility.isNullOrEmpty(targetCulture) || key.getValue0().equalsIgnoreCase(targetCulture)) + .forEach(key -> this.initializeModel(key.getValue1(), key.getValue0(), options)); + } + + private void initializeModel(Type modelType, String culture, TModelOptions options) { + this.getModel(modelType, culture, options); + } + + @Override + public Function put(Pair config, Function modelCreator) { + return super.put( + generateKey(config.getValue0(), config.getValue1()), + modelCreator); + } + + private static Pair generateKey(String culture, Type modelType) { + return new Pair<>(culture.toLowerCase(), modelType); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java new file mode 100644 index 000000000..69229e121 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ModelResult.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text; + +import java.util.SortedMap; + +public class ModelResult { + + public final String text; + public final int start; + public final int end; + public final String typeName; + public final SortedMap resolution; + + public ModelResult(String text, int start, int end, String typeName, SortedMap resolution) { + this.text = text; + this.start = start; + this.end = end; + this.typeName = typeName; + this.resolution = resolution; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java new file mode 100644 index 000000000..9a7b0a8cb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ParseResult.java @@ -0,0 +1,46 @@ +package com.microsoft.recognizers.text; + +public class ParseResult extends ExtractResult { + + // Value is for resolution. + // e.g. 1000 for "one thousand". + // The resolutions are different for different parsers. + // Therefore, we use object here. + private Object value; + + // Output the value in string format. + // It is used in some parsers. + private String resolutionStr; + + public ParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr) { + super(start, length, text, type, data, null); + this.value = value; + this.resolutionStr = resolutionStr; + } + + public ParseResult(ExtractResult er) { + this(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), null, null); + } + + public ParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, Metadata metadata) { + super(start, length, text, type, data, metadata); + this.value = value; + this.resolutionStr = resolutionStr; + } + + public Object getValue() { + return value; + } + + public void setValue(Object value) { + this.value = value; + } + + public String getResolutionStr() { + return resolutionStr; + } + + public void setResolutionStr(String resolutionStr) { + this.resolutionStr = resolutionStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java new file mode 100644 index 000000000..e0d2253f8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/Recognizer.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text; + +import java.util.function.Function; +import org.javatuples.Pair; + +public abstract class Recognizer> { + + public final String targetCulture; + public final TRecognizerOptions options; + + private final ModelFactory factory; + + protected Recognizer(String targetCulture, TRecognizerOptions options, boolean lazyInitialization) { + this.targetCulture = targetCulture; + this.options = options; + + this.factory = new ModelFactory<>(); + this.initializeConfiguration(); + + if (!lazyInitialization) { + this.initializeModels(targetCulture, options); + } + } + + public T getModel(Class modelType, String culture, boolean fallbackToDefaultCulture) { + return this.factory.getModel( + modelType, + culture != null ? culture : targetCulture, + fallbackToDefaultCulture, + options); + } + + public void registerModel(Class modelType, String culture, Function modelCreator) { + this.factory.put(new Pair<>(culture, modelType), modelCreator); + } + + private void initializeModels(String targetCulture, TRecognizerOptions options) { + this.factory.initializeModels(targetCulture, options); + } + + protected abstract void initializeConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java new file mode 100644 index 000000000..6e14a7ee5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/ResolutionKey.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text; + +public class ResolutionKey { + public static final String ValueSet = "values"; + public static final String Value = "value"; + public static final String Type = "type"; + public static final String Unit = "unit"; + public static final String Score = "score"; + public static final String IsoCurrency = "isoCurrency"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java new file mode 100644 index 000000000..16470e707 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceOptions.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text.choice; + +public enum ChoiceOptions { + None +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java new file mode 100644 index 000000000..782046b0f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/ChoiceRecognizer.java @@ -0,0 +1,74 @@ +package com.microsoft.recognizers.text.choice; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.choice.english.extractors.EnglishBooleanExtractorConfiguration; +import com.microsoft.recognizers.text.choice.extractors.BooleanExtractor; +import com.microsoft.recognizers.text.choice.models.BooleanModel; +import com.microsoft.recognizers.text.choice.parsers.BooleanParser; + +import java.util.List; + +public class ChoiceRecognizer extends Recognizer { + + public ChoiceRecognizer(String targetCulture, ChoiceOptions options, boolean lazyInitialization) { + super(targetCulture, options, lazyInitialization); + } + + public ChoiceRecognizer(String targetCulture, int options, boolean lazyInitialization) { + this(targetCulture, ChoiceOptions.values()[options], lazyInitialization); + } + + public ChoiceRecognizer(int options, boolean lazyInitialization) { + this(null, ChoiceOptions.values()[options], lazyInitialization); + } + + public ChoiceRecognizer(ChoiceOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public ChoiceRecognizer(boolean lazyInitialization) { + this(null, ChoiceOptions.None, lazyInitialization); + } + + public ChoiceRecognizer(int options) { + this(null, ChoiceOptions.values()[options], true); + } + + public ChoiceRecognizer(ChoiceOptions options) { + this(null, options, true); + } + + public ChoiceRecognizer() { + this(null, ChoiceOptions.None, true); + } + + public BooleanModel getBooleanModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(BooleanModel.class, culture, fallbackToDefaultCulture); + } + + public static List recognizeBoolean(String query, String culture, ChoiceOptions options, boolean fallbackToDefaultCulture) { + + ChoiceRecognizer recognizer = new ChoiceRecognizer(options); + IModel model = recognizer.getBooleanModel(culture, fallbackToDefaultCulture); + + return model.parse(query); + } + + public static List recognizeBoolean(String query, String culture, ChoiceOptions options) { + return recognizeBoolean(query, culture, options, true); + } + + public static List recognizeBoolean(String query, String culture) { + return recognizeBoolean(query, culture, ChoiceOptions.None); + } + + @Override + protected void initializeConfiguration() { + + //English + registerModel(BooleanModel.class, Culture.English, (options) -> new BooleanModel(new BooleanParser(), new BooleanExtractor(new EnglishBooleanExtractorConfiguration()))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java new file mode 100644 index 000000000..3db27c8dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/Constants.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.choice; + +public class Constants { + public static final String SYS_BOOLEAN_TRUE = "boolean_true"; + public static final String SYS_BOOLEAN_FALSE = "boolean_false"; + // Model type name + public static final String MODEL_BOOLEAN = "boolean"; +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java new file mode 100644 index 000000000..e19e35402 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/BooleanParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.choice.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.choice.Constants; + +import java.util.Map; + +public class BooleanParserConfiguration implements IChoiceParserConfiguration { + + public static Map Resolutions = ImmutableMap.builder() + .put(Constants.SYS_BOOLEAN_TRUE, true) + .put(Constants.SYS_BOOLEAN_FALSE, false) + .build(); + + @Override + public Map getResolutions() { + return Resolutions; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java new file mode 100644 index 000000000..d56112251 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/config/IChoiceParserConfiguration.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.choice.config; + +import java.util.Map; + +public interface IChoiceParserConfiguration { + public Map getResolutions(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java new file mode 100644 index 000000000..8b2c4ab65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/english/extractors/EnglishBooleanExtractorConfiguration.java @@ -0,0 +1,70 @@ +package com.microsoft.recognizers.text.choice.english.extractors; + +import com.microsoft.recognizers.text.choice.Constants; +import com.microsoft.recognizers.text.choice.extractors.IBooleanExtractorConfiguration; +import com.microsoft.recognizers.text.choice.resources.EnglishChoice; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class EnglishBooleanExtractorConfiguration implements IBooleanExtractorConfiguration { + public static final Pattern trueRegex = RegExpUtility.getSafeRegExp(EnglishChoice.TrueRegex); + public static final Pattern falseRegex = RegExpUtility.getSafeRegExp(EnglishChoice.FalseRegex); + public static final Pattern tokenRegex = RegExpUtility.getSafeRegExp(EnglishChoice.TokenizerRegex); + public static final Map mapRegexes; + + static { + mapRegexes = new HashMap(); + mapRegexes.put(trueRegex, Constants.SYS_BOOLEAN_TRUE); + mapRegexes.put(falseRegex, Constants.SYS_BOOLEAN_FALSE); + } + + public boolean allowPartialMatch = false; + public int maxDistance = 2; + public boolean onlyTopMatch; + + public EnglishBooleanExtractorConfiguration(boolean topMatch) { + onlyTopMatch = topMatch; + } + + public EnglishBooleanExtractorConfiguration() { + this(true); + } + + @Override + public Map getMapRegexes() { + return mapRegexes; + } + + @Override + public Pattern getTrueRegex() { + return trueRegex; + } + + @Override + public Pattern getFalseRegex() { + return falseRegex; + } + + @Override + public Pattern getTokenRegex() { + return tokenRegex; + } + + @Override + public boolean getAllowPartialMatch() { + return allowPartialMatch; + } + + @Override + public int getMaxDistance() { + return maxDistance; + } + + @Override + public boolean getOnlyTopMatch() { + return onlyTopMatch; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java new file mode 100644 index 000000000..6f9b8036f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/BooleanExtractor.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.extractors; + +public class BooleanExtractor extends ChoiceExtractor { + + public BooleanExtractor(IBooleanExtractorConfiguration config) { + + super(config); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java new file mode 100644 index 000000000..7fd89481a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractDataResult.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.ArrayList; +import java.util.List; + +public class ChoiceExtractDataResult { + + public final List otherMatches; + public final String source; + public final double score; + + public ChoiceExtractDataResult(String extractDataSource, double extractDataScore, List extractDataOtherMatches) { + otherMatches = extractDataOtherMatches; + source = extractDataSource; + score = extractDataScore; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java new file mode 100644 index 000000000..55f351bb9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/ChoiceExtractor.java @@ -0,0 +1,170 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.choice.utilities.UnicodeUtils; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class ChoiceExtractor implements IExtractor { + + private IChoiceExtractorConfiguration config; + + public ChoiceExtractor(IChoiceExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String text) { + + List results = new ArrayList<>(); + String trimmedText = text.toLowerCase(); + List partialResults = new ArrayList<>(); + List sourceTokens = tokenize(trimmedText); + + if (text.isEmpty()) { + return results; + } + + for (Map.Entry entry : this.config.getMapRegexes().entrySet()) { + + Pattern regexKey = entry.getKey(); + String constantValue = entry.getValue(); + Match[] matches = RegExpUtility.getMatches(regexKey, trimmedText); + double topScore = 0; + + for (Match match : matches) { + + List matchToken = tokenize(match.value); + for (int i = 0; i < sourceTokens.size(); i++) { + double score = matchValue(sourceTokens, matchToken, i); + topScore = Math.max(topScore, score); + } + + if (topScore > 0.0) { + int start = match.index; + int length = match.length; + partialResults.add( + new ExtractResult( + start, + length, + text.substring(start, length + start), + constantValue, + new ChoiceExtractDataResult(text, topScore, new ArrayList<>()) + ) + ); + } + + } + } + + if (partialResults.size() == 0) { + return results; + } + + partialResults.sort(Comparator.comparingInt(er -> er.getStart())); + + if (this.config.getOnlyTopMatch()) { + + double topScore = 0; + int topResultIndex = 0; + + for (int i = 0; i < partialResults.size(); i++) { + + ChoiceExtractDataResult data = (ChoiceExtractDataResult)partialResults.get(i).getData(); + if (data.score > topScore) { + topScore = data.score; + topResultIndex = i; + } + + } + results.add(partialResults.get(topResultIndex)); + partialResults.remove(topResultIndex); + } else { + results = partialResults; + } + + return results; + } + + private final double matchValue(List source, List match, int startPosition) { + + double matched = 0; + double totalDeviation = 0; + double score = 0; + + for (String token : match) { + int pos = indexOfToken(source, token, startPosition); + if (pos >= 0) { + int distance = matched > 0 ? pos - startPosition : 0; + if (distance <= config.getMaxDistance()) { + matched++; + totalDeviation += distance; + startPosition = pos + 1; + } + } + } + + if (matched > 0 && (matched == match.size() || config.getAllowPartialMatch())) { + double completeness = matched / match.size(); + double accuracy = completeness * (matched / (matched + totalDeviation)); + double initialScore = accuracy * (matched / source.size()); + score = 0.4 + (0.6 * initialScore); + } + + return score; + } + + private static int indexOfToken(List tokens, String token, int startPos) { + + if (tokens.size() <= startPos) { + return -1; + } + + return tokens.indexOf(token); + } + + private final List tokenize(String text) { + + List tokens = new ArrayList<>(); + List letters = UnicodeUtils.letters(text); + String token = ""; + + for (String letter : letters) { + + Optional isMatch = Arrays.stream(RegExpUtility.getMatches(this.config.getTokenRegex(), letter)).findFirst(); + if (UnicodeUtils.isEmoji(letter)) { + + // Character is in a Supplementary Unicode Plane. This is where emoji live so + // we're going to just break each character in this range out as its own token. + tokens.add(letter); + if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + + } else if (!(isMatch.isPresent() || StringUtility.isNullOrWhiteSpace(letter))) { + token = token + letter; + } else if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + } + + if (!StringUtility.isNullOrWhiteSpace(token)) { + tokens.add(token); + token = ""; + } + + return tokens; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java new file mode 100644 index 000000000..fbcbe8e9a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IBooleanExtractorConfiguration.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import java.util.regex.Pattern; + +public interface IBooleanExtractorConfiguration extends IChoiceExtractorConfiguration { + public Pattern getTrueRegex(); + + public Pattern getFalseRegex(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java new file mode 100644 index 000000000..609a3b0d4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/extractors/IChoiceExtractorConfiguration.java @@ -0,0 +1,16 @@ +package com.microsoft.recognizers.text.choice.extractors; + +import java.util.Map; +import java.util.regex.Pattern; + +public interface IChoiceExtractorConfiguration { + public Map getMapRegexes(); + + public Pattern getTokenRegex(); + + public boolean getAllowPartialMatch(); + + public int getMaxDistance(); + + public boolean getOnlyTopMatch(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java new file mode 100644 index 000000000..9c696f446 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/BooleanModel.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.Constants; +import com.microsoft.recognizers.text.choice.parsers.OptionsOtherMatchParseResult; +import com.microsoft.recognizers.text.choice.parsers.OptionsParseDataResult; + +import java.util.SortedMap; +import java.util.TreeMap; + +public class BooleanModel extends ChoiceModel { + + public BooleanModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + public String getModelTypeName() { + return Constants.MODEL_BOOLEAN; + } + + @Override + protected SortedMap getResolution(ParseResult parseResult) { + + OptionsParseDataResult parseResultData = (OptionsParseDataResult)parseResult.getData(); + SortedMap results = new TreeMap(); + SortedMap otherMatchesMap = new TreeMap(); + + results.put("value", parseResult.getValue()); + results.put("score", parseResultData.score); + + for (OptionsOtherMatchParseResult otherMatchParseRes : parseResultData.otherMatches) { + otherMatchesMap.put("text", otherMatchParseRes.text); + otherMatchesMap.put("value", otherMatchParseRes.value); + otherMatchesMap.put("score", otherMatchParseRes.score); + } + + results.put("otherResults", otherMatchesMap); + + return results; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java new file mode 100644 index 000000000..516bb121c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/models/ChoiceModel.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.Constants; + +import java.util.List; +import java.util.SortedMap; +import java.util.stream.Collectors; + +public abstract class ChoiceModel implements IModel { + protected IExtractor extractor; + protected IParser parser; + + public ChoiceModel(IParser choiceParser, IExtractor choiceExtractor) { + parser = choiceParser; + extractor = choiceExtractor; + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_BOOLEAN; + } + + @Override + public List parse(String query) { + + List extractResults = extractor.extract(query); + List parseResults = extractResults.stream().map(exRes -> parser.parse(exRes)).collect(Collectors.toList()); + + List modelResults = parseResults.stream().map( + parseRes -> new ModelResult(parseRes.getText(), parseRes.getStart(), parseRes.getStart() + parseRes.getLength() - 1, getModelTypeName(), getResolution(parseRes)) + ).collect(Collectors.toList()); + + return modelResults; + } + + protected abstract SortedMap getResolution(ParseResult parseResult); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java new file mode 100644 index 000000000..f0ce82382 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/BooleanParser.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import com.microsoft.recognizers.text.choice.config.BooleanParserConfiguration; + +public class BooleanParser extends ChoiceParser { + public BooleanParser() { + super(new BooleanParserConfiguration()); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java new file mode 100644 index 000000000..91316e36a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/ChoiceParser.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.choice.config.IChoiceParserConfiguration; +import com.microsoft.recognizers.text.choice.extractors.ChoiceExtractDataResult; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class ChoiceParser implements IParser { + + private IChoiceParserConfiguration config; + + public ChoiceParser(IChoiceParserConfiguration config) { + this.config = config; + } + + public ParseResult parse(ExtractResult extractResult) { + + ParseResult parseResult = new ParseResult(extractResult); + ChoiceExtractDataResult data = (ChoiceExtractDataResult)extractResult.getData(); + Map resolutions = this.config.getResolutions(); + List matches = data.otherMatches.stream().map(match -> getOptionsOtherMatchResult(match)).collect(Collectors.toList()); + + parseResult.setData(new OptionsParseDataResult(data.score, matches)); + parseResult.setValue(resolutions.getOrDefault(parseResult.getType(), false)); + + return parseResult; + } + + private OptionsOtherMatchParseResult getOptionsOtherMatchResult(ExtractResult extractResult) { + + ParseResult parseResult = new ParseResult(extractResult); + ChoiceExtractDataResult data = (ChoiceExtractDataResult)extractResult.getData(); + Map resolutions = this.config.getResolutions(); + OptionsOtherMatchParseResult result = new OptionsOtherMatchParseResult(parseResult.getText(), resolutions.get(parseResult.getType()), data.score); + + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java new file mode 100644 index 000000000..dd66f24b8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsOtherMatchParseResult.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.choice.parsers; + +public class OptionsOtherMatchParseResult { + + public final double score; + public final String text; + public final Object value; + + public OptionsOtherMatchParseResult(String parseResultText, Object parseResultValue, double parseResultScore) { + score = parseResultScore; + text = parseResultText; + value = parseResultValue; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java new file mode 100644 index 000000000..bd08beaa8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/parsers/OptionsParseDataResult.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.choice.parsers; + +import java.util.ArrayList; +import java.util.List; + +public class OptionsParseDataResult { + + public final double score; + public final List otherMatches; + + public OptionsParseDataResult(double optionScore, List optionOtherMatches) { + score = optionScore; + otherMatches = optionOtherMatches; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java new file mode 100644 index 000000000..19fa19526 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class ChineseChoice { + + public static final String LangMarker = "Chs"; + + public static final String TokenizerRegex = "[^\\u3040-\\u30ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\uff66-\\uff9f]"; + + public static final String TrueRegex = "(好[的啊呀嘞哇]|没问题|可以|中|好|同意|行|是的|是|对)|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "(不行|不好|拒绝|否定|不中|不可以|不是的|不是|不对|不)|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java new file mode 100644 index 000000000..440e5a162 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class EnglishChoice { + + public static final String LangMarker = "Eng"; + + public static final String TokenizerRegex = "[^\\w\\d]"; + + public static final String TrueRegex = "\\b(true|yes|yep|yup|yeah|y|sure|ok|agree)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C|\\u0001f44c)"; + + public static final String FalseRegex = "\\b(false|nope|nop|no|not\\s+ok|disagree)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90|\\u0001F44E|\\u0001F590)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java new file mode 100644 index 000000000..7dcff1c3a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class FrenchChoice { + + public static final String LangMarker = "Fr"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(s[uû]r|ouais|oui|yep|y|sure|approuver|accepter|consentir|d'accord|ça march[eé])\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(faux|nan|non|pas\\s+d'accord|pas\\s+concorder|n'est\\s+pas\\s+(correct|ok)|pas)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java new file mode 100644 index 000000000..7c6b48ef0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class PortugueseChoice { + + public static final String LangMarker = "Por"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(verdade|verdadeir[oa]|sim|isso|claro|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(falso|n[aã]o|incorreto|nada disso)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java new file mode 100644 index 000000000..d3cd40413 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.choice.resources; + +public class SpanishChoice { + + public static final String LangMarker = "Spa"; + + public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; + + public static final String TrueRegex = "\\b(verdad|verdadero|sí|sip|s|si|cierto|por supuesto|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + + public static final String FalseRegex = "\\b(falso|no|nop|n|no)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java new file mode 100644 index 000000000..fe7a281a4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/utilities/UnicodeUtils.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.choice.utilities; + +import java.lang.Character; +import java.util.ArrayList; +import java.util.List; + +public class UnicodeUtils { + public static boolean isEmoji(String letter) { + final int WhereEmojiLive = 0xFFFF; // Supplementary Unicode Plane. This is where emoji live + return Character.isHighSurrogate(letter.charAt(0)) && (letter.codePoints().sum() > WhereEmojiLive); + } + + public static List letters(String text) { + char codePoint = 0; + List result = new ArrayList<>(); + for (int i = 0; i < text.length(); i++) { + char c = text.charAt(i); + if (codePoint != 0) { + result.add(new String(Character.toChars(codePoint + c))); + codePoint = 0; + } else if (!Character.isHighSurrogate(c)) { + result.add(Character.toString(c)); + } else { + codePoint = c; + } + } + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java new file mode 100644 index 000000000..5046497d0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/Constants.java @@ -0,0 +1,182 @@ +package com.microsoft.recognizers.text.datetime; + +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; + +public class Constants { + + public static final String SYS_DATETIME_DATE = "date"; + public static final String SYS_DATETIME_TIME = "time"; + public static final String SYS_DATETIME_DATEPERIOD = "daterange"; + public static final String SYS_DATETIME_DATETIME = "datetime"; + public static final String SYS_DATETIME_TIMEPERIOD = "timerange"; + public static final String SYS_DATETIME_DATETIMEPERIOD = "datetimerange"; + public static final String SYS_DATETIME_DURATION = "duration"; + public static final String SYS_DATETIME_SET = "set"; + public static final String SYS_DATETIME_DATETIMEALT = "datetimealt"; + public static final String SYS_DATETIME_TIMEZONE = "timezone"; + + // SourceEntity Types + public static final String SYS_DATETIME_DATETIMEPOINT = "datetimepoint"; + + // Model Name + public static final String MODEL_DATETIME = "datetime"; + + // Multiple Duration Types + public static final String MultipleDuration_Prefix = "multipleDuration"; + public static final String MultipleDuration_Type = MultipleDuration_Prefix + "Type"; + public static final String MultipleDuration_DateTime = MultipleDuration_Prefix + "DateTime"; + public static final String MultipleDuration_Date = MultipleDuration_Prefix + "Date"; + public static final String MultipleDuration_Time = MultipleDuration_Prefix + "Time"; + + // DateTime Parse + public static final String Resolve = "resolve"; + public static final String ResolveToPast = "resolveToPast"; + public static final String ResolveToFuture = "resolveToFuture"; + public static final String FutureDate = "futureDate"; + public static final String PastDate = "pastDate"; + public static final String ParseResult1 = "parseResult1"; + public static final String ParseResult2 = "parseResult2"; + + // In the ExtractResult data + public static final String Context = "context"; + public static final String ContextType_RelativePrefix = "relativePrefix"; + public static final String ContextType_RelativeSuffix = "relativeSuffix"; + public static final String ContextType_AmPm = "AmPm"; + public static final String SubType = "subType"; + + // Comment - internal tag used during entity processing, never exposed to users. + // Tags are filtered out in BaseMergedDateTimeParser DateTimeResolution() + public static final String Comment = "Comment"; + // AmPm time representation for time parser + public static final String Comment_AmPm = "ampm"; + // Prefix early/late for time parser + public static final String Comment_Early = "early"; + public static final String Comment_Late = "late"; + // Parse week of date format + public static final String Comment_WeekOf = "WeekOf"; + public static final String Comment_MonthOf = "MonthOf"; + + public static final String Comment_DoubleTimex = "doubleTimex"; + + public static final String InvalidDateString = "0001-01-01"; + public static final String CompositeTimexDelimiter = "|"; + public static final String CompositeTimexSplit = "\\|"; + + // Mod Value + // "before" -> To mean "preceding in time". I.e. Does not include the extracted datetime entity in the resolution's ending point. Equivalent to "<" + public static final String BEFORE_MOD = "before"; + + // "after" -> To mean "following in time". I.e. Does not include the extracted datetime entity in the resolution's starting point. Equivalent to ">" + public static final String AFTER_MOD = "after"; + + // "since" -> Same as "after", but including the extracted datetime entity. Equivalent to ">=" + public static final String SINCE_MOD = "since"; + + // "until" -> Same as "before", but including the extracted datetime entity. Equivalent to "<=" + public static final String UNTIL_MOD = "until"; + + public static final String EARLY_MOD = "start"; + public static final String MID_MOD = "mid"; + public static final String LATE_MOD = "end"; + + public static final String MORE_THAN_MOD = "more"; + public static final String LESS_THAN_MOD = "less"; + + public static final String REF_UNDEF_MOD = "ref_undef"; + + public static final String APPROX_MOD = "approx"; + + // Invalid year + public static final int InvalidYear = Integer.MIN_VALUE; + public static final int InvalidMonth = Integer.MIN_VALUE; + public static final int InvalidDay = Integer.MIN_VALUE; + public static final int InvalidHour = Integer.MIN_VALUE; + public static final int InvalidMinute = Integer.MIN_VALUE; + public static final int InvalidSecond = Integer.MIN_VALUE; + + public static final int MinYearNum = BaseDateTime.MinYearNum; + public static final int MaxYearNum = BaseDateTime.MaxYearNum; + + public static final int MaxTwoDigitYearFutureNum = BaseDateTime.MaxTwoDigitYearFutureNum; + public static final int MinTwoDigitYearPastNum = BaseDateTime.MinTwoDigitYearPastNum; + + // These are some particular values for timezone recognition + public static final int InvalidOffsetValue = -10000; + public static final String UtcOffsetMinsKey = "utcOffsetMins"; + public static final String TimeZoneText = "timezoneText"; + public static final String TimeZone = "timezone"; + public static final String ResolveTimeZone = "resolveTimeZone"; + public static final int PositiveSign = 1; + public static final int NegativeSign = -1; + + public static final int TrimesterMonthCount = 3; + public static final int QuarterCount = 4; + public static final int SemesterMonthCount = 6; + public static final int WeekDayCount = 7; + public static final int CenturyYearsCount = 100; + public static final int MaxWeekOfMonth = 5; + + // hours of one half day + public static final int HalfDayHourCount = 12; + // hours of a half mid-day-duration + public static final int HalfMidDayDurationHourCount = 2; + + // the length of four digits year, e.g., 2018 + public static final int FourDigitsYearLength = 4; + + // specifies the priority interpreting month and day order + public static final String DefaultLanguageFallback_MDY = "MDY"; + public static final String DefaultLanguageFallback_DMY = "DMY"; + public static final String DefaultLanguageFallback_YMD = "YMD"; // ZH + + // Groups' names for named groups in regexes + public static final String NextGroupName = "next"; + public static final String AmGroupName = "am"; + public static final String PmGroupName = "pm"; + public static final String ImplicitAmGroupName = "iam"; + public static final String ImplicitPmGroupName = "ipm"; + public static final String PrefixGroupName = "prefix"; + public static final String SuffixGroupName = "suffix"; + public static final String DescGroupName = "desc"; + public static final String SecondGroupName = "sec"; + public static final String MinuteGroupName = "min"; + public static final String HourGroupName = "hour"; + public static final String TimeOfDayGroupName = "timeOfDay"; + public static final String BusinessDayGroupName = "business"; + public static final String LeftAmPmGroupName = "leftDesc"; + public static final String RightAmPmGroupName = "rightDesc"; + + public static final String DECADE_UNIT = "10Y"; + public static final String FORTNIGHT_UNIT = "2W"; + + // Timex + public static final String[] DatePeriodTimexSplitter = { ",", "(", ")" }; + public static final String TimexYear = "Y"; + public static final String TimexMonth = "M"; + public static final String TimexMonthFull = "MON"; + public static final String TimexWeek = "W"; + public static final String TimexDay = "D"; + public static final String TimexBusinessDay = "BD"; + public static final String TimexWeekend = "WE"; + public static final String TimexHour = "H"; + public static final String TimexMinute = "M"; + public static final String TimexSecond = "S"; + public static final char TimexFuzzy = 'X'; + public static final String TimexFuzzyYear = "XXXX"; + public static final String TimexFuzzyMonth = "XX"; + public static final String TimexFuzzyWeek = "WXX"; + public static final String TimexFuzzyDay = "XX"; + public static final String DateTimexConnector = "-"; + public static final String TimeTimexConnector = ":"; + public static final String GeneralPeriodPrefix = "P"; + public static final String TimeTimexPrefix = "T"; + + // Timex of TimeOfDay + public static final String EarlyMorning = "TDA"; + public static final String Morning = "TMO"; + public static final String Afternoon = "TAF"; + public static final String Evening = "TEV"; + public static final String Daytime = "TDT"; + public static final String Night = "TNI"; + public static final String BusinessHour = "TBH"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java new file mode 100644 index 000000000..853934357 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DatePeriodTimexType.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.datetime; + +public enum DatePeriodTimexType { + ByDay, + ByWeek, + ByMonth, + ByYear +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java new file mode 100644 index 000000000..5519bd005 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeOptions.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime; + +public enum DateTimeOptions { + None(0), + SkipFromToMerge(1), + SplitDateAndTime(2), + CalendarMode(4), + ExtendedTypes(8), + EnablePreview(8388608), + ExperimentalMode(4194304), + ComplexCalendar(8 + 4 + 8388608); + + private final int value; + + DateTimeOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public boolean match(DateTimeOptions option) { + return (this.value & option.value) == option.value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java new file mode 100644 index 000000000..9d4c0adf1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeRecognizer.java @@ -0,0 +1,93 @@ +package com.microsoft.recognizers.text.datetime; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.parsers.EnglishMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseMergedDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.parsers.FrenchMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.models.DateTimeModel; +import com.microsoft.recognizers.text.datetime.parsers.BaseMergedDateTimeParser; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.parsers.SpanishMergedParserConfiguration; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.function.Function; + +public class DateTimeRecognizer extends Recognizer { + + public DateTimeRecognizer() { + this(null, DateTimeOptions.None, true); + } + + public DateTimeRecognizer(String culture) { + this(culture, DateTimeOptions.None, false); + } + + public DateTimeRecognizer(DateTimeOptions options) { + this(null, options, true); + } + + public DateTimeRecognizer(DateTimeOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public DateTimeRecognizer(String culture, DateTimeOptions options, boolean lazyInitialization) { + super(culture, options, lazyInitialization); + } + + public DateTimeModel getDateTimeModel() { + return getDateTimeModel(null, true); + } + + public DateTimeModel getDateTimeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(DateTimeModel.class, culture, fallbackToDefaultCulture); + } + + //region Helper methods for less verbosity + public static List recognizeDateTime(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, true), query, DateTimeOptions.None, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, true), query, options, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, fallbackToDefaultCulture), query, options, LocalDateTime.now()); + } + + public static List recognizeDateTime(String query, String culture, DateTimeOptions options, boolean fallbackToDefaultCulture, LocalDateTime reference) { + return recognizeByModel(recognizer -> recognizer.getDateTimeModel(culture, fallbackToDefaultCulture), query, options, reference); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, DateTimeOptions options, LocalDateTime reference) { + DateTimeRecognizer recognizer = new DateTimeRecognizer(options); + DateTimeModel model = getModelFun.apply(recognizer); + return model.parse(query, reference); + } + + @Override + protected void initializeConfiguration() { + // English + registerModel(DateTimeModel.class, Culture.English, + (options) -> new DateTimeModel( + new BaseMergedDateTimeParser(new EnglishMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new EnglishMergedExtractorConfiguration(options)))); + + // Spanish + registerModel(DateTimeModel.class, Culture.Spanish, + (options) -> new DateTimeModel( + new BaseMergedDateTimeParser(new SpanishMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new SpanishMergedExtractorConfiguration(options)))); + + registerModel(DateTimeModel.class, Culture.French, dateTimeOptions -> new DateTimeModel( + new BaseMergedDateTimeParser(new FrenchMergedParserConfiguration(options)), + new BaseMergedDateTimeExtractor(new FrenchMergedExtractorConfiguration(options)) + )); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java new file mode 100644 index 000000000..e29f7c08f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/DateTimeResolutionKey.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.datetime; + +public class DateTimeResolutionKey { + public static final String Timex = "timex"; + public static final String Mod = "Mod"; + public static final String IsLunar = "isLunar"; + public static final String START = "start"; + public static final String END = "end"; + public static final String List = "list"; + public static final String SourceEntity = "sourceEntity"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java new file mode 100644 index 000000000..914b8b3a7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/TimeTypeConstants.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.datetime; + +public class TimeTypeConstants { + public static final String DATE = "date"; + public static final String DATETIME = "dateTime"; + public static final String DATETIMEALT = "dateTimeAlt"; + public static final String DURATION = "duration"; + public static final String SET = "set"; + public static final String TIME = "time"; + + // Internal SubType for Future/Past in DateTimeResolutionResult + public static final String START_DATE = "startDate"; + public static final String END_DATE = "endDate"; + public static final String START_DATETIME = "startDateTime"; + public static final String END_DATETIME = "endDateTime"; + public static final String START_TIME = "startTime"; + public static final String END_TIME = "endTime"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java new file mode 100644 index 000000000..36fb5ac88 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/BaseOptionsConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.config; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; + +public class BaseOptionsConfiguration implements IOptionsConfiguration { + private final DateTimeOptions options; + private final boolean dmyDateFormat; + + public BaseOptionsConfiguration() { + this(DateTimeOptions.None, false); + } + + public BaseOptionsConfiguration(DateTimeOptions options) { + this(options, false); + } + + public BaseOptionsConfiguration(DateTimeOptions options, boolean dmyDateFormat) { + this.options = options; + this.dmyDateFormat = dmyDateFormat; + } + + @Override + public DateTimeOptions getOptions() { + return options; + } + + @Override + public boolean getDmyDateFormat() { + return dmyDateFormat; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java new file mode 100644 index 000000000..45d3f3f0c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/config/IOptionsConfiguration.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text.datetime.config; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; + +public interface IOptionsConfiguration { + DateTimeOptions getOptions(); + + boolean getDmyDateFormat(); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java new file mode 100644 index 000000000..588917bf8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateExtractorConfiguration.java @@ -0,0 +1,241 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class EnglishDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ImplicitDayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayRegex); + public static final Pattern SingleWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SingleWeekDayRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextDateRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDayRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayOfMonthRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeWeekDayRegex); + public static final Pattern SpecialDate = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDate); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecialDayWithNumRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrefixArticleRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility.getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + + public static final List DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor3)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor4)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor5)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor6)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor7L)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor7S)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor8)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor9L)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractor9S)); + add(RegExpUtility.getSafeRegExp(EnglishDateTime.DateExtractorA)); + } + }; + + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(SingleWeekDayRegex); + add(WeekDayOfMonthRegex); + add(SpecialDate); + add(SpecialDayWithNumRegex); + add(RelativeWeekDayRegex); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(EnglishDateTime.OfMonth); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthEnd); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(EnglishDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + + public static final ImmutableMap DayOfWeek = EnglishDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = EnglishDateTime.MonthOfYear; + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final List implicitDateList; + + public EnglishDateExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + + implicitDateList = new ArrayList<>(ImplicitDateList); + if (this.getOptions().match(DateTimeOptions.CalendarMode)) { + implicitDateList.add(DayRegex); + } + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return implicitDateList; + } + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..2e2019b79 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDatePeriodExtractorConfiguration.java @@ -0,0 +1,311 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TillRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberCombinedWithDateUnit); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.FutureSuffixRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearPeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDecadeRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ComplexDatePeriodRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.CenturySuffixRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NowRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleCasesRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYear = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthWithYear); + public static final Pattern MonthNumWithYear = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthNumWithYear); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekOfYearRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthFrontBetweenRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WhichWeekRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RestOfDateRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterEarlyPeriodRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DecadeWithCenturyRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(BetweenRegex); + add(OneWordPeriodRegex); + add(MonthWithYear); + add(MonthNumWithYear); + add(YearRegex); + add(WeekOfMonthRegex); + add(WeekOfYearRegex); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(AllHalfYearRegex); + add(SeasonRegex); + add(WhichWeekRegex); + add(RestOfDateRegex); + add(LaterEarlyPeriodRegex); + add(WeekWithWeekDayRangeRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + add(ReferenceDatePeriodRegex); + } + }; + + public static final Pattern rangeConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeConnectorRegex); + private final String[] durationDateRestrictions = EnglishDateTime.DurationDateRestrictions.toArray(new String[0]); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + + public EnglishDatePeriodExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PreviousPrefixRegex; + } + + @Override + public Pattern getFutureRegex() { + return NextPrefixRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("from")) { + result = true; + index = text.lastIndexOf("from"); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("between")) { + result = true; + index = text.lastIndexOf("between"); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + return RegexExtension.isExactMatch(rangeConnectorRegex, text, true); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..75c75d11e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeAltExtractorConfiguration.java @@ -0,0 +1,90 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DayRegex); + private static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangePrefixRegex); + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public EnglishDateTimeAltExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..317ade20d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimeExtractorConfiguration.java @@ -0,0 +1,160 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class EnglishDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificEndOfRegex); + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAfterRegex); + + public IExtractor integerExtractor; + public IDateTimeExtractor datePointExtractor; + public IDateTimeExtractor timePointExtractor; + public IDateTimeExtractor durationExtractor; + public IDateTimeUtilityConfiguration utilityConfiguration; + + public EnglishDateTimeExtractorConfiguration(DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + } + + public EnglishDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..6ddc07c71 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,281 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodExtractorConfiguration { + + public static final Iterable SimpleCases = new ArrayList() { + { + add(EnglishTimePeriodExtractorConfiguration.PureNumFromTo); + add(EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd); + } + }; + + public static final Pattern PeriodTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodTimeOfDayRegex); + public static final Pattern PeriodSpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodSpecificTimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeFollowedUnit); + public static final Pattern TimeNumberCombinedWithUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeNumberCombinedWithUnit); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeTimeUnitRegex); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RestOfDateTimeRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterRegex); + + private final String tokenBeforeDate; + + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + private final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WeekDayRegex); + private final Pattern rangeConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeConnectorRegex); + + public EnglishDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishDateTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + //TODO add english implementations + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + singleDateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return EnglishTimePeriodExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return EnglishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return PeriodSpecificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return PeriodTimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return TimeNumberCombinedWithUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + // TODO: these three methods are the same in DatePeriod, should be abstracted + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("from")) { + result = true; + index = text.lastIndexOf("from"); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + if (text.endsWith("between")) { + result = true; + index = text.lastIndexOf("between"); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + return RegexExtension.isExactMatch(rangeConnectorRegex, text, true); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java new file mode 100644 index 000000000..773a600ef --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishDurationExtractorConfiguration.java @@ -0,0 +1,138 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationUnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAndRegex); + public static final Pattern DurationFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationFollowedUnit); + public static final Pattern NumberCombinedWithDurationUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberCombinedWithDurationUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public EnglishDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishDurationExtractorConfiguration(DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = EnglishDateTime.UnitMap; + unitValueMap = EnglishDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return DurationFollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithDurationUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java new file mode 100644 index 000000000..7843fda3a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishHolidayExtractorConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern YearPattern = RegExpUtility.getSafeRegExp(EnglishDateTime.YearRegex); + + public static final Pattern H = RegExpUtility.getSafeRegExp(EnglishDateTime.HolidayRegex); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H); + } + }; + + public EnglishHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java new file mode 100644 index 000000000..dd083d202 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishMergedExtractorConfiguration.java @@ -0,0 +1,220 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +import org.javatuples.Pair; + +public class EnglishMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AroundRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.BeforeRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.FromToRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SuffixAfterRegex); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(EnglishDateTime.NumberEndingPattern); + public static final Pattern PrepositionSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility.getSafeRegExp(EnglishDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SingleAmbiguousMonthRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificDatePeriodRegex); + private final Iterable> ambiguityFiltersDict; + + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + private static final Iterable filterWordRegexList = new ArrayList() { + { + // one on one + add(RegExpUtility.getSafeRegExp(EnglishDateTime.OneOnOneRegex)); + + // (the)? (day|week|month|year) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.SingleAmbiguousTermsRegex)); + } + }; + + public final Iterable getFilterWordRegexList() { + return filterWordRegexList; + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + private IDateTimeExtractor setExtractor; + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeExtractor holidayExtractor; + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeZoneExtractor timeZoneExtractor; + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + private IDateTimeListExtractor dateTimeAltExtractor; + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public EnglishMergedExtractorConfiguration(DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new EnglishSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new EnglishHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new EnglishDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + integerExtractor = IntegerExtractor.getInstance(); + + ambiguityFiltersDict = EnglishDateTime.AmbiguityFiltersDict.entrySet().stream().map(pair -> { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + return new Pair(key, val); + }).collect(Collectors.toList()); + + if (!this.getOptions().match(DateTimeOptions.EnablePreview)) { + getSuperfluousWordMatcher().init(EnglishDateTime.SuperfluousWordList); + } + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return AmbiguousRangeModifierPrefix; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java new file mode 100644 index 000000000..6879dc559 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishSetExtractorConfiguration.java @@ -0,0 +1,120 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern SetLastRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetLastRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetEachRegex); + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachUnitRegex); + public static final Pattern SetUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DurationUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EachPrefixRegex); + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SetWeekDayRegex); + + public EnglishSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishSetExtractorConfiguration(DateTimeOptions options) { + super(options); + + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return SetLastRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return null; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java new file mode 100644 index 000000000..d4d1c4a61 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeExtractorConfiguration.java @@ -0,0 +1,140 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishTimeExtractorConfiguration extends BaseOptionsConfiguration implements ITimeExtractorConfiguration { + + // part 1: smallest component + // -------------------------------------- + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MinuteNumRegex); + + // part 2: middle level component + // -------------------------------------- + // handle "... o'clock" + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.OclockRegex); + + // handle "... afternoon" + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + + // handle "... in the morning" + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + + // handle "half past ..." "a quarter to ..." + // rename 'min' group to 'deltamin' + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(EnglishDateTime.LessThanOneHour); + + // handle "six thirty", "six twenty one" + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(EnglishDateTime.BasicTime); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(EnglishDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeSuffix); + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WrittenTimeRegex); + + // handle special time such as 'at midnight', 'midnight', 'midday' + public static final Pattern MiddayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MiddayRegex); + public static final Pattern MidTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidTimeRegex); + public static final Pattern MidnightRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidnightRegex); + public static final Pattern MidmorningRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidmorningRegex); + public static final Pattern MidafternoonRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MidafternoonRegex); + + // part 3: regex for time + // -------------------------------------- + // handle "at four" "at 3" + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AtRegex); + public static final Pattern IshRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.IshRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeBeforeAfterRegex); + + public static final Iterable TimeRegexList = new ArrayList() { + { + // (three min past)? seven|7|(senven thirty) pm + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex1)); + + // (three min past)? 3:00(:00)? (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex2)); + + // (three min past)? 3.00 (pm) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex3)); + + // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex4)); + + // (three min past) (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex5)); + + // (five thirty|seven|7|7:00(:00)?) (pm)? (in the night) + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex6)); + + // (in the night) at (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex7)); + + // (in the night) (five thirty|seven|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex8)); + + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex9)); + + // (three min past)? 3h00 (pm)? + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex10)); + + // at 2.30, "at" prefix is required here + // 3.30pm, "am/pm" suffix is required here + add(RegExpUtility.getSafeRegExp(EnglishDateTime.TimeRegex11)); + + // 340pm + add(ConnectNumRegex); + } + }; + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getIshRegex() { + return IshRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeZoneExtractor; + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public EnglishTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + //C# TO JAVA CONVERTER NOTE: Java does not support optional parameters. Overloaded method(s) are created above: + //ORIGINAL LINE: public EnglishTimeExtractorConfiguration(DateTimeOptions options = DateTimeOptions.None) + public EnglishTimeExtractorConfiguration(DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..bdf1e1e50 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimePeriodExtractorConfiguration.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class EnglishTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + private String tokenBeforeDate; + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmRegex); + public static final Pattern HourRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.HourRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TillRegex); + public static final Pattern PeriodDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DescRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(EnglishDateTime.PureNumFromTo); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeOfDayRegex); + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PrepositionRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeFollowedUnit); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(EnglishDateTime.PureNumBetweenAnd); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.GeneralEndingRegex); + public static final Pattern PeriodHourNumRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PeriodHourNumRegex); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeBetweenAnd); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeNumberCombinedWithUnit = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeNumberCombinedWithUnit); + + public EnglishTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + integerExtractor = IntegerExtractor.getInstance(); + timeZoneExtractor = new BaseTimeZoneExtractor(new EnglishTimeZoneExtractorConfiguration(options)); + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + private IDateTimeExtractor singleTimeExtractor; + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private final IDateTimeExtractor timeZoneExtractor; + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + add(SpecificTimeFromTo); + add(SpecificTimeBetweenAnd); + } + }; + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + public final ResultIndex getFromTokenIndex(String input) { + ResultIndex result = new ResultIndex(false, -1); + if (input.endsWith("from")) { + result = new ResultIndex(true, input.lastIndexOf("from")); + } + + return result; + } + + public final ResultIndex getBetweenTokenIndex(String input) { + ResultIndex result = new ResultIndex(false, -1); + if (input.endsWith("between")) { + result = new ResultIndex(true, input.lastIndexOf("between")); + } + + return result; + } + + public final boolean hasConnectorToken(String input) { + return input.equals("and"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..71cbb7aff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/extractors/EnglishTimeZoneExtractorConfiguration.java @@ -0,0 +1,93 @@ +package com.microsoft.recognizers.text.datetime.english.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishTimeZone; +import com.microsoft.recognizers.text.matcher.MatchStrategy; +import com.microsoft.recognizers.text.matcher.NumberWithUnitTokenizer; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class EnglishTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + + // These regexes do need to be case insensitive for them to work correctly + public static final Pattern DirectUtcRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.DirectUtcRegex, Pattern.CASE_INSENSITIVE); + public static final List AbbreviationsList = EnglishTimeZone.AbbreviationsList; + public static final List FullNameList = EnglishTimeZone.FullNameList; + public static final Pattern LocationTimeSuffixRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.LocationTimeSuffixRegex, Pattern.CASE_INSENSITIVE); + public static final StringMatcher LocationMatcher = new StringMatcher(); + public static final StringMatcher TimeZoneMatcher = buildMatcherFromLists(AbbreviationsList, FullNameList); + + public static final List AmbiguousTimezoneList = EnglishTimeZone.AmbiguousTimezoneList; + + public EnglishTimeZoneExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public EnglishTimeZoneExtractorConfiguration(DateTimeOptions options) { + + super(options); + + if (options.match(DateTimeOptions.EnablePreview)) { + LocationMatcher.init( + EnglishTimeZone.MajorLocations.stream() + .map(o -> QueryProcessor.removeDiacritics(o.toLowerCase())) + .collect(Collectors.toCollection(ArrayList::new))); + } + } + + protected static StringMatcher buildMatcherFromLists(List...collections) { + StringMatcher matcher = new StringMatcher(MatchStrategy.TrieTree, new NumberWithUnitTokenizer()); + List matcherList = new ArrayList(); + + for (List collection : collections) { + for (String item : collection) { + matcherList.add(item.toLowerCase()); + } + } + + matcherList.stream().forEach( + item -> { + if (!matcherList.contains(item)) { + matcherList.add(item); + } + } + ); + + matcher.init(matcherList); + + return matcher; + } + + @Override + public Pattern getDirectUtcRegex() { + return DirectUtcRegex; + } + + @Override + public Pattern getLocationTimeSuffixRegex() { + return LocationTimeSuffixRegex; + } + + @Override + public StringMatcher getLocationMatcher() { + return LocationMatcher; + } + + @Override + public StringMatcher getTimeZoneMatcher() { + return TimeZoneMatcher; + } + + @Override + public List getAmbiguousTimezoneList() { + return AmbiguousTimezoneList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..c4a219fec --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishCommonDateTimeParserConfiguration.java @@ -0,0 +1,290 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.utilities.EnglishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; + +public class EnglishCommonDateTimeParserConfiguration extends BaseDateParserConfiguration implements ICommonDateTimeParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeekMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + private final IDateTimeParser timeZoneParser; + + public EnglishCommonDateTimeParserConfiguration(DateTimeOptions options) { + + super(options); + + utilityConfiguration = new EnglishDatetimeUtilityConfiguration(); + + unitMap = EnglishDateTime.UnitMap; + unitValueMap = EnglishDateTime.UnitValueMap; + seasonMap = EnglishDateTime.SeasonMap; + specialYearPrefixesMap = EnglishDateTime.SpecialYearPrefixesMap; + cardinalMap = EnglishDateTime.CardinalMap; + dayOfWeekMap = EnglishDateTime.DayOfWeek; + dayOfMonth = ImmutableMap.builder().putAll(super.getDayOfMonth()).putAll(EnglishDateTime.DayOfMonth).build(); + monthOfYear = EnglishDateTime.MonthOfYear; + numbers = EnglishDateTime.Numbers; + doubleNumbers = EnglishDateTime.DoubleNumbers; + writtenDecades = EnglishDateTime.WrittenDecades; + specialDecadeCases = EnglishDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration()); + dateExtractor = new BaseDateExtractor(new EnglishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new EnglishTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new EnglishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new EnglishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new EnglishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new EnglishDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + durationParser = new BaseDurationParser(new EnglishDurationParserConfiguration(this)); + dateParser = new BaseDateParser(new EnglishDateParserConfiguration(this)); + timeParser = new TimeParser(new EnglishTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new EnglishDateTimeParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new EnglishDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new EnglishTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new EnglishDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new EnglishDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeekMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java new file mode 100644 index 000000000..472b5861a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateParserConfiguration.java @@ -0,0 +1,351 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + public EnglishDateParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = EnglishDateTime.DateTokenPrefix; + + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + + dateRegexes = Collections.unmodifiableList(EnglishDateExtractorConfiguration.DateRegexList); + onRegex = EnglishDateExtractorConfiguration.OnRegex; + specialDayRegex = EnglishDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = EnglishDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = EnglishDateExtractorConfiguration.NextDateRegex; + thisRegex = EnglishDateExtractorConfiguration.ThisRegex; + lastRegex = EnglishDateExtractorConfiguration.LastDateRegex; + unitRegex = EnglishDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = EnglishDateExtractorConfiguration.WeekDayRegex; + monthRegex = EnglishDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = EnglishDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = EnglishDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = EnglishDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = EnglishDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = EnglishDateExtractorConfiguration.StrictRelativeRegex; + relativeWeekDayRegex = EnglishDateExtractorConfiguration.RelativeWeekDayRegex; + + yearSuffix = EnglishDateExtractorConfiguration.YearSuffix; + unitMap = config.getUnitMap(); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + utilityConfiguration = config.getUtilityConfiguration(); + + relativeDayRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + sameDayTerms = Collections.unmodifiableList(EnglishDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(EnglishDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(EnglishDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(EnglishDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(EnglishDateTime.MinusTwoDayTerms); + + } + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final Iterable dateRegexes; + + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + + // The following three regexes only used in this configuration + // They are not used in the base parser, therefore they are not extracted + // If the spanish date parser need the same regexes, they should be extracted + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(String text) { + return getSwift(text); + } + + private Integer getSwift(String text) { + + String trimmedText = text.trim().toLowerCase(); + Integer swift = 0; + + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + + if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public String normalize(String text) { + return text; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java new file mode 100644 index 000000000..ff24cd50d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDatePeriodParserConfiguration.java @@ -0,0 +1,575 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class EnglishDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public EnglishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + durationExtractor = config.getDurationExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + + monthFrontBetweenRegex = EnglishDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = EnglishDatePeriodExtractorConfiguration.BetweenRegex; + monthFrontSimpleCasesRegex = EnglishDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = EnglishDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = EnglishDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = EnglishDatePeriodExtractorConfiguration.MonthWithYear; + monthNumWithYear = EnglishDatePeriodExtractorConfiguration.MonthNumWithYear; + yearRegex = EnglishDatePeriodExtractorConfiguration.YearRegex; + pastRegex = EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + futureRegex = EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + futureSuffixRegex = EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = EnglishDurationExtractorConfiguration.NumberCombinedWithDurationUnit; + weekOfMonthRegex = EnglishDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = EnglishDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = EnglishDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = EnglishDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = EnglishDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = EnglishDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = EnglishDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = EnglishDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = EnglishDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = EnglishDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = EnglishDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = EnglishDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = EnglishDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = EnglishDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = EnglishDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = EnglishDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = EnglishDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = EnglishDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = EnglishDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = EnglishDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = EnglishDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = EnglishDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = EnglishDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = EnglishDatePeriodExtractorConfiguration.CenturySuffixRegex; + relativeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RelativeRegex); + unspecificEndOfRangeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.UnspecificEndOfRangeRegex); + nowRegex = EnglishDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + writtenDecades = config.getWrittenDecades(); + numbers = config.getNumbers(); + specialDecadeCases = config.getSpecialDecadeCases(); + + nextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PreviousPrefixRegex); + thisPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.ThisPrefixRegex); + afterNextSuffixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfterNextSuffixRegex); + } + + private final String tokenBeforeDate; + + // InternalParsers + + private final IDateExtractor dateExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser durationParser; + + // Regex + + private final Pattern monthFrontBetweenRegex; + private final Pattern betweenRegex; + private final Pattern monthFrontSimpleCasesRegex; + private final Pattern simpleCasesRegex; + private final Pattern oneWordPeriodRegex; + private final Pattern monthWithYear; + private final Pattern monthNumWithYear; + private final Pattern yearRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnit; + private final Pattern weekOfMonthRegex; + private final Pattern weekOfYearRegex; + private final Pattern quarterRegex; + private final Pattern quarterRegexYearFront; + private final Pattern allHalfYearRegex; + private final Pattern seasonRegex; + private final Pattern whichWeekRegex; + private final Pattern weekOfRegex; + private final Pattern monthOfRegex; + private final Pattern inConnectorRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern restOfDateRegex; + private final Pattern laterEarlyPeriodRegex; + private final Pattern weekWithWeekDayRangeRegex; + private final Pattern yearPlusNumberRegex; + private final Pattern decadeWithCenturyRegex; + private final Pattern yearPeriodRegex; + private final Pattern complexDatePeriodRegex; + private final Pattern relativeDecadeRegex; + private final Pattern referenceDatePeriodRegex; + private final Pattern agoRegex; + private final Pattern laterRegex; + private final Pattern lessThanRegex; + private final Pattern moreThanRegex; + private final Pattern centurySuffixRegex; + private final Pattern relativeRegex; + private final Pattern unspecificEndOfRangeRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + private final Pattern thisPrefixRegex; + private final Pattern afterNextSuffixRegex; + private final Pattern nowRegex; + + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public Pattern getYearRegex() { + return yearRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public Pattern getWeekWithWeekDayRangeRegex() { + return weekWithWeekDayRangeRegex; + } + + @Override + public Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return relativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchPast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchThisPresent = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNext.isPresent()) { + swift = 1; + } else if (matchPast.isPresent()) { + swift = -1; + } else if (matchThisPresent.isPresent()) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(String text) { + String trimmedText = text.trim().toLowerCase(); + return (trimmedText.startsWith("this") || trimmedText.startsWith("next")); + } + + @Override + public boolean isLastCardinal(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public boolean isMonthOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("month") || trimmedText.contains(" month ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isMonthToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("month to date"); + } + + @Override + public boolean isWeekend(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("weekend") || trimmedText.contains(" weekend ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isWeekOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + return trimmedText.endsWith("week") || trimmedText.contains(" week ") && matchAfterNext.isPresent(); + } + + @Override + public boolean isYearOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + return EnglishDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + (getYearTermsPadded().anyMatch(o -> trimmedText.contains(o)) && RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText).length > 0) || + (EnglishDateTime.GenericYearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) && RegExpUtility.getMatches(unspecificEndOfRangeRegex, trimmedText).length > 0); + } + + @Override + public boolean isYearToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("year to date"); + } + + private Stream getYearTermsPadded() { + return EnglishDateTime.YearTerms.stream().map(i -> String.format(" %s ", i)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..158464df9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class EnglishDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public EnglishDateTimeAltParserConfiguration(ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java new file mode 100644 index 000000000..aa285aa9e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimeParserConfiguration.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + private final String tokenBeforeDate; + private final String tokenBeforeTime; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeParser durationParser; + + private final Pattern nowRegex; + private final Pattern amTimeRegex; + private final Pattern pmTimeRegex; + private final Pattern simpleTimeOfTodayAfterRegex; + private final Pattern simpleTimeOfTodayBeforeRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern specificEndOfRegex; + private final Pattern unspecificEndOfRegex; + private final Pattern unitRegex; + private final Pattern dateNumberConnectorRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public EnglishDateTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + tokenBeforeTime = EnglishDateTime.TokenBeforeTime; + + cardinalExtractor = config.getCardinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationExtractor = config.getDurationExtractor(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + durationParser = config.getDurationParser(); + + nowRegex = EnglishDateTimeExtractorConfiguration.NowRegex; + + amTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AMTimeRegex); + pmTimeRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PMTimeRegex); + + simpleTimeOfTodayAfterRegex = EnglishDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = EnglishDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + specificTimeOfDayRegex = EnglishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + specificEndOfRegex = EnglishDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = EnglishDateTimeExtractorConfiguration.UnspecificEndOfRegex; + unitRegex = EnglishTimeExtractorConfiguration.TimeUnitRegex; + dateNumberConnectorRegex = EnglishDateTimeExtractorConfiguration.DateNumberConnectorRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + @Override + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public boolean containsAmbiguousToken(String text, String matchedText) { + return false; + } + + @Override + public ResultTimex getMatchedNowTimex(String text) { + + String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("now")) { + return new ResultTimex(true, "PRESENT_REF"); + } else if (trimmedText.equals("recently") || trimmedText.equals("previously")) { + return new ResultTimex(true, "PAST_REF"); + } else if (trimmedText.equals("as soon as possible") || trimmedText.equals("asap")) { + return new ResultTimex(true, "FUTURE_REF"); + } + + return new ResultTimex(false, null); + } + + @Override + public int getSwiftDay(String text) { + + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } + + return swift; + } + + @Override + public int getHour(String text, int hour) { + + String trimmedText = text.trim().toLowerCase(); + int result = hour; + + if (trimmedText.endsWith("morning") && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!trimmedText.endsWith("morning") && hour < Constants.HalfDayHourCount && !(trimmedText.endsWith("night") && hour < 6)) { + result += Constants.HalfDayHourCount; + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..3e277e865 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDateTimePeriodParserConfiguration.java @@ -0,0 +1,337 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + public static final Pattern MorningStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.MorningStartEndRegex); + public static final Pattern AfternoonStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AfternoonStartEndRegex); + public static final Pattern EveningStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.EveningStartEndRegex); + public static final Pattern NightStartEndRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NightStartEndRegex); + + public EnglishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = EnglishDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = EnglishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = EnglishDateTimePeriodExtractorConfiguration.PeriodSpecificTimeOfDayRegex; + timeOfDayRegex = EnglishDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = EnglishDatePeriodExtractorConfiguration.PreviousPrefixRegex; + futureRegex = EnglishDatePeriodExtractorConfiguration.NextPrefixRegex; + futureSuffixRegex = EnglishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = EnglishDateTimePeriodExtractorConfiguration.TimeNumberCombinedWithUnit; + unitRegex = EnglishTimePeriodExtractorConfiguration.TimeUnitRegex; + periodTimeOfDayWithDateRegex = EnglishDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = EnglishDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = EnglishDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = EnglishDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = EnglishDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = EnglishDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = EnglishDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = EnglishDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = EnglishDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + private boolean checkRegex(Pattern regex, String input) { + return RegExpUtility.getMatches(regex, input).length > 0; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + beginHour = 0; + endHour = 0; + endMin = 0; + timeStr = null; + boolean result = false; + + if (checkRegex(MorningStartEndRegex, trimmedText)) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + result = true; + } else if (checkRegex(AfternoonStartEndRegex, trimmedText)) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + result = true; + } else if (checkRegex(EveningStartEndRegex, trimmedText)) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + result = true; + } else if (checkRegex(NightStartEndRegex, trimmedText)) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + result = true; + } else { + timeStr = null; + } + + return new MatchedTimeRangeResult(result, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(String text) { + + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java new file mode 100644 index 000000000..5a453bd75 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishDurationParserConfiguration.java @@ -0,0 +1,145 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; + +import java.util.regex.Pattern; + +public class EnglishDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public EnglishDurationParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new EnglishDurationExtractorConfiguration(), false); + numberCombinedWithUnit = EnglishDurationExtractorConfiguration.NumberCombinedWithDurationUnit; + + anUnitRegex = EnglishDurationExtractorConfiguration.AnUnitRegex; + duringRegex = EnglishDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = EnglishDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = EnglishDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = EnglishDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = EnglishDurationExtractorConfiguration.DurationFollowedUnit; + conjunctionRegex = EnglishDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = EnglishDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = EnglishDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = EnglishDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java new file mode 100644 index 000000000..bbac800b0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishHolidayParserConfiguration.java @@ -0,0 +1,225 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.function.IntFunction; + +public class EnglishHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public EnglishHolidayParserConfiguration() { + + super(); + + this.setHolidayRegexList(EnglishHolidayExtractorConfiguration.HolidayRegexList); + + HashMap> newMap = new HashMap<>(); + for (Map.Entry entry : EnglishDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + newMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + this.setHolidayNames(ImmutableMap.copyOf(newMap)); + } + + @Override + protected HashMap> initHolidayFuncs() { + + HashMap> holidays = new HashMap<>(super.initHolidayFuncs()); + holidays.put("mayday", EnglishHolidayParserConfiguration::mayday); + holidays.put("yuandan", EnglishHolidayParserConfiguration::newYear); + holidays.put("newyear", EnglishHolidayParserConfiguration::newYear); + holidays.put("youthday", EnglishHolidayParserConfiguration::youthDay); + holidays.put("girlsday", EnglishHolidayParserConfiguration::girlsDay); + holidays.put("xmas", EnglishHolidayParserConfiguration::christmasDay); + holidays.put("newyearday", EnglishHolidayParserConfiguration::newYear); + holidays.put("aprilfools", EnglishHolidayParserConfiguration::foolDay); + holidays.put("easterday", EnglishHolidayParserConfiguration::easterDay); + holidays.put("newyearsday", EnglishHolidayParserConfiguration::newYear); + holidays.put("femaleday", EnglishHolidayParserConfiguration::femaleDay); + holidays.put("singleday", EnglishHolidayParserConfiguration::singlesDay); + holidays.put("newyeareve", EnglishHolidayParserConfiguration::newYearEve); + holidays.put("arborday", EnglishHolidayParserConfiguration::treePlantDay); + holidays.put("loverday", EnglishHolidayParserConfiguration::valentinesDay); + holidays.put("christmas", EnglishHolidayParserConfiguration::christmasDay); + holidays.put("teachersday", EnglishHolidayParserConfiguration::teacherDay); + holidays.put("stgeorgeday", EnglishHolidayParserConfiguration::stGeorgeDay); + holidays.put("baptisteday", EnglishHolidayParserConfiguration::baptisteDay); + holidays.put("bastilleday", EnglishHolidayParserConfiguration::bastilleDay); + holidays.put("allsoulsday", EnglishHolidayParserConfiguration::allSoulsDay); + holidays.put("veteransday", EnglishHolidayParserConfiguration::veteransDay); + holidays.put("childrenday", EnglishHolidayParserConfiguration::childrenDay); + holidays.put("maosbirthday", EnglishHolidayParserConfiguration::maoBirthday); + holidays.put("allsaintsday", EnglishHolidayParserConfiguration::halloweenDay); + holidays.put("stpatrickday", EnglishHolidayParserConfiguration::stPatrickDay); + holidays.put("halloweenday", EnglishHolidayParserConfiguration::halloweenDay); + holidays.put("allhallowday", EnglishHolidayParserConfiguration::allHallowDay); + holidays.put("guyfawkesday", EnglishHolidayParserConfiguration::guyFawkesDay); + holidays.put("christmaseve", EnglishHolidayParserConfiguration::christmasEve); + holidays.put("groundhougday", EnglishHolidayParserConfiguration::groundhogDay); + holidays.put("whiteloverday", EnglishHolidayParserConfiguration::whiteLoverDay); + holidays.put("valentinesday", EnglishHolidayParserConfiguration::valentinesDay); + holidays.put("treeplantingday", EnglishHolidayParserConfiguration::treePlantDay); + holidays.put("cincodemayoday", EnglishHolidayParserConfiguration::cincoDeMayoDay); + holidays.put("inaugurationday", EnglishHolidayParserConfiguration::inaugurationDay); + holidays.put("independenceday", EnglishHolidayParserConfiguration::usaIndependenceDay); + holidays.put("usindependenceday", EnglishHolidayParserConfiguration::usaIndependenceDay); + holidays.put("juneteenth", EnglishHolidayParserConfiguration::juneteenth); + + return holidays; + } + + private static LocalDateTime easterDay(int year) { + return DateUtil.minValue(); + } + + private static LocalDateTime mayday(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + private static LocalDateTime newYear(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime foolDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 1); + } + + private static LocalDateTime girlsDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 7); + } + + private static LocalDateTime youthDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 4); + } + + private static LocalDateTime femaleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime teacherDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime groundhogDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 2); + } + + private static LocalDateTime stGeorgeDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 23); + } + + private static LocalDateTime baptisteDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 24); + } + + private static LocalDateTime bastilleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 14); + } + + private static LocalDateTime allSoulsDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 2); + } + + private static LocalDateTime singlesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime newYearEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime treePlantDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 12); + } + + private static LocalDateTime stPatrickDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 17); + } + + private static LocalDateTime allHallowDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 1); + } + + private static LocalDateTime guyFawkesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 5); + } + + private static LocalDateTime veteransDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime maoBirthday(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 26); + } + + private static LocalDateTime valentinesDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 14); + } + + private static LocalDateTime whiteLoverDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 14); + } + + private static LocalDateTime cincoDeMayoDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 5); + } + + private static LocalDateTime halloweenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime christmasEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime christmasDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime inaugurationDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 20); + } + + private static LocalDateTime usaIndependenceDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 4); + } + + private static LocalDateTime juneteenth(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 19); + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = StringUtility.trimStart(StringUtility.trimEnd(text)).toLowerCase(Locale.ROOT); + int swift = -10; + + if (trimmedText.startsWith("next")) { + swift = 1; + } else if (trimmedText.startsWith("last")) { + swift = -1; + } else if (trimmedText.startsWith("this")) { + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(String holiday) { + return holiday.replace(" ", "").replace("'", ""); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java new file mode 100644 index 000000000..b8b43c163 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishMergedParserConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public class EnglishMergedParserConfiguration extends EnglishCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public EnglishMergedParserConfiguration(DateTimeOptions options) { + super(options); + + beforeRegex = EnglishMergedExtractorConfiguration.BeforeRegex; + afterRegex = EnglishMergedExtractorConfiguration.AfterRegex; + sinceRegex = EnglishMergedExtractorConfiguration.SinceRegex; + aroundRegex = EnglishMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = EnglishMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = EnglishDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = EnglishMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new EnglishSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new EnglishHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java new file mode 100644 index 000000000..290cd5a7f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishSetParserConfiguration.java @@ -0,0 +1,251 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Pattern; + +public class EnglishSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private IDateTimeParser timeParser; + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + private IDateTimeParser dateParser; + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + private ImmutableMap unitMap; + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + private IDateTimeParser dateTimeParser; + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + private IDateTimeParser durationParser; + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeParser datePeriodParser; + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + private IDateTimeParser timePeriodParser; + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeParser dateTimePeriodParser; + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private Pattern eachDayRegex; + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + private Pattern setEachRegex; + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + private Pattern periodicRegex; + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + private Pattern eachUnitRegex; + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + private Pattern setWeekDayRegex; + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + private Pattern eachPrefixRegex; + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + private static Pattern doubleMultiplierRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.DoubleMultiplierRegex); + + private static Pattern halfMultiplierRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.HalfMultiplierRegex); + + private static Pattern dayTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.DayTypeRegex); + + private static Pattern weekTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.WeekTypeRegex); + + private static Pattern weekendTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.WeekendTypeRegex); + + private static Pattern monthTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.MonthTypeRegex); + + private static Pattern quarterTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.QuarterTypeRegex); + + private static Pattern yearTypeRegex = + RegExpUtility.getSafeRegExp(EnglishDateTime.YearTypeRegex); + + public EnglishSetParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + durationExtractor = config.getDurationExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + unitMap = config.getUnitMap(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + durationParser = config.getDurationParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + + eachDayRegex = EnglishSetExtractorConfiguration.EachDayRegex; + setEachRegex = EnglishSetExtractorConfiguration.SetEachRegex; + eachUnitRegex = EnglishSetExtractorConfiguration.EachUnitRegex; + periodicRegex = EnglishSetExtractorConfiguration.PeriodicRegex; + eachPrefixRegex = EnglishSetExtractorConfiguration.EachPrefixRegex; + setWeekDayRegex = EnglishSetExtractorConfiguration.SetWeekDayRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + float durationLength = 1; // Default value + float multiplier = 1; + String durationType; + + if (trimmedText.equals("daily")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("weekly")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("biweekly")) { + result.setTimex("P2W"); + } else if (trimmedText.equals("monthly")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("quarterly")) { + result.setTimex("P3M"); + } else if (trimmedText.equals("yearly") || trimmedText.equals("annually") || trimmedText.equals("annual")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } + + public MatchedTimexResult getMatchedUnitTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.equals("day")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("week")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("month")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("year")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java new file mode 100644 index 000000000..72fab44ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimeParserConfiguration.java @@ -0,0 +1,187 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class EnglishTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + + private final Pattern atRegex; + private final Iterable timeRegexes; + private final Pattern timeSuffixFull = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeSuffixFull); + private final Pattern lunchRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LunchRegex); + private final Pattern nightRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.NightRegex); + + public EnglishTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = EnglishTimeExtractorConfiguration.AtRegex; + timeRegexes = EnglishTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return EnglishDateTime.TimeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin) { + + int deltaMin; + String trimmedPrefix = prefix.trim().toLowerCase(); + + if (trimmedPrefix.startsWith("half")) { + deltaMin = 30; + } else if (trimmedPrefix.startsWith("a quarter") || trimmedPrefix.startsWith("quarter")) { + deltaMin = 15; + } else if (trimmedPrefix.startsWith("three quarter")) { + deltaMin = 45; + } else { + + Optional match = Arrays.stream(RegExpUtility.getMatches(EnglishTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix)).findFirst(); + String minStr = match.get().getGroup("deltamin").value; + if (!StringUtility.isNullOrWhiteSpace(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match.get().getGroup("deltaminnum").value; + deltaMin = numbers.getOrDefault(minStr, 0); + } + } + + if (trimmedPrefix.endsWith("to")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + hasMin = true; + + return new PrefixAdjustResult(hour, min, hasMin); + } + + @Override + public SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm) { + + String lowerSuffix = suffix.toLowerCase(); + int deltaHour = 0; + ConditionalMatch match = RegexExtension.matchExact(timeSuffixFull, lowerSuffix, true); + if (match.getSuccess()) { + + String oclockStr = match.getMatch().get().getGroup("oclock").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } else { + hasAm = true; + } + + } + + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(pmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + + if (checkMatch(lunchRegex, pmStr)) { + // for hour >= 10, < 12 + if (hour >= 10 && hour <= Constants.HalfDayHourCount) { + deltaHour = 0; + if (hour == Constants.HalfDayHourCount) { + hasPm = true; + } else { + hasAm = true; + } + + } else { + hasPm = true; + } + + } else if (checkMatch(nightRegex, pmStr)) { + //For hour <= 3 or == 12, we treat it as am, for example 1 in the night (midnight) == 1am + if (hour <= 3 || hour == Constants.HalfDayHourCount) { + if (hour == Constants.HalfDayHourCount) { + hour = 0; + } + + deltaHour = 0; + hasAm = true; + } else { + hasPm = true; + } + + } else { + hasPm = true; + } + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } + + private boolean checkMatch(Pattern regex, String input) { + return RegExpUtility.getMatches(regex, input).length > 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java new file mode 100644 index 000000000..0d6d17779 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/EnglishTimePeriodParserConfiguration.java @@ -0,0 +1,159 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; + +import java.util.regex.Pattern; + +public class EnglishTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final ImmutableMap numbers; + + public EnglishTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + + pureNumberFromToRegex = EnglishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = EnglishTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = EnglishTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = EnglishTimePeriodExtractorConfiguration.TimeOfDayRegex; + + generalEndingRegex = EnglishTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = EnglishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + if (trimmedText.endsWith("s")) { + trimmedText = trimmedText.substring(0, trimmedText.length() - 1); + } + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + + if (EnglishDateTime.MorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Morning; + } else if (EnglishDateTime.AfternoonTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Afternoon; + } else if (EnglishDateTime.EveningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Evening; + } else if (EnglishDateTime.DaytimeTermList.stream().anyMatch(trimmedText::equals)) { + timeOfDay = Constants.Daytime; + } else if (EnglishDateTime.NightTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Night; + } else if (EnglishDateTime.BusinessHourSplitStrings.stream().allMatch(trimmedText::contains)) { + timeOfDay = Constants.BusinessHour; + } else { + timex = null; + return new MatchedTimeRangeResult(false, timex, beginHour, endHour, endMin); + } + + TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java new file mode 100644 index 000000000..5c898aecc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/parsers/TimeParser.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.datetime.english.parsers; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.english.extractors.EnglishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.Optional; + +public class TimeParser extends BaseTimeParser { + + public TimeParser(ITimeParserConfiguration config) { + super(config); + } + + @Override + protected DateTimeResolutionResult internalParse(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult innerResult = super.internalParse(text, referenceTime); + + if (!innerResult.getSuccess()) { + innerResult = parseIsh(text, referenceTime); + } + + return innerResult; + } + + // parse "noonish", "11-ish" + private DateTimeResolutionResult parseIsh(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String lowerText = text.toLowerCase(); + + ConditionalMatch match = RegexExtension.matchExact(EnglishTimeExtractorConfiguration.IshRegex, text, true); + if (match.getSuccess()) { + String hourStr = match.getMatch().get().getGroup(Constants.HourGroupName).value; + int hour = Constants.HalfDayHourCount; + + if (!StringUtility.isNullOrEmpty(hourStr)) { + hour = Integer.parseInt(hourStr); + } + + result.setTimex(String.format("T%02d", hour)); + LocalDateTime resultTime = DateUtil.safeCreateFromMinValue( + referenceTime.getYear(), + referenceTime.getMonthValue(), + referenceTime.getDayOfMonth(), + hour, 0, 0); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + result.setSuccess(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..b85e88b72 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/english/utilities/EnglishDatetimeUtilityConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.datetime.english.utilities; + +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.LaterRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.WithinNextPrefixRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.PmDescRegex); + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.AmPmDescRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.RangeUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.TimeUnitRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.DateUnitRegex); + public static final Pattern CommonDatePrefixRegex = RegExpUtility.getSafeRegExp(EnglishDateTime.CommonDatePrefixRegex); + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java new file mode 100644 index 000000000..65d13f941 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/AbstractYearExtractor.java @@ -0,0 +1,108 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.List; + +public abstract class AbstractYearExtractor implements IDateExtractor { + + protected final IDateExtractorConfiguration config; + + public AbstractYearExtractor(IDateExtractorConfiguration config) { + this.config = config; + } + + @Override + public abstract String getExtractorName(); + + @Override + public abstract List extract(String input, LocalDateTime reference); + + @Override + public abstract List extract(String input); + + @Override + public int getYearFromText(Match match) { + + int year = Constants.InvalidYear; + + String yearStr = match.getGroup("year").value; + String writtenYearStr = match.getGroup("fullyear").value; + + if (!StringUtility.isNullOrEmpty(yearStr) && !yearStr.equals(writtenYearStr)) { + + year = Math.round(Double.valueOf(yearStr).floatValue()); + + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } else { + + MatchGroup firstTwoYear = match.getGroup("firsttwoyearnum"); + + if (!StringUtility.isNullOrEmpty(firstTwoYear.value)) { + ExtractResult er = new ExtractResult(); + er.setStart(firstTwoYear.index); + er.setLength(firstTwoYear.length); + er.setText(firstTwoYear.value); + + int firstTwoYearNum = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + + int lastTwoYearNum = 0; + + MatchGroup lastTwoYear = match.getGroup("lasttwoyearnum"); + + if (!StringUtility.isNullOrEmpty(lastTwoYear.value)) { + er = new ExtractResult(); + er.setStart(lastTwoYear.index); + er.setLength(lastTwoYear.length); + er.setText(lastTwoYear.value); + + lastTwoYearNum = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + } + + // Exclude pure number like "nineteen", "twenty four" + if (firstTwoYearNum < 100 && lastTwoYearNum == 0 || firstTwoYearNum < 100 && firstTwoYearNum % 10 == 0 && lastTwoYear.value.trim().split(" ").length == 1) { + year = Constants.InvalidYear; + return year; + } + + if (firstTwoYearNum >= 100) { + year = firstTwoYearNum + lastTwoYearNum; + } else { + year = firstTwoYearNum * 100 + lastTwoYearNum; + } + + } else { + + if (!StringUtility.isNullOrEmpty(writtenYearStr)) { + + MatchGroup writtenYear = match.getGroup("fullyear"); + + ExtractResult er = new ExtractResult(); + er.setStart(writtenYear.index); + er.setLength(writtenYear.length); + er.setText(writtenYear.value); + + year = Math.round(Double.valueOf((double)config.getNumberParser().parse(er).getValue()).floatValue()); + + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } + } + } + + return year; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java new file mode 100644 index 000000000..76b57dab3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateExtractor.java @@ -0,0 +1,478 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class BaseDateExtractor extends AbstractYearExtractor implements IDateTimeExtractor { + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATE; + } + + public BaseDateExtractor(IDateExtractorConfiguration config) { + super(config); + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(implicitDate(input)); + tokens.addAll(numberWithMonth(input, reference)); + tokens.addAll(extractRelativeDurationDate(input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + // match basic patterns in DateRegexList + private Collection basicRegexMatch(String text) { + List result = new ArrayList<>(); + + for (Pattern regex : config.getDateRegexList()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + + for (Match match : matches) { + // some match might be part of the date range entity, and might be splitted in a wrong way + + if (validateMatch(match, text)) { + // Cases that the relative term is before the detected date entity, like "this 5/12", "next friday 5/12" + String preText = text.substring(0, match.index); + ConditionalMatch relativeRegex = RegexExtension.matchEnd(config.getStrictRelativeRegex(), preText, true); + if (relativeRegex.getSuccess()) { + result.add(new Token(relativeRegex.getMatch().get().index, match.index + match.length)); + } else { + result.add(new Token(match.index, match.index + match.length)); + } + } + } + } + + return result; + } + + // this method is to validate whether the match is part of date range and is a correct split + // For example: in case "10-1 - 11-7", "10-1 - 11" can be matched by some of the Regexes, + // but the full text is a date range, so "10-1 - 11" is not a correct split + private boolean validateMatch(Match match, String text) { + // If the match doesn't contains "year" part, it will not be ambiguous and it's a valid match + boolean isValidMatch = StringUtility.isNullOrEmpty(match.getGroup("year").value); + + if (!isValidMatch) { + MatchGroup yearGroup = match.getGroup("year"); + + // If the "year" part is not at the end of the match, it's a valid match + if (yearGroup.index + yearGroup.length != match.index + match.length) { + isValidMatch = true; + } else { + String subText = text.substring(yearGroup.index); + + // If the following text (include the "year" part) doesn't start with a Date entity, it's a valid match + if (!startsWithBasicDate(subText)) { + isValidMatch = true; + } else { + // If the following text (include the "year" part) starts with a Date entity, + // but the following text (doesn't include the "year" part) also starts with a valid Date entity, + // the current match is still valid + // For example, "10-1-2018-10-2-2018". Match "10-1-2018" is valid because though "2018-10-2" a valid match + // (indicates the first year "2018" might belongs to the second Date entity), but "10-2-2018" is also a valid match. + subText = text.substring(yearGroup.index + yearGroup.length).trim(); + subText = trimStartRangeConnectorSymbols(subText); + isValidMatch = startsWithBasicDate(subText); + } + } + } + + return isValidMatch; + } + + // TODO: Simplify this method to improve the performance + private String trimStartRangeConnectorSymbols(String text) { + Match[] rangeConnectorSymbolMatches = RegExpUtility.getMatches(config.getRangeConnectorSymbolRegex(), text); + + for (Match symbolMatch : rangeConnectorSymbolMatches) { + int startSymbolLength = -1; + + if (symbolMatch.value != "" && symbolMatch.index == 0 && symbolMatch.length > startSymbolLength) { + startSymbolLength = symbolMatch.length; + } + + if (startSymbolLength > 0) { + text = text.substring(startSymbolLength); + } + } + + return text.trim(); + } + + // TODO: Simplify this method to improve the performance + private boolean startsWithBasicDate(String text) { + for (Pattern regex : config.getDateRegexList()) { + ConditionalMatch match = RegexExtension.matchBegin(regex, text, true); + + if (match.getSuccess()) { + return true; + } + } + + return false; + } + + // match several other cases + // including 'today', 'the day after tomorrow', 'on 13' + private Collection implicitDate(String text) { + List result = new ArrayList<>(); + + for (Pattern regex : config.getImplicitDateList()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + + for (Match match : matches) { + result.add(new Token(match.index, match.index + match.length)); + } + } + + return result; + } + + // Check every integers and ordinal number for date + private Collection numberWithMonth(String text, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List ers = config.getOrdinalExtractor().extract(text); + ers.addAll(config.getIntegerExtractor().extract(text)); + + for (ExtractResult result : ers) { + int num; + try { + ParseResult parseResult = config.getNumberParser().parse(result); + num = Float.valueOf(parseResult.getValue().toString()).intValue(); + } catch (NumberFormatException e) { + num = 0; + } + + if (num < 1 || num > 31) { + continue; + } + + if (result.getStart() >= 0) { + // Handling cases like '(Monday,) Jan twenty two' + String frontStr = text.substring(0, result.getStart()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getMonthEnd(), frontStr)).findFirst(); + if (match.isPresent()) { + int startIndex = match.get().index; + int endIndex = match.get().index + match.get().length + result.getLength(); + + int month = config.getMonthOfYear().getOrDefault(match.get().getGroup("month").value.toLowerCase(), reference.getMonthValue()); + + Pair startEnd = extendWithWeekdayAndYear(startIndex, endIndex, month, num, text, reference); + + tokens.add(new Token(startEnd.getValue0(), startEnd.getValue1())); + continue; + } + + // Handling cases like 'for the 25th' + Match[] matches = RegExpUtility.getMatches(config.getForTheRegex(), text); + boolean isFound = false; + + for (Match matchCase : matches) { + if (matchCase != null) { + String ordinalNum = matchCase.getGroup("DayOfMonth").value; + if (ordinalNum.equals(result.getText())) { + int endLenght = 0; + if (!matchCase.getGroup("end").value.equals("")) { + endLenght = matchCase.getGroup("end").value.length(); + } + + tokens.add(new Token(matchCase.index, matchCase.index + matchCase.length - endLenght)); + isFound = true; + } + } + } + + if (isFound) { + continue; + } + + // Handling cases like 'Thursday the 21st', which both 'Thursday' and '21st' refer to a same date + matches = RegExpUtility.getMatches(config.getWeekDayAndDayOfMonthRegex(), text); + isFound = false; + for (Match matchCase : matches) { + if (matchCase != null) { + String ordinalNum = matchCase.getGroup("DayOfMonth").value; + if (ordinalNum.equals(result.getText())) { + // Get week of day for the ordinal number which is regarded as a date of reference month + LocalDateTime date = DateUtil.safeCreateFromMinValue(reference.getYear(), reference.getMonthValue(), num); + String numWeekDayStr = date.getDayOfWeek().toString().toLowerCase(); + + // Get week day from text directly, compare it with the weekday generated above + // to see whether they refer to the same week day + String extractedWeekDayStr = matchCase.getGroup("weekday").value.toLowerCase(); + int numWeekDay = config.getDayOfWeek().get(numWeekDayStr); + int extractedWeekDay = config.getDayOfWeek().get(extractedWeekDayStr); + + if (date != DateUtil.minValue() && numWeekDay == extractedWeekDay) { + tokens.add(new Token(matchCase.index, result.getStart() + result.getLength())); + isFound = true; + } + } + } + } + + if (isFound) { + continue; + } + + // Handling cases like '20th of next month' + String suffixStr = text.substring(result.getStart() + result.getLength()); + ConditionalMatch beginMatch = RegexExtension.matchBegin(config.getRelativeMonthRegex(), suffixStr.trim(), true); + if (beginMatch.getSuccess() && beginMatch.getMatch().get().index == 0) { + int spaceLen = suffixStr.length() - suffixStr.trim().length(); + int resStart = result.getStart(); + int resEnd = resStart + result.getLength() + spaceLen + beginMatch.getMatch().get().length; + + // Check if prefix contains 'the', include it if any + String prefix = text.substring(0, resStart); + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPrefixArticleRegex(), prefix)).findFirst(); + if (prefixMatch.isPresent()) { + resStart = prefixMatch.get().index; + } + + tokens.add(new Token(resStart, resEnd)); + } + + // Handling cases like 'second Sunday' + suffixStr = text.substring(result.getStart() + result.getLength()); + beginMatch = RegexExtension.matchBegin(config.getWeekDayRegex(), suffixStr.trim(), true); + if (beginMatch.getSuccess() && num >= 1 && num <= 5 && result.getType().equals("builtin.num.ordinal")) { + String weekDayStr = beginMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + if (config.getDayOfWeek().containsKey(weekDayStr)) { + int spaceLen = suffixStr.length() - suffixStr.trim().length(); + tokens.add(new Token(result.getStart(), result.getStart() + result.getLength() + spaceLen + beginMatch.getMatch().get().length)); + } + } + } + + // For cases like "I'll go back twenty second of June" + if (result.getStart() + result.getLength() < text.length()) { + String afterStr = text.substring(result.getStart() + result.getLength()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getOfMonth(), afterStr)).findFirst(); + if (match.isPresent()) { + int startIndex = result.getStart(); + int endIndex = result.getStart() + result.getLength() + match.get().length; + + int month = config.getMonthOfYear().getOrDefault(match.get().getGroup("month").value.toLowerCase(), reference.getMonthValue()); + + Pair startEnd = extendWithWeekdayAndYear(startIndex, endIndex, month, num, text, reference); + tokens.add(new Token(startEnd.getValue0(), startEnd.getValue1())); + } + } + } + + return tokens; + } + + private Pair extendWithWeekdayAndYear(int startIndex, int endIndex, int month, int day, String text, LocalDateTime reference) { + int year = reference.getYear(); + int startIndexResult = startIndex; + int endIndexResult = endIndex; + + // Check whether there's a year + String suffix = text.substring(endIndexResult); + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearSuffix(), suffix)).findFirst(); + + if (matchYear.isPresent() && matchYear.get().index == 0) { + year = getYearFromText(matchYear.get()); + + if (year >= Constants.MinYearNum && year <= Constants.MaxYearNum) { + endIndexResult += matchYear.get().length; + } + } + + LocalDateTime date = DateUtil.safeCreateFromMinValue(year, month, day); + + // Check whether there's a weekday + String prefix = text.substring(0, startIndexResult); + Optional matchWeekDay = Arrays.stream(RegExpUtility.getMatches(config.getWeekDayEnd(), prefix)).findFirst(); + if (matchWeekDay.isPresent()) { + // Get weekday from context directly, compare it with the weekday extraction above + // to see whether they are referred to the same weekday + String extractedWeekDayStr = matchWeekDay.get().getGroup("weekday").value.toLowerCase(); + String numWeekDayStr = date.getDayOfWeek().toString().toLowerCase(); + + if (config.getDayOfWeek().containsKey(numWeekDayStr) && config.getDayOfWeek().containsKey(extractedWeekDayStr)) { + int weekDay1 = config.getDayOfWeek().get(numWeekDayStr); + int weekday2 = config.getDayOfWeek().get(extractedWeekDayStr); + if (date != DateUtil.minValue() && weekDay1 == weekday2) { + startIndexResult = matchWeekDay.get().index; + } + + } + } + + return new Pair<>(startIndexResult, endIndexResult); + } + + // Cases like "3 days from today", "5 weeks before yesterday", "2 months after tomorrow" + // Note that these cases are of type "date" + private Collection extractRelativeDurationDate(String text, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List durations = config.getDurationExtractor().extract(text, reference); + + for (ExtractResult duration : durations) { + // if it is a multiple duration but its type is not equal to Date, skip it here + if (isMultipleDuration(duration) && !isMultipleDurationDate(duration)) { + continue; + } + + // Some types of duration can be compounded with "before", "after" or "from" suffix to create a "date" + // While some other types of durations, when compounded with such suffix, it will not create a "date", but create a "dateperiod" + // For example, durations like "3 days", "2 weeks", "1 week and 2 days", can be compounded with such suffix to create a "date" + // But "more than 3 days", "less than 2 weeks", when compounded with such suffix, it will become cases + // like "more than 3 days from today" which is a "dateperiod", not a "date" + // As this parent method is aimed to extract RelativeDurationDate, so for cases with "more than" or "less than", + // we remove the prefix so as to extract the expected RelativeDurationDate + if (isInequalityDuration(duration)) { + duration = stripInequalityDuration(duration); + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), duration.getText())).findFirst(); + + if (match.isPresent()) { + tokens = AgoLaterUtil.extractorDurationWithBeforeAndAfter(text, duration, tokens, config.getUtilityConfiguration()); + } + + } + + // Extract cases like "in 3 weeks", which equals to "3 weeks from today" + List relativeDurationDateWithInPrefix = extractRelativeDurationDateWithInPrefix(text, durations, reference); + + // For cases like "in 3 weeks from today", we should choose "3 weeks from today" as the extract result rather than "in 3 weeks" or "in 3 weeks from today" + for (Token erWithInPrefix : relativeDurationDateWithInPrefix) { + if (!isOverlapWithExistExtractions(erWithInPrefix, tokens)) { + tokens.add(erWithInPrefix); + } + } + + return tokens; + } + + public boolean isOverlapWithExistExtractions(Token er, List existErs) { + for (Token existEr : existErs) { + if (er.getStart() < existEr.getEnd() && er.getEnd() > existEr.getStart()) { + return true; + } + } + + return false; + } + + // "In 3 days/weeks/months/years" = "3 days/weeks/months/years from now" + public List extractRelativeDurationDateWithInPrefix(String text, List durationEr, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List durations = new ArrayList<>(); + + for (ExtractResult durationExtraction : durationEr) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), durationExtraction.getText())).findFirst(); + if (match.isPresent()) { + int start = durationExtraction.getStart() != null ? durationExtraction.getStart() : 0; + int end = start + (durationExtraction.getLength() != null ? durationExtraction.getLength() : 0); + durations.add(new Token(start, end)); + } + } + + for (Token duration : durations) { + String beforeStr = text.substring(0, duration.getStart()).toLowerCase(); + String afterStr = text.substring(duration.getStart() + duration.getLength()).toLowerCase(); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + ConditionalMatch match = RegexExtension.matchEnd(config.getInConnectorRegex(), beforeStr, true); + + if (match.getSuccess() && match.getMatch().isPresent()) { + int startToken = match.getMatch().get().index; + Optional rangeUnitMatch = Arrays.stream( + RegExpUtility.getMatches(config.getRangeUnitRegex(), + text.substring(duration.getStart(), + duration.getStart() + duration.getLength()))).findFirst(); + + if (rangeUnitMatch.isPresent()) { + tokens.add(new Token(startToken, duration.getEnd())); + } + } + } + + return tokens; + } + + private ExtractResult stripInequalityDuration(ExtractResult er) { + ExtractResult result = er; + result = stripInequalityPrefix(result, config.getMoreThanRegex()); + result = stripInequalityPrefix(result, config.getLessThanRegex()); + return result; + } + + private ExtractResult stripInequalityPrefix(ExtractResult er, Pattern regex) { + ExtractResult result = er; + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, er.getText())).findFirst(); + + if (match.isPresent()) { + int originalLength = er.getText().length(); + String text = er.getText().replace(match.get().value, "").trim(); + int start = er.getStart() + originalLength - text.length(); + int length = text.length(); + String data = ""; + result.setStart(start); + result.setLength(length); + result.setText(text); + result.setData(data); + } + + return result; + } + + // Cases like "more than 3 days", "less than 4 weeks" + private boolean isInequalityDuration(ExtractResult er) { + return er.getData() != null && (er.getData().toString().equals(Constants.MORE_THAN_MOD) || er.getData().toString().equals(Constants.LESS_THAN_MOD)); + } + + private boolean isMultipleDurationDate(ExtractResult er) { + return er.getData() != null && er.getData().toString().equals(Constants.MultipleDuration_Date); + } + + private boolean isMultipleDuration(ExtractResult er) { + return er.getData() != null && er.getData().toString().startsWith(Constants.MultipleDuration_Prefix); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java new file mode 100644 index 000000000..19d792327 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDatePeriodExtractor.java @@ -0,0 +1,464 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class BaseDatePeriodExtractor implements IDateTimeExtractor { + + private final IDatePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATEPERIOD; + } + + public BaseDatePeriodExtractor(IDatePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(matchSimpleCases(input)); + + List simpleCasesResults = Token.mergeAllTokens(tokens, input, getExtractorName()); + List ordinalExtractions = config.getOrdinalExtractor().extract(input); + + tokens.addAll(mergeTwoTimePoints(input, reference)); + tokens.addAll(matchDuration(input, reference)); + tokens.addAll(singleTimePointWithPatterns(input, ordinalExtractions, reference)); + tokens.addAll(matchComplexCases(input, simpleCasesResults, reference)); + tokens.addAll(matchYearPeriod(input, reference)); + tokens.addAll(matchOrdinalNumberWithCenturySuffix(input, ordinalExtractions)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + private List matchSimpleCases(String input) { + List results = new ArrayList<>(); + + for (Pattern regex : config.getSimpleCasesRegexes()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + + for (Match match : matches) { + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), match.value)).findFirst(); + + if (matchYear.isPresent() && matchYear.get().length == match.length) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(matchYear.get()); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + continue; + } + } + + // handle single year which is surrounded by '-' at both sides, e.g., a single year falls in a GUID + if (match.length == Constants.FourDigitsYearLength && + RegExpUtility.getMatches(this.config.getYearRegex(), match.value).length > 0 && + infixBoundaryCheck(match, input)) { + String subStr = input.substring(match.index - 1, match.index - 1 + 6); + if (RegExpUtility.getMatches(this.config.getIllegalYearRegex(), subStr).length > 0) { + continue; + } + } + + results.add(new Token(match.index, match.index + match.length)); + } + + } + + return results; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference) { + List ers = config.getDatePointExtractor().extract(input, reference); + + // Handle "now" + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), input); + if (matches.length != 0) { + for (Match match : matches) { + ers.add(new ExtractResult(match.index, match.length, match.value, Constants.SYS_DATETIME_DATE)); + } + + ers.sort(Comparator.comparingInt(arg -> arg.getStart())); + } + + return mergeMultipleExtractions(input, ers); + } + + private List mergeMultipleExtractions(String input, List extractionResults) { + List results = new ArrayList<>(); + + Metadata metadata = new Metadata() { + { + setPossiblyIncludePeriodEnd(true); + } + }; + + if (extractionResults.size() <= 1) { + return results; + } + + int idx = 0; + + while (idx < extractionResults.size() - 1) { + ExtractResult thisResult = extractionResults.get(idx); + ExtractResult nextResult = extractionResults.get(idx + 1); + + int middleBegin = thisResult.getStart() + thisResult.getLength(); + int middleEnd = nextResult.getStart(); + if (middleBegin >= middleEnd) { + idx++; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + + if (RegexExtension.isExactMatch(config.getTillRegex(), middleStr, true)) { + int periodBegin = thisResult.getStart(); + int periodEnd = nextResult.getStart() + nextResult.getLength(); + + // handle "from/between" together with till words (till/until/through...) + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex fromIndex = config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + + if (fromIndex.getResult()) { + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + } + + results.add(new Token(periodBegin, periodEnd, metadata)); + + // merge two tokens here, increase the index by two + idx += 2; + continue; + } + + boolean hasConnectorToken = config.hasConnectorToken(middleStr); + if (hasConnectorToken) { + int periodBegin = thisResult.getStart(); + int periodEnd = nextResult.getStart() + nextResult.getLength(); + + // handle "between...and..." case + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex beforeIndex = config.getBetweenTokenIndex(beforeStr); + + if (beforeIndex.getResult()) { + periodBegin = beforeIndex.getIndex(); + results.add(new Token(periodBegin, periodEnd, metadata)); + + // merge two tokens here, increase the index by two + idx += 2; + continue; + } + } + idx++; + } + + return results; + } + + private List matchDuration(String input, LocalDateTime reference) { + + List results = new ArrayList<>(); + + List durations = new ArrayList<>(); + Iterable durationExtractions = config.getDurationExtractor().extract(input, reference); + + for (ExtractResult durationExtraction : durationExtractions) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), durationExtraction.getText())).findFirst(); + if (match.isPresent()) { + durations.add(new Token(durationExtraction.getStart(), durationExtraction.getStart() + durationExtraction.getLength())); + } + } + + for (Token duration : durations) { + String beforeStr = input.substring(0, duration.getStart()).toLowerCase(); + String afterStr = input.substring(duration.getStart() + duration.getLength()).toLowerCase(); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + // within "Days/Weeks/Months/Years" should be handled as dateRange here + // if duration contains "Seconds/Minutes/Hours", it should be treated as datetimeRange + ConditionalMatch match = RegexExtension.matchEnd(config.getWithinNextPrefixRegex(), beforeStr, true); + + if (match.getSuccess()) { + int startToken = match.getMatch().get().index; + String tokenString = input.substring(duration.getStart(), duration.getEnd()); + Match matchDate = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), tokenString)).findFirst().orElse(null); + Match matchTime = Arrays.stream(RegExpUtility.getMatches(config.getTimeUnitRegex(), tokenString)).findFirst().orElse(null); + + if (matchDate != null && matchTime == null) { + results.add(new Token(startToken, duration.getEnd())); + continue; + } + } + + // Match prefix + match = RegexExtension.matchEnd(config.getPastRegex(), beforeStr, true); + + int index = -1; + + if (match.getSuccess()) { + index = match.getMatch().get().index; + } + + if (index < 0) { + // For cases like "next five days" + match = RegexExtension.matchEnd(config.getFutureRegex(), beforeStr, true); + + if (match.getSuccess()) { + index = match.getMatch().get().index; + } + } + + if (index >= 0) { + String prefix = beforeStr.substring(0, index).trim(); + String durationText = input.substring(duration.getStart(), duration.getStart() + duration.getLength()); + List numbersInPrefix = config.getCardinalExtractor().extract(prefix); + List numbersInDuration = config.getCardinalExtractor().extract(durationText); + + // Cases like "2 upcoming days", should be supported here + // Cases like "2 upcoming 3 days" is invalid, only extract "upcoming 3 days" by default + if (!numbersInPrefix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult lastNumber = numbersInPrefix.stream() + .sorted(Comparator.comparingInt(x -> x.getStart() + x.getLength())) + .reduce((acc, item) -> item).orElse(null); + + // Prefix should ends with the last number + if (lastNumber.getStart() + lastNumber.getLength() == prefix.length()) { + results.add(new Token(lastNumber.getStart(), duration.getEnd())); + } + + } else { + results.add(new Token(index, duration.getEnd())); + } + + continue; + } + + // Match suffix + match = RegexExtension.matchBegin(config.getPastRegex(), afterStr, true); + if (match.getSuccess()) { + int matchLength = match.getMatch().get().index + match.getMatch().get().length; + results.add(new Token(duration.getStart(), duration.getEnd() + matchLength)); + continue; + } + + match = RegexExtension.matchBegin(config.getFutureSuffixRegex(), afterStr, true); + if (match.getSuccess()) { + int matchLength = match.getMatch().get().index + match.getMatch().get().length; + results.add(new Token(duration.getStart(), duration.getEnd() + matchLength)); + } + } + + return results; + } + + // 1. Extract the month of date, week of date to a date range + // 2. Extract cases like within two weeks from/before today/tomorrow/yesterday + private List singleTimePointWithPatterns(String input, List ordinalExtractions, LocalDateTime reference) { + List results = new ArrayList<>(); + + List datePoints = config.getDatePointExtractor().extract(input, reference); + + // For cases like "week of the 18th" + datePoints.addAll(ordinalExtractions.stream().filter(o -> datePoints.stream().noneMatch(er -> er.isOverlap(o))).collect(Collectors.toList())); + + if (datePoints.size() < 1) { + return results; + } + + for (ExtractResult er : datePoints) { + if (er.getStart() != null && er.getLength() != null) { + String beforeStr = input.substring(0, er.getStart()); + results.addAll(getTokenForRegexMatching(beforeStr, config.getWeekOfRegex(), er)); + results.addAll(getTokenForRegexMatching(beforeStr, config.getMonthOfRegex(), er)); + + // Cases like "3 days from today", "2 weeks before yesterday", "3 months after tomorrow" + if (isRelativeDurationDate(er)) { + results.addAll(getTokenForRegexMatching(beforeStr, config.getLessThanRegex(), er)); + results.addAll(getTokenForRegexMatching(beforeStr, config.getMoreThanRegex(), er)); + + // For "within" case, only duration with relative to "today" or "now" makes sense + // Cases like "within 3 days from yesterday/tomorrow" does not make any sense + if (isDateRelativeToNowOrToday(er)) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getWithinNextPrefixRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + boolean isNext = !StringUtility.isNullOrEmpty(match.get().getGroup(Constants.NextGroupName).value); + + // For "within" case + // Cases like "within the next 5 days before today" is not acceptable + if (!(isNext && isAgoRelativeDurationDate(er))) { + results.addAll(getTokenForRegexMatching(beforeStr, config.getWithinNextPrefixRegex(), er)); + } + } + } + } + } + } + + return results; + } + + private boolean isAgoRelativeDurationDate(ExtractResult er) { + return Arrays.stream(RegExpUtility.getMatches(config.getAgoRegex(), er.getText())).findAny().isPresent(); + } + + // Cases like "3 days from today", "2 weeks before yesterday", "3 months after + // tomorrow" + private boolean isRelativeDurationDate(ExtractResult er) { + boolean isAgo = Arrays.stream(RegExpUtility.getMatches(config.getAgoRegex(), er.getText())).findAny().isPresent(); + boolean isLater = Arrays.stream(RegExpUtility.getMatches(config.getLaterRegex(), er.getText())).findAny().isPresent(); + + return isAgo || isLater; + } + + private List getTokenForRegexMatching(String source, Pattern regex, ExtractResult er) { + List results = new ArrayList<>(); + Match match = Arrays.stream(RegExpUtility.getMatches(regex, source)).findFirst().orElse(null); + if (match != null && source.trim().endsWith(match.value.trim())) { + int startIndex = source.lastIndexOf(match.value); + results.add(new Token(startIndex, er.getStart() + er.getLength())); + } + + return results; + } + + // Complex cases refer to the combination of daterange and datepoint + // For Example: from|between {DateRange|DatePoint} to|till|and {DateRange|DatePoint} + private List matchComplexCases(String input, List simpleCasesResults, LocalDateTime reference) { + List ers = config.getDatePointExtractor().extract(input, reference); + + // Filter out DateRange results that are part of DatePoint results + // For example, "Feb 1st 2018" => "Feb" and "2018" should be filtered out here + List simpleErs = simpleCasesResults.stream().filter(simpleDateRange -> filterErs(simpleDateRange, ers)).collect(Collectors.toList()); + ers.addAll(simpleErs); + + List results = ers.stream().sorted((o1, o2) -> o1.getStart().compareTo(o2.getStart())).collect(Collectors.toList()); + + return mergeMultipleExtractions(input, results); + } + + private boolean filterErs(ExtractResult simpleDateRange, List ers) { + return !ers.stream().anyMatch(datePoint -> compareErs(simpleDateRange, datePoint)); + } + + private boolean compareErs(ExtractResult simpleDateRange, ExtractResult datePoint) { + return datePoint.getStart() <= simpleDateRange.getStart() && datePoint.getStart() + datePoint.getLength() >= simpleDateRange.getStart() + simpleDateRange.getLength(); + } + + private List matchYearPeriod(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + Metadata metadata = new Metadata() { + { + setPossiblyIncludePeriodEnd(true); + } + }; + + Match[] matches = RegExpUtility.getMatches(config.getYearPeriodRegex(), input); + for (Match match : matches) { + Match matchYear = Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), match.value)).findFirst().orElse(null); + if (matchYear != null && matchYear.length == match.value.length()) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(matchYear); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + continue; + } + // Possibly include period end only apply for cases like "2014-2018", which are not single year cases + metadata.setPossiblyIncludePeriodEnd(false); + } else { + Match[] yearMatches = RegExpUtility.getMatches(config.getYearRegex(), match.value); + boolean isValidYear = true; + for (Match yearMatch : yearMatches) { + int year = ((BaseDateExtractor)config.getDatePointExtractor()).getYearFromText(yearMatch); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + isValidYear = false; + break; + } + } + + if (!isValidYear) { + continue; + } + + } + + results.add(new Token(match.index, match.index + match.length, metadata)); + } + + return results; + } + + private List matchOrdinalNumberWithCenturySuffix(String input, List ordinalExtractions) { + List results = new ArrayList<>(); + + for (ExtractResult er : ordinalExtractions) { + if (er.getStart() + er.getLength() >= input.length()) { + continue; + } + + String afterStr = input.substring(er.getStart() + er.getLength()); + String trimmedAfterStr = afterStr.trim(); + int whiteSpacesCount = afterStr.length() - trimmedAfterStr.length(); + int afterStringOffset = er.getStart() + er.getLength() + whiteSpacesCount; + + Match match = Arrays.stream(RegExpUtility.getMatches(config.getCenturySuffixRegex(), trimmedAfterStr)).findFirst().orElse(null); + + if (match != null) { + results.add(new Token(er.getStart(), afterStringOffset + match.index + match.length)); + } + } + + return results; + } + + private boolean isDateRelativeToNowOrToday(ExtractResult input) { + for (String flagWord : config.getDurationDateRestrictions()) { + if (input.getText().contains(flagWord)) { + return true; + } + } + + return false; + } + + // check whether the match is an infix of source + private boolean infixBoundaryCheck(Match match, String source) { + boolean isMatchInfixOfSource = false; + if (match.index > 0 && match.index + match.length < source.length()) { + if (source.substring(match.index, match.index + match.length).equals(match.value)) { + isMatchInfixOfSource = true; + } + } + + return isMatchInfixOfSource; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java new file mode 100644 index 000000000..13362108b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeAltExtractor.java @@ -0,0 +1,598 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; +import java.util.function.Function; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Stream; + +public class BaseDateTimeAltExtractor implements IDateTimeListExtractor { + + private final IDateTimeAltExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIMEALT; + } + + public BaseDateTimeAltExtractor(IDateTimeAltExtractorConfiguration config) { + this.config = config; + } + + public List extract(List extractResults, String text) { + return this.extract(extractResults, text, LocalDateTime.now()); + } + + @Override + public List extract(List extractResults, String text, LocalDateTime reference) { + return extractAlt(extractResults, text, reference); + } + + // Modify time entity to an alternative DateTime expression, such as "8pm" in "Monday 7pm or 8pm" + // or "Thursday" in "next week on Tuesday or Thursday" + private List extractAlt(List extractResults, String text, LocalDateTime reference) { + List ers = addImplicitDates(extractResults, text); + + // Sort the extracted results for the further sequential process. + ers.sort(Comparator.comparingInt(erA -> erA.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + List altErs = getAltErsWithSameParentText(ers, i, text); + + if (altErs.size() == 0) { + i++; + continue; + } + + int j = i + altErs.size() - 1; + + int parentTextStart = ers.get(i).getStart(); + int parentTextLen = ers.get(j).getStart() + ers.get(j).getLength() - ers.get(i).getStart(); + String parentText = text.substring(parentTextStart, parentTextStart + parentTextLen); + + boolean success = extractAndApplyMetadata(altErs, parentText); + + if (success) { + i = j + 1; + } else { + i++; + } + } + + ers = resolveImplicitRelativeDatePeriod(ers, text); + ers = pruneInvalidImplicitDate(ers); + + return ers; + } + + private List getAltErsWithSameParentText(List ers, int startIndex, String text) { + int pivot = startIndex + 1; + HashSet types = new HashSet(); + types.add(ers.get(startIndex).getType()); + + while (pivot < ers.size()) { + // Currently only support merge two kinds of types + if (!types.contains(ers.get(pivot).getType()) && types.size() > 1) { + break; + } + + // Check whether middle string is a connector + int middleBegin = ers.get(pivot - 1).getStart() + ers.get(pivot - 1).getLength(); + int middleEnd = ers.get(pivot).getStart(); + + if (!isConnectorOrWhiteSpace(middleBegin, middleEnd, text)) { + break; + } + + int prefixEnd = ers.get(pivot - 1).getStart(); + String prefixStr = text.substring(0, prefixEnd); + + if (isEndsWithRangePrefix(prefixStr)) { + break; + } + + if (isSupportedAltEntitySequence(ers.subList(startIndex, startIndex + (pivot - startIndex + 1)))) { + types.add(ers.get(pivot).getType()); + pivot++; + } else { + break; + } + } + + pivot--; + + if (startIndex == pivot) { + startIndex++; + } + + return ers.subList(startIndex, startIndex + (pivot - startIndex + 1)); + } + + private List addImplicitDates(List originalErs, String text) { + List result = new ArrayList<>(); + + Match[] implicitDateMatches = RegExpUtility.getMatches(config.getDayRegex(), text); + int i = 0; + originalErs.sort(Comparator.comparingInt(er -> er.getStart())); + + for (Match dateMatch : implicitDateMatches) { + boolean notBeContained = true; + while (i < originalErs.size()) { + if (originalErs.get(i).getStart() <= dateMatch.index && originalErs.get(i).getStart() + originalErs.get(i).getLength() >= dateMatch.index + dateMatch.length) { + notBeContained = false; + break; + } + + if (originalErs.get(i).getStart() + originalErs.get(i).getLength() < dateMatch.index + dateMatch.length) { + i++; + } else if (originalErs.get(i).getStart() + originalErs.get(i).getLength() >= dateMatch.index + dateMatch.length) { + break; + } + } + + ExtractResult dateEr = new ExtractResult( + dateMatch.index, + dateMatch.length, + dateMatch.value, + Constants.SYS_DATETIME_DATE); + + dateEr.setData(getExtractorName()); + if (notBeContained) { + result.add(dateEr); + } else if (i + 1 < originalErs.size()) { + // For cases like "I am looking at 18 and 19 June" + // in which "18" is wrongly recognized as time without context. + ExtractResult nextEr = originalErs.get(i + 1); + if (nextEr.getType().equals(Constants.SYS_DATETIME_DATE) && + originalErs.get(i).getText().equals(dateEr.getText()) && + isConnectorOrWhiteSpace(dateEr.getStart() + dateEr.getLength(), nextEr.getStart(), text)) { + result.add(dateEr); + originalErs.remove(i); + } + } + } + + result.addAll(originalErs); + result.sort(Comparator.comparingInt(er -> er.getStart())); + + return result; + } + + private List pruneInvalidImplicitDate(List ers) { + ers.removeIf(er -> { + if (er.getData() != null && er.getType().equals(Constants.SYS_DATETIME_DATE) && er.getData().equals(getExtractorName())) { + return true; + } + return false; + }); + + return ers; + } + + // Resolve cases like "this week or next". + private List resolveImplicitRelativeDatePeriod(List ers, String text) { + List relativeTermsMatches = new ArrayList<>(); + for (Pattern regex : config.getRelativePrefixList()) { + relativeTermsMatches.addAll(Arrays.asList(RegExpUtility.getMatches(regex, text))); + } + + List results = new ArrayList<>(); + + List relativeDatePeriodErs = new ArrayList<>(); + int i = 0; + for (ExtractResult result : ers.toArray(new ExtractResult[0])) { + if (!result.getType().equals(Constants.SYS_DATETIME_DATETIMEALT)) { + int resultEnd = result.getStart() + result.getLength(); + for (Match relativeTermsMatch : relativeTermsMatches) { + int relativeTermsMatchEnd = relativeTermsMatch.index + relativeTermsMatch.length; + if (relativeTermsMatch.index > resultEnd || relativeTermsMatchEnd < result.getStart()) { + // Check whether middle string is a connector + int middleBegin = relativeTermsMatch.index > resultEnd ? resultEnd : relativeTermsMatchEnd; + int middleEnd = relativeTermsMatch.index > resultEnd ? relativeTermsMatch.index : result.getStart(); + String middleStr = text.substring(middleBegin, middleEnd).trim().toLowerCase(); + Match[] orTermMatches = RegExpUtility.getMatches(config.getOrRegex(), middleStr); + if (orTermMatches.length == 1 && orTermMatches[0].index == 0 && orTermMatches[0].length == middleStr.length()) { + int parentTextStart = relativeTermsMatch.index > resultEnd ? result.getStart() : relativeTermsMatch.index; + int parentTextEnd = relativeTermsMatch.index > resultEnd ? relativeTermsMatchEnd : resultEnd; + String parentText = text.substring(parentTextStart, parentTextEnd); + + ExtractResult contextErs = new ExtractResult(); + for (Pattern regex : config.getRelativePrefixList()) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, result.getText())).findFirst(); + if (match.isPresent()) { + int matchEnd = match.get().index + match.get().length; + contextErs = new ExtractResult( + matchEnd, + result.getLength() - matchEnd, + result.getText().substring(matchEnd, result.getLength()), + Constants.ContextType_RelativeSuffix); + break; + } + } + + Map customData = new LinkedHashMap<>(); + customData.put(Constants.SubType, result.getType()); + customData.put(ExtendedModelResult.ParentTextKey, parentText); + customData.put(Constants.Context, contextErs); + + relativeDatePeriodErs.add(new ExtractResult( + relativeTermsMatch.index, + relativeTermsMatch.length, + relativeTermsMatch.value, + Constants.SYS_DATETIME_DATETIMEALT, + customData)); + + Map resultData = new LinkedHashMap<>(); + resultData.put(Constants.SubType, result.getType()); + resultData.put(ExtendedModelResult.ParentTextKey, parentText); + + result.setData(resultData); + result.setType(Constants.SYS_DATETIME_DATETIMEALT); + ers.set(i, result); + } + } + } + } + i++; + } + + results.addAll(ers); + results.addAll(relativeDatePeriodErs); + results.sort(Comparator.comparingInt(er -> er.getStart())); + + return results; + } + + private boolean isConnectorOrWhiteSpace(int start, int end, String text) { + if (end <= start) { + return false; + } + + String middleStr = text.substring(start, end).trim().toLowerCase(); + + if (StringUtility.isNullOrEmpty(middleStr)) { + return true; + } + + Match[] orTermMatches = RegExpUtility.getMatches(config.getOrRegex(), middleStr); + + return orTermMatches.length == 1 && orTermMatches[0].index == 0 && orTermMatches[0].length == middleStr.length(); + } + + private boolean isEndsWithRangePrefix(String prefixText) { + return RegexExtension.matchEnd(config.getRangePrefixRegex(), prefixText, true).getSuccess(); + } + + private boolean extractAndApplyMetadata(List extractResults, String parentText) { + boolean success = extractAndApplyMetadata(extractResults, parentText, false); + + if (!success) { + success = extractAndApplyMetadata(extractResults, parentText, true); + } + + if (!success && shouldApplyParentText(extractResults)) { + success = applyParentText(extractResults, parentText); + } + + return success; + } + + private boolean extractAndApplyMetadata(List extractResults, String parentText, boolean reverse) { + if (reverse) { + Collections.reverse(extractResults); + } + + boolean success = false; + + // Currently, we support alt entity sequence only when the second alt entity to the last alt entity share the same type + if (isSupportedAltEntitySequence(extractResults)) { + HashMap metadata = extractMetadata(extractResults.stream().findFirst().get(), parentText, extractResults); + HashMap metadataCandidate = null; + + int i = 0; + while (i < extractResults.size()) { + if (metadata == null) { + break; + } + + int j = i + 1; + + while (j < extractResults.size()) { + metadataCandidate = extractMetadata(extractResults.get(j), parentText, extractResults); + + // No context extracted, the context would follow the previous one + // Such as "Wednesday" in "next Tuesday or Wednesday" + if (metadataCandidate == null) { + j++; + } else { + // Current extraction has context, the context would not follow the previous ones + // Such as "Wednesday" in "next Monday or Tuesday or previous Wednesday" + break; + } + } + List ersShareContext = extractResults.subList(i, j); + applyMetadata(ersShareContext, metadata, parentText); + metadata = metadataCandidate; + + i = j; + success = true; + } + } + + return success; + } + + private boolean shouldApplyParentText(List extractResults) { + boolean shouldApply = false; + + if (isSupportedAltEntitySequence(extractResults)) { + String firstEntityType = extractResults.stream().findFirst().get().getType(); + String lastEntityType = extractResults.get(extractResults.size() - 1).getType(); + + if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + shouldApply = true; // "11/20 or 11/22" + } else if (firstEntityType.equals(Constants.SYS_DATETIME_TIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + shouldApply = true; // "7 oclock or 8 oclock" + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_DATETIME)) { + shouldApply = true; // "Monday 1pm or Tuesday 2pm" + } + } + + return shouldApply; + } + + private boolean applyParentText(List extractResults, String parentText) { + boolean success = false; + + if (isSupportedAltEntitySequence(extractResults)) { + for (int i = 0; i < extractResults.size(); i++) { + ExtractResult extractResult = extractResults.get(i); + Map metadata = createMetadata(extractResult.getType(), parentText, null); + Map data = mergeMetadata(extractResult.getData(), metadata); + extractResult.setData(data); + extractResult.setType(getExtractorName()); + extractResults.set(i, extractResult); + } + + success = true; + } + + return success; + } + + private boolean isSupportedAltEntitySequence(List altEntities) { + Stream subSeq = altEntities.stream().skip(1); + List entityTypes = new ArrayList<>(); + + for (ExtractResult er : subSeq.toArray(ExtractResult[]::new)) { + if (!entityTypes.contains(er.getType())) { + entityTypes.add(er.getType()); + } + } + + return entityTypes.size() == 1; + } + + // This method is to extract metadata from the targeted ExtractResult + // For cases like "next week Monday or Tuesday or previous Wednesday", ExtractMethods can be more than one + private HashMap extractMetadata(ExtractResult targetEr, String parentText, List ers) { + HashMap metadata = null; + ArrayList>> extractMethods = getExtractMethods(targetEr.getType(), ers.get(ers.size() - 1).getType()); + BiConsumer postProcessMethod = getPostProcessMethod(targetEr.getType(), ers.get(ers.size() - 1).getType()); + ExtractResult contextEr = extractContext(targetEr, extractMethods, postProcessMethod); + + if (shouldCreateMetadata(ers, contextEr)) { + metadata = createMetadata(targetEr.getType(), parentText, contextEr); + } + + return metadata; + } + + private ExtractResult extractContext(ExtractResult er, ArrayList>> extractMethods, BiConsumer postProcessMethod) { + ExtractResult contextEr = null; + + for (Function> extractMethod : extractMethods) { + List contextErCandidates = extractMethod.apply(er.getText()); + if (contextErCandidates.size() == 1) { + contextEr = contextErCandidates.get(contextErCandidates.size() - 1); + break; + } + } + + if (contextEr != null && postProcessMethod != null) { + postProcessMethod.accept(contextEr, er); + } + + if (contextEr != null && StringUtility.isNullOrEmpty(contextEr.getText())) { + contextEr = null; + } + + return contextEr; + } + + private boolean shouldCreateMetadata(List originalErs, ExtractResult contextEr) { + // For alternative entities sequence which are all DatePeriod, we should create metadata even if context is null + return (contextEr != null || + (originalErs.get(0).getType() == Constants.SYS_DATETIME_DATEPERIOD && originalErs.get(originalErs.size() - 1).getType() == Constants.SYS_DATETIME_DATEPERIOD)); + } + + private void applyMetadata(List ers, HashMap metadata, String parentText) { + // The first extract results don't need any context + ExtractResult first = ers.stream().findFirst().orElse(null); + + // Share the timeZone info + Map metaDataOrigin = (HashMap)first.getData(); + if (metaDataOrigin != null && metaDataOrigin.containsKey(Constants.SYS_DATETIME_TIMEZONE)) { + metadata.put(Constants.SYS_DATETIME_TIMEZONE, metaDataOrigin.get(Constants.SYS_DATETIME_TIMEZONE)); + } + + HashMap metadataWithoutContext = createMetadata(first.getType(), parentText, null); + first.setData(mergeMetadata(first.getData(), metadataWithoutContext)); + first.setType(getExtractorName()); + ers.set(0, first); + + for (int i = 1; i < ers.size(); i++) { + ExtractResult er = ers.get(i); + er.setData(mergeMetadata(ers.get(i).getData(), metadata)); + er.setType(getExtractorName()); + ers.set(i, er); + } + } + + private Map mergeMetadata(Object originalMetadata, Map newMetadata) { + Map result = new HashMap<>(); + + if (originalMetadata instanceof Map) { + result = (Map)originalMetadata; + } + + if (originalMetadata == null) { + result = newMetadata; + } else { + for (Map.Entry data : newMetadata.entrySet()) { + result.put(data.getKey(), data.getValue()); + } + } + + return result; + } + + private HashMap createMetadata(String subType, String parentText, ExtractResult contextEr) { + HashMap data = new HashMap<>(); + + if (!StringUtility.isNullOrEmpty(subType)) { + data.put(Constants.SubType, subType); + } + + if (!StringUtility.isNullOrEmpty(parentText)) { + data.put(ExtendedModelResult.ParentTextKey, parentText); + } + + if (contextEr != null) { + data.put(Constants.Context, contextEr); + } + + return data; + } + + private List extractRelativePrefixContext(String entityText) { + List results = new ArrayList<>(); + + for (Pattern pattern : config.getRelativePrefixList()) { + Matcher match = pattern.matcher(entityText); + + if (match.find()) { + ExtractResult er = new ExtractResult(); + er.setText(match.group()); + er.setStart(match.start()); + er.setLength(match.end() - match.start()); + er.setType(Constants.ContextType_RelativePrefix); + results.add(er); + } + } + + return results; + } + + private List extractAmPmContext(String entityText) { + List results = new ArrayList<>(); + for (Pattern pattern : config.getAmPmRegexList()) { + Matcher match = pattern.matcher(entityText); + if (match.find()) { + ExtractResult er = new ExtractResult(); + er.setText(match.group()); + er.setStart(match.start()); + er.setLength(match.end() - match.start()); + er.setType(Constants.ContextType_AmPm); + results.add(er); + } + } + + return results; + } + + private BiConsumer getPostProcessMethod(String firstEntityType, String lastEntityType) { + if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + return (contextEr, originalEr) -> { + contextEr.setText(originalEr.getText().substring(0, contextEr.getStart()) + originalEr.getText().substring(contextEr.getStart() + contextEr.getLength())); + contextEr.setType(Constants.ContextType_RelativeSuffix); + }; + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + return (contextEr, originalEr) -> { + contextEr.setText(originalEr.getText().substring(0, contextEr.getStart())); + }; + } + + return null; + } + + private ArrayList>> getExtractMethods(String firstEntityType, String lastEntityType) { + ArrayList>> methods = new ArrayList<>(); + + if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + + // "Monday 7pm or 8pm" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + + // "next week Monday or Tuesday", "previous Monday or Wednesday" + methods.add(config.getDatePeriodExtractor()::extract); + methods.add(this::extractRelativePrefixContext); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_TIME) && lastEntityType.equals(Constants.SYS_DATETIME_TIME)) { + + // "in the morning at 7 oclock or 8 oclock" + methods.add(this::extractAmPmContext); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIME) && lastEntityType.equals(Constants.SYS_DATETIME_DATETIME)) { + + // "next week Mon 9am or Tue 1pm" + methods.add(config.getDatePeriodExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + + // "Monday 7-8 am or 9-10am" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + + // For alt entities that are all DatePeriod, no need to share context + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD) && lastEntityType.equals(Constants.SYS_DATETIME_DATE)) { + + // "Tuesday or Wednesday morning" + methods.add(config.getDateExtractor()::extract); + + } else if (firstEntityType.equals(Constants.SYS_DATETIME_DATE) && lastEntityType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + + // "Monday this week or next week" + methods.add(config.getDatePeriodExtractor()::extract); + + } + + return methods; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java new file mode 100644 index 000000000..8fbdd8b89 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimeExtractor.java @@ -0,0 +1,292 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; + +public class BaseDateTimeExtractor implements IDateTimeExtractor { + private static final String SYS_NUM_INTEGER = com.microsoft.recognizers.text.number.Constants.SYS_NUM; + + private final IDateTimeExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIME; + } + + public BaseDateTimeExtractor(IDateTimeExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + tokens.addAll(mergeDateAndTime(input, reference)); + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(timeOfTodayBefore(input, reference)); + tokens.addAll(timeOfTodayAfter(input, reference)); + tokens.addAll(specialTimeOfDate(input, reference)); + tokens.addAll(durationWithBeforeAndAfter(input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List durationWithBeforeAndAfter(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + + List ers = this.config.getDurationExtractor().extract(input, reference); + for (ExtractResult er : ers) { + // if it is a multiple duration and its type is equal to Date then skip it. + if (er.getData() != null && er.getData().toString() == Constants.MultipleDuration_Date) { + continue; + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getUnitRegex(), er.getText())).findFirst(); + if (!match.isPresent()) { + continue; + } + + ret = AgoLaterUtil.extractorDurationWithBeforeAndAfter(input, er, ret, this.config.getUtilityConfiguration()); + } + + return ret; + } + + public List specialTimeOfDate(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getDatePointExtractor().extract(input, reference); + + // handle "the end of the day" + for (ExtractResult er : ers) { + String beforeStr = input.substring(0, (er != null) ? er.getStart() : 0); + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSpecificEndOfRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, (er != null) ? er.getStart() + er.getLength() : 0)); + } else { + String afterStr = input.substring((er != null) ? er.getStart() + er.getLength() : 0); + + match = Arrays.stream(RegExpUtility.getMatches(this.config.getSpecificEndOfRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token( + (er != null) ? er.getStart() : 0, + ((er != null) ? er.getStart() + er.getLength() : 0) + ((match != null) ? match.get().index + match.get().length : 0))); + } + } + } + + // Handle "eod, end of day" + Match[] matches = RegExpUtility.getMatches(config.getUnspecificEndOfRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Parses a specific time of today, tonight, this afternoon, like "seven this afternoon" + public List timeOfTodayAfter(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + + List ers = this.config.getTimePointExtractor().extract(input, reference); + + for (ExtractResult er : ers) { + String afterStr = input.substring(er.getStart() + er.getLength()); + + if (StringUtility.isNullOrEmpty(afterStr)) { + continue; //@here + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfTodayAfterRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + int begin = er.getStart(); + int end = er.getStart() + er.getLength() + match.get().length; + ret.add(new Token(begin, end)); + } + } + + Match[] matches = RegExpUtility.getMatches(this.config.getSimpleTimeOfTodayAfterRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + public List timeOfTodayBefore(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getTimePointExtractor().extract(input, reference); + for (ExtractResult er : ers) { + String beforeStr = input.substring(0, (er != null) ? er.getStart() : 0); + + // handle "this morningh at 7am" + ConditionalMatch innerMatch = RegexExtension.matchBegin(this.config.getTimeOfDayRegex(), er.getText(), true); + if (innerMatch.getSuccess()) { + beforeStr = input.substring(0, ((er != null) ? er.getStart() : 0) + innerMatch.getMatch().get().length); + } + + if (StringUtility.isNullOrEmpty(beforeStr)) { + continue; + } + + Match match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfTodayBeforeRegex(), beforeStr)).findFirst().orElse(null); + if (match != null) { + int begin = match.index; + int end = er.getStart() + er.getLength(); + ret.add(new Token(begin, end)); + } + } + + Match[] matches = RegExpUtility.getMatches(this.config.getSimpleTimeOfTodayBeforeRegex(), input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Match "now" + public List basicRegexMatch(String input) { + List ret = new ArrayList<>(); + input = input.trim().toLowerCase(); + + // Handle "now" + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), input); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + // Merge a Date entity and a Time entity, like "at 7 tomorrow" + public List mergeDateAndTime(String input, LocalDateTime reference) { + List ret = new ArrayList<>(); + List dateErs = this.config.getDatePointExtractor().extract(input, reference); + if (dateErs.size() == 0) { + return ret; + } + + List timeErs = this.config.getTimePointExtractor().extract(input, reference); + Match[] timeNumMatches = RegExpUtility.getMatches(config.getNumberAsTimeRegex(), input); + + if (timeErs.size() == 0 && timeNumMatches.length == 0) { + return ret; + } + + List ers = dateErs; + ers.addAll(timeErs); + + // handle cases which use numbers as time points + // only enabled in CalendarMode + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + List numErs = new ArrayList<>(); + for (Match timeNumMatch : timeNumMatches) { + ExtractResult node = new ExtractResult(timeNumMatch.index, timeNumMatch.length, timeNumMatch.value, SYS_NUM_INTEGER); + numErs.add(node); + } + ers.addAll(numErs); + } + + ers.sort(Comparator.comparingInt(erA -> erA.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + int j = i + 1; + while (j < ers.size() && ers.get(i).isOverlap(ers.get(j))) { + j++; + } + if (j >= ers.size()) { + break; + } + + ExtractResult ersI = ers.get(i); + ExtractResult ersJ = ers.get(j); + if (ersI.getType() == Constants.SYS_DATETIME_DATE && ersJ.getType() == Constants.SYS_DATETIME_TIME || + ersI.getType() == Constants.SYS_DATETIME_TIME && ersJ.getType() == Constants.SYS_DATETIME_DATE || + ersI.getType() == Constants.SYS_DATETIME_DATE && ersJ.getType() == SYS_NUM_INTEGER) { + int middleBegin = ersI != null ? ersI.getStart() + ersI.getLength() : 0; + int middleEnd = ersJ != null ? ersJ.getStart() : 0; + if (middleBegin > middleEnd) { + i = j + 1; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + boolean valid = false; + // for cases like "tomorrow 3", "tomorrow at 3" + if (ersJ.getType() == SYS_NUM_INTEGER) { + Optional matches = Arrays.stream(RegExpUtility.getMatches(this.config.getDateNumberConnectorRegex(), input)).findFirst(); + if (StringUtility.isNullOrEmpty(middleStr) || matches.isPresent()) { + valid = true; + } + } else { + // For case like "3pm or later on monday" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSuffixAfterRegex(), middleStr)).findFirst(); + if (match.isPresent()) { + middleStr = middleStr.substring(match.get().index + match.get().length).trim(); + } + + if (!(match.isPresent() && middleStr.isEmpty())) { + if (this.config.isConnector(middleStr)) { + valid = true; + } + } + } + + if (valid) { + int begin = ersI.getStart(); + int end = ersJ.getStart() + ersJ.getLength(); + ret.add(new Token(begin, end)); + i = j + 1; + continue; + } + } + i = j; + } + + // Handle "in the afternoon" at the end of entity + for (int idx = 0; idx < ret.size(); idx++) { + Token idxToken = ret.get(idx); + String afterStr = input.substring(idxToken.getEnd()); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + ret.set(idx, new Token(idxToken.getStart(), idxToken.getEnd() + match.get().length)); + } + } + + // Handle "day" prefixes + for (int idx = 0; idx < ret.size(); idx++) { + Token idxToken = ret.get(idx); + String beforeStr = input.substring(0, idxToken.getStart()); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getUtilityConfiguration().getCommonDatePrefixRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + ret.set(idx, new Token(idxToken.getStart() - match.get().length, idxToken.getEnd())); + } + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java new file mode 100644 index 000000000..d4b8b7b5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDateTimePeriodExtractor.java @@ -0,0 +1,575 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDateTimePeriodExtractor implements IDateTimeExtractor { + + private final IDateTimePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + public BaseDateTimePeriodExtractor(IDateTimePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + + // Date and time Extractions should be extracted from the text only once, and shared in the methods below, passed by value + List dateErs = config.getSingleDateExtractor().extract(input, reference); + List timeErs = config.getSingleTimeExtractor().extract(input, reference); + + tokens.addAll(matchSimpleCases(input, reference)); + tokens.addAll(mergeTwoTimePoints(input, reference, new ArrayList(dateErs), new ArrayList(timeErs))); + tokens.addAll(matchDuration(input, reference)); + tokens.addAll(matchTimeOfDay(input, reference, new ArrayList(dateErs))); + tokens.addAll(matchRelativeUnit(input)); + tokens.addAll(matchDateWithPeriodPrefix(input, reference, new ArrayList(dateErs))); + tokens.addAll(mergeDateWithTimePeriodSuffix(input, new ArrayList(dateErs), new ArrayList(timeErs))); + + List ers = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + ers = TimeZoneUtility.mergeTimeZones(ers, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return ers; + } + + private List matchSimpleCases(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + + for (Pattern regex : config.getSimpleCasesRegex()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + for (Match match : matches) { + // Is there a date before it? + boolean hasBeforeDate = false; + String beforeStr = input.substring(0, match.index); + if (!StringUtility.isNullOrEmpty(beforeStr)) { + List ers = config.getSingleDateExtractor().extract(beforeStr, reference); + if (ers.size() > 0) { + ExtractResult er = ers.get(ers.size() - 1); + int begin = er.getStart(); + String middleStr = beforeStr.substring(begin + er.getLength()).trim().toLowerCase(); + if (StringUtility.isNullOrEmpty(middleStr) || RegexExtension.isExactMatch(config.getPrepositionRegex(), middleStr, true)) { + results.add(new Token(begin, match.index + match.length)); + hasBeforeDate = true; + } + } + } + + String followedStr = input.substring(match.index + match.length); + if (!StringUtility.isNullOrEmpty(followedStr) && !hasBeforeDate) { + // Is it followed by a date? + List ers = config.getSingleDateExtractor().extract(followedStr, reference); + if (ers.size() > 0) { + ExtractResult er = ers.get(0); + int begin = er.getStart(); + int end = er.getStart() + er.getLength(); + String middleStr = followedStr.substring(0, begin).trim().toLowerCase(); + if (StringUtility.isNullOrEmpty(middleStr) || RegexExtension.isExactMatch(config.getPrepositionRegex(), middleStr, true)) { + results.add(new Token(match.index, match.index + match.length + end)); + } + } + } + } + } + + return results; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference, List dateErs, List timeErs) { + + List results = new ArrayList<>(); + List dateTimeErs = config.getSingleDateTimeExtractor().extract(input, reference); + List timePoints = new ArrayList<>(); + + // Handle the overlap problem + int j = 0; + for (ExtractResult er : dateTimeErs) { + timePoints.add(er); + + while (j < timeErs.size() && timeErs.get(j).getStart() + timeErs.get(j).getLength() < er.getStart()) { + timePoints.add(timeErs.get(j)); + j++; + } + + while (j < timeErs.size() && timeErs.get(j).isOverlap(er)) { + j++; + } + } + + for (; j < timeErs.size(); j++) { + timePoints.add(timeErs.get(j)); + } + + timePoints.sort(Comparator.comparingInt(arg -> arg.getStart())); + + // Merge "{TimePoint} to {TimePoint}", "between {TimePoint} and {TimePoint}" + int idx = 0; + while (idx < timePoints.size() - 1) { + // If both ends are Time. then this is a TimePeriod, not a DateTimePeriod + if (timePoints.get(idx).getType().equals(Constants.SYS_DATETIME_TIME) && timePoints.get(idx + 1).getType().equals(Constants.SYS_DATETIME_TIME)) { + idx++; + continue; + } + + int middleBegin = timePoints.get(idx).getStart() + timePoints.get(idx).getLength(); + int middleEnd = timePoints.get(idx + 1).getStart(); + + String middleStr = input.substring(middleBegin, middleEnd).trim(); + + // Handle "{TimePoint} to {TimePoint}" + if (RegexExtension.isExactMatch(config.getTillRegex(), middleStr, true)) { + int periodBegin = timePoints.get(idx).getStart(); + int periodEnd = timePoints.get(idx + 1).getStart() + timePoints.get(idx + 1).getLength(); + + // Handle "from" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex fromIndex = config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + if (fromIndex.getResult()) { + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + } + + results.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + + // Handle "between {TimePoint} and {TimePoint}" + if (config.hasConnectorToken(middleStr)) { + int periodBegin = timePoints.get(idx).getStart(); + int periodEnd = timePoints.get(idx + 1).getStart() + timePoints.get(idx + 1).getLength(); + + // Handle "between" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(); + + ResultIndex betweenIndex = config.getBetweenTokenIndex(beforeStr); + if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + results.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + } + idx++; + } + + // Regarding the pharse as-- {Date} {TimePeriod}, like "2015-9-23 1pm to 4" + // Or {TimePeriod} on {Date}, like "1:30 to 4 on 2015-9-23" + List timePeriodErs = config.getTimePeriodExtractor().extract(input, reference); + dateErs.addAll(timePeriodErs); + + dateErs.sort(Comparator.comparingInt(arg -> arg.getStart())); + + for (idx = 0; idx < dateErs.size() - 1; idx++) { + if (dateErs.get(idx).getType().equals(dateErs.get(idx + 1).getType())) { + continue; + } + + int midBegin = dateErs.get(idx).getStart() + dateErs.get(idx).getLength(); + int midEnd = dateErs.get(idx + 1).getStart(); + + if (midEnd - midBegin > 0) { + String midStr = input.substring(midBegin, midEnd); + if (StringUtility.isNullOrWhiteSpace(midStr) || StringUtility.trimStart(midStr).startsWith(config.getTokenBeforeDate())) { + // Extend date extraction for cases like "Monday evening next week" + String extendedStr = dateErs.get(idx).getText() + input.substring(dateErs.get(idx + 1).getStart() + dateErs.get(idx + 1).getLength()); + Optional extendedDateEr = config.getSingleDateExtractor().extract(extendedStr).stream().findFirst(); + int offset = 0; + + if (extendedDateEr.isPresent() && extendedDateEr.get().getStart() == 0) { + offset = extendedDateEr.get().getLength() - dateErs.get(idx).getLength(); + } + + results.add(new Token(dateErs.get(idx).getStart(), offset + dateErs.get(idx + 1).getStart() + dateErs.get(idx + 1).getLength())); + idx += 2; + } + } + } + + return results; + } + + //TODO: this can be abstracted with the similar method in BaseDatePeriodExtractor + private List matchDuration(String input, LocalDateTime reference) { + List results = new ArrayList<>(); + + List durations = new ArrayList<>(); + List durationErs = config.getDurationExtractor().extract(input, reference); + + for (ExtractResult durationEr : durationErs) { + Optional match = match(config.getTimeUnitRegex(), durationEr.getText()); + if (match.isPresent()) { + durations.add(new Token(durationEr.getStart(), durationEr.getStart() + durationEr.getLength())); + } + } + + for (Token duration : durations) { + String beforeStr = input.substring(0, duration.getStart()).toLowerCase(); + String afterStr = input.substring(duration.getStart() + duration.getLength()); + + if (StringUtility.isNullOrWhiteSpace(beforeStr) && StringUtility.isNullOrWhiteSpace(afterStr)) { + continue; + } + + // within (the) (next) "Seconds/Minutes/Hours" should be handled as datetimeRange here + // within (the) (next) XX days/months/years + "Seconds/Minutes/Hours" should also be handled as datetimeRange here + Optional match = match(config.getWithinNextPrefixRegex(), beforeStr); + if (matchPrefixRegexInSegment(beforeStr, match)) { + int startToken = match.get().index; + int durationLength = duration.getStart() + duration.getLength(); + match = match(config.getTimeUnitRegex(), input.substring(duration.getStart(), durationLength)); + if (match.isPresent()) { + results.add(new Token(startToken, duration.getEnd())); + continue; + } + } + + int index = -1; + + match = match(config.getPastPrefixRegex(), beforeStr); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length))) { + index = match.get().index; + } + + if (index < 0) { + match = match(config.getNextPrefixRegex(), beforeStr); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length))) { + index = match.get().index; + } + } + + if (index >= 0) { + + String prefix = beforeStr.substring(0, index).trim(); + String durationText = input.substring(duration.getStart(), duration.getStart() + duration.getLength()); + List numbersInPrefix = config.getCardinalExtractor().extract(prefix); + List numbersInDuration = config.getCardinalExtractor().extract(durationText); + + // Cases like "2 upcoming days", should be supported here + // Cases like "2 upcoming 3 days" is invalid, only extract "upcoming 3 days" by default + if (!numbersInPrefix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult lastNumber = numbersInPrefix.stream() + .sorted(Comparator.comparingInt(x -> x.getStart() + x.getLength())) + .reduce((acc, item) -> item).orElse(null); + + // Prefix should ends with the last number + if (lastNumber.getStart() + lastNumber.getLength() == prefix.length()) { + results.add(new Token(lastNumber.getStart(), duration.getEnd())); + } + + } else { + results.add(new Token(index, duration.getEnd())); + } + + continue; + } + + Optional matchDateUnit = Arrays.stream(RegExpUtility.getMatches(config.getDateUnitRegex(), afterStr)).findFirst(); + if (!matchDateUnit.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPastPrefixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + continue; + } + + match = Arrays.stream(RegExpUtility.getMatches(config.getNextPrefixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + continue; + } + + match = Arrays.stream(RegExpUtility.getMatches(config.getFutureSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent() && StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(duration.getStart(), duration.getStart() + duration.getLength() + match.get().index + match.get().length)); + } + } + } + + return results; + } + + private Optional match(Pattern regex, String input) { + return Arrays.stream(RegExpUtility.getMatches(regex, input)).findFirst(); + } + + private boolean matchPrefixRegexInSegment(String beforeStr, Optional match) { + return match.isPresent() && StringUtility.isNullOrWhiteSpace(beforeStr.substring(match.get().index + match.get().length)); + } + + private List matchTimeOfDay(String input, LocalDateTime reference, List dateErs) { + + List results = new ArrayList<>(); + Match[] matches = RegExpUtility.getMatches(config.getSpecificTimeOfDayRegex(), input); + for (Match match : matches) { + results.add(new Token(match.index, match.index + match.length)); + } + + // Date followed by morning, afternoon or morning, afternoon followed by Date + if (dateErs.size() == 0) { + return results; + } + + for (ExtractResult er : dateErs) { + String afterStr = input.substring(er.getStart() + er.getLength()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), afterStr)).findFirst(); + + if (match.isPresent()) { + // For cases like "Friday afternoon between 1PM and 4 PM" which "Friday afternoon" need to be extracted first + if (StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + int start = er.getStart(); + int end = er.getStart() + er.getLength() + + match.get().getGroup(Constants.TimeOfDayGroupName).index + + match.get().getGroup(Constants.TimeOfDayGroupName).length; + + results.add(new Token(start, end)); + continue; + } + + String connectorStr = afterStr.substring(0, match.get().index); + + // Trim here is set to false as the Regex might catch white spaces before or after the text + boolean isMatch = RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false); + if (isMatch) { + String suffix = StringUtility.trimStart(afterStr.substring(match.get().index + match.get().length)); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + + if (endingMatch.isPresent()) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } + } + } + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), afterStr)).findFirst(); + } + + if (!match.isPresent() || !StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), afterStr)).findFirst(); + } + + if (match.isPresent()) { + if (StringUtility.isNullOrWhiteSpace(afterStr.substring(0, match.get().index))) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } else { + String connectorStr = afterStr.substring(0, match.get().index); + // Trim here is set to false as the Regex might catch white spaces before or after the text + if (RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false)) { + String suffix = afterStr.substring(match.get().index + match.get().length).replaceAll("^\\s+", ""); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + if (endingMatch.isPresent()) { + results.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().index + match.get().length)); + } + } + } + } + + String prefixStr = input.substring(0, er.getStart()); + + match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), prefixStr)).findFirst(); + if (match.isPresent()) { + if (StringUtility.isNullOrWhiteSpace(prefixStr.substring(match.get().index + match.get().length))) { + String midStr = input.substring(match.get().index + match.get().length, er.getStart()); + if (!StringUtility.isNullOrEmpty(midStr) && StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } else { + String connectorStr = prefixStr.substring(match.get().index + match.get().length); + + // Trim here is set to false as the Regex might catch white spaces before or after the text + if (RegexExtension.isExactMatch(config.getMiddlePauseRegex(), connectorStr, false)) { + String suffix = StringUtility.trimStart(input.substring(er.getStart() + er.getLength())); + + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(config.getGeneralEndingRegex(), suffix)).findFirst(); + if (endingMatch.isPresent()) { + results.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } + } + } + } + + // Check whether there are adjacent time period strings, before or after + for (Token result : results.toArray(new Token[0])) { + // Try to extract a time period in before-string + if (result.getStart() > 0) { + String beforeStr = input.substring(0, result.getStart()); + if (!StringUtility.isNullOrEmpty(beforeStr)) { + List timeErs = config.getTimePeriodExtractor().extract(beforeStr); + if (timeErs.size() > 0) { + for (ExtractResult timeEr : timeErs) { + String midStr = beforeStr.substring(timeEr.getStart() + timeEr.getLength()); + if (StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(timeEr.getStart(), timeEr.getStart() + timeEr.getLength() + midStr.length() + result.getLength())); + } + } + } + } + } + + // Try to extract a time period in after-string + if (result.getStart() + result.getLength() <= input.length()) { + String afterStr = input.substring(result.getStart() + result.getLength()); + if (!StringUtility.isNullOrEmpty(afterStr)) { + List timeErs = config.getTimePeriodExtractor().extract(afterStr); + for (ExtractResult timeEr: timeErs) { + String midStr = afterStr.substring(0, timeEr.getStart()); + if (StringUtility.isNullOrWhiteSpace(midStr)) { + results.add(new Token(result.getStart(), result.getStart() + result.getLength() + midStr.length() + timeEr.getLength())); + } + } + + } + } + } + + return results; + } + + private List matchRelativeUnit(String input) { + List results = new ArrayList(); + + Match[] matches = RegExpUtility.getMatches(config.getRelativeTimeUnitRegex(), input); + if (matches.length == 0) { + matches = RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), input); + } + + for (Match match : matches) { + results.add(new Token(match.index, match.index + match.length)); + } + + return results; + } + + private List matchDateWithPeriodPrefix(String input, LocalDateTime reference, List dateErs) { + List results = new ArrayList(); + + for (ExtractResult dateEr : dateErs) { + int dateStrEnd = dateEr.getStart() + dateEr.getLength(); + String beforeStr = input.substring(0, dateEr.getStart()).trim(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPrefixDayRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + results.add(new Token(match.get().index, dateStrEnd)); + } + } + + return results; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + private List mergeDateWithTimePeriodSuffix(String input, List dateErs, List timeErs) { + List results = new ArrayList(); + + if (dateErs.isEmpty()) { + return results; + } + + if (timeErs.isEmpty()) { + return results; + } + + List ers = new ArrayList<>(); + ers.addAll(dateErs); + ers.addAll(timeErs); + + ers.sort(Comparator.comparingInt(arg -> arg.getStart())); + + int i = 0; + while (i < ers.size() - 1) { + int j = i + 1; + while (j < ers.size() && ers.get(i).isOverlap(ers.get(j))) { + j++; + } + + if (j >= ers.size()) { + break; + } + + if (ers.get(i).getType().equals(Constants.SYS_DATETIME_DATE) && ers.get(j).getType().equals(Constants.SYS_DATETIME_TIME)) { + int middleBegin = ers.get(i).getStart() + ers.get(i).getLength(); + int middleEnd = ers.get(j).getStart(); + if (middleBegin > middleEnd) { + i = j + 1; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(); + if (isValidConnectorForDateAndTimePeriod(middleStr)) { + int begin = ers.get(i).getStart(); + int end = ers.get(j).getStart() + ers.get(j).getLength(); + results.add(new Token(begin, end)); + } + + i = j + 1; + continue; + } + i = j; + } + + // Handle "in the afternoon" at the end of entity + for (int idx = 0; idx < results.size(); idx++) { + String afterStr = input.substring(results.get(idx).getEnd()); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixRegex(), afterStr)).findFirst(); + if (match.isPresent()) { + Token oldToken = results.get(idx); + results.set(idx, new Token(oldToken.getStart(), oldToken.getEnd() + match.get().length)); + } + } + + return results; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + // Valid connector in English for Before include: "before", "no later than", "in advance of", "prior to", "earlier than", "sooner than", "by", "till", "until"... + // Valid connector in English for After include: "after", "later than" + private boolean isValidConnectorForDateAndTimePeriod(String text) { + + List beforeAfterRegexes = new ArrayList<>(); + beforeAfterRegexes.add(config.getBeforeRegex()); + beforeAfterRegexes.add(config.getAfterRegex()); + + for (Pattern regex : beforeAfterRegexes) { + if (RegexExtension.isExactMatch(regex, text, true)) { + return true; + } + } + + return false; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java new file mode 100644 index 000000000..949aa4c1e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseDurationExtractor.java @@ -0,0 +1,281 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DurationParsingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BaseDurationExtractor implements IDateTimeExtractor { + + private final IDurationExtractorConfiguration config; + private final boolean merge; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DURATION; + } + + public BaseDurationExtractor(IDurationExtractorConfiguration config) { + this(config, true); + } + + public BaseDurationExtractor(IDurationExtractorConfiguration config, boolean merge) { + this.config = config; + this.merge = merge; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + + List numberWithUnitTokens = numberWithUnit(input); + + tokens.addAll(numberWithUnitTokens); + tokens.addAll(numberWithUnitAndSuffix(input, numberWithUnitTokens)); + tokens.addAll(implicitDuration(input)); + + List result = Token.mergeAllTokens(tokens, input, getExtractorName()); + + // First MergeMultipleDuration then ResolveMoreThanOrLessThanPrefix so cases like "more than 4 days and less than 1 week" will not be merged into one "multipleDuration" + if (merge) { + result = mergeMultipleDuration(input, result); + } + + result = tagInequalityPrefix(input, result); + + return result; + } + + private List tagInequalityPrefix(String input, List result) { + Stream resultStream = result.stream().map(er -> { + String beforeString = input.substring(0, er.getStart()); + boolean isInequalityPrefixMatched = false; + + ConditionalMatch match = RegexExtension.matchEnd(this.config.getMoreThanRegex(), beforeString, true); + + // The second condition is necessary so for "1 week" in "more than 4 days and less than 1 week", it will not be tagged incorrectly as "more than" + if (match.getSuccess()) { + er.setData(Constants.MORE_THAN_MOD); + isInequalityPrefixMatched = true; + } + + if (!isInequalityPrefixMatched) { + match = RegexExtension.matchEnd(this.config.getLessThanRegex(), beforeString, true); + + if (match.getSuccess()) { + er.setData(Constants.LESS_THAN_MOD); + isInequalityPrefixMatched = true; + } + } + + if (isInequalityPrefixMatched) { + int length = er.getLength() + er.getStart() - match.getMatch().get().index; + int start = match.getMatch().get().index; + String text = input.substring(start, start + length); + er.setStart(start); + er.setLength(length); + er.setText(text); + } + + return er; + }); + return resultStream.collect(Collectors.toList()); + } + + private List mergeMultipleDuration(String input, List extractResults) { + if (extractResults.size() <= 1) { + return extractResults; + } + + ImmutableMap unitMap = config.getUnitMap(); + ImmutableMap unitValueMap = config.getUnitValueMap(); + Pattern unitRegex = config.getDurationUnitRegex(); + + List result = new ArrayList<>(); + + int firstExtractionIndex = 0; + int timeUnit = 0; + int totalUnit = 0; + + while (firstExtractionIndex < extractResults.size()) { + String currentUnit = null; + Optional unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, extractResults.get(firstExtractionIndex).getText())).findFirst(); + + if (unitMatch.isPresent() && unitMap.containsKey(unitMatch.get().getGroup("unit").value)) { + currentUnit = unitMatch.get().getGroup("unit").value; + totalUnit++; + if (DurationParsingUtil.isTimeDurationUnit(unitMap.get(currentUnit))) { + timeUnit++; + } + } + + if (StringUtility.isNullOrEmpty(currentUnit)) { + firstExtractionIndex++; + continue; + } + + int secondExtractionIndex = firstExtractionIndex + 1; + while (secondExtractionIndex < extractResults.size()) { + boolean valid = false; + int midStrBegin = extractResults.get(secondExtractionIndex - 1).getStart() + extractResults.get(secondExtractionIndex - 1).getLength(); + int midStrEnd = extractResults.get(secondExtractionIndex).getStart(); + String midStr = input.substring(midStrBegin, midStrEnd); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getDurationConnectorRegex(), midStr)).findFirst(); + + if (match.isPresent()) { + unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, extractResults.get(secondExtractionIndex).getText())).findFirst(); + + if (unitMatch.isPresent() && unitMap.containsKey(unitMatch.get().getGroup("unit").value)) { + String nextUnitStr = unitMatch.get().getGroup("unit").value; + + if (unitValueMap.get(nextUnitStr) != unitValueMap.get(currentUnit)) { + valid = true; + + if (unitValueMap.get(nextUnitStr) < unitValueMap.get(currentUnit)) { + currentUnit = nextUnitStr; + } + } + + totalUnit++; + + if (DurationParsingUtil.isTimeDurationUnit(unitMap.get(nextUnitStr))) { + timeUnit++; + } + } + } + + if (!valid) { + break; + } + + secondExtractionIndex++; + } + + if (secondExtractionIndex - 1 > firstExtractionIndex) { + int start = extractResults.get(firstExtractionIndex).getStart(); + int length = extractResults.get(secondExtractionIndex - 1).getStart() + extractResults.get(secondExtractionIndex - 1).getLength() - start; + String text = input.substring(start, start + length); + String rType = extractResults.get(firstExtractionIndex).getType(); + ExtractResult node = new ExtractResult(start, length, text, rType, null); + + // add multiple duration type to extract result + String type = null; + + if (timeUnit == totalUnit) { + type = Constants.MultipleDuration_Time; + } else if (timeUnit == 0) { + type = Constants.MultipleDuration_Date; + } else { + type = Constants.MultipleDuration_DateTime; + } + + node.setData(type); + result.add(node); + + timeUnit = 0; + totalUnit = 0; + + } else { + result.add(extractResults.get(firstExtractionIndex)); + } + + firstExtractionIndex = secondExtractionIndex; + } + + return result; + } + + // handle cases that don't contain nubmer + private Collection implicitDuration(String text) { + Collection result = new ArrayList<>(); + + // handle "all day", "all year" + result.addAll(getTokenFromRegex(config.getAllRegex(), text)); + + // handle "half day", "half year" + result.addAll(getTokenFromRegex(config.getHalfRegex(), text)); + + // handle "next day", "last year" + result.addAll(getTokenFromRegex(config.getRelativeDurationUnitRegex(), text)); + + // handle "during/for the day/week/month/year" + if (config.getOptions().match(DateTimeOptions.CalendarMode)) { + result.addAll(getTokenFromRegex(config.getDuringRegex(), text)); + } + + return result; + } + + // simple cases made by a number followed an unit + private List numberWithUnit(String text) { + List result = new ArrayList<>(); + List ers = this.config.getCardinalExtractor().extract(text); + for (ExtractResult er : ers) { + String afterStr = text.substring(er.getStart() + er.getLength()); + ConditionalMatch match = RegexExtension.matchBegin(this.config.getFollowedUnit(), afterStr, true); + if (match.getSuccess() && match.getMatch().get().index == 0) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.getMatch().get().length)); + } + } + + // handle "3hrs" + result.addAll(this.getTokenFromRegex(this.config.getNumberCombinedWithUnit(), text)); + + // handle "an hour" + result.addAll(this.getTokenFromRegex(this.config.getAnUnitRegex(), text)); + + // handle "few" related cases + result.addAll(this.getTokenFromRegex(this.config.getInexactNumberUnitRegex(), text)); + + return result; + } + + private Collection getTokenFromRegex(Pattern pattern, String text) { + Collection result = new ArrayList<>(); + + for (Match match : RegExpUtility.getMatches(pattern, text)) { + result.add(new Token(match.index, match.index + match.length)); + } + + return result; + } + + // handle cases look like: {number} {unit}? and {an|a} {half|quarter} {unit}? + // define the part "and {an|a} {half|quarter}" as Suffix + private Collection numberWithUnitAndSuffix(String text, Collection tokens) { + Collection result = new ArrayList<>(); + + for (Token token : tokens) { + String afterStr = text.substring(token.getStart() + token.getLength()); + ConditionalMatch match = RegexExtension.matchBegin(this.config.getSuffixAndRegex(), afterStr, true); + if (match.getSuccess() && match.getMatch().get().index == 0) { + result.add(new Token(token.getStart(), token.getStart() + token.getLength() + match.getMatch().get().length)); + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java new file mode 100644 index 000000000..d901688f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseHolidayExtractor.java @@ -0,0 +1,60 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class BaseHolidayExtractor implements IDateTimeExtractor { + + private final IHolidayExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_DATE; + } + + public BaseHolidayExtractor(IHolidayExtractorConfiguration config) { + this.config = config; + } + + @Override + public final List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + tokens.addAll(holidayMatch(input)); + List ers = Token.mergeAllTokens(tokens, input, getExtractorName()); + for (ExtractResult er : ers) { + Metadata metadata = new Metadata() { + { + setIsHoliday(true); + } + }; + er.setMetadata(metadata); + } + return ers; + } + + @Override + public final List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List holidayMatch(String text) { + List ret = new ArrayList<>(); + for (Pattern regex : this.config.getHolidayRegexes()) { + Match[] matches = RegExpUtility.getMatches(regex, text); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java new file mode 100644 index 000000000..eb765c993 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseMergedDateTimeExtractor.java @@ -0,0 +1,361 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ProcessedSuperfluousWords; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class BaseMergedDateTimeExtractor implements IDateTimeExtractor { + + private final IMergedExtractorConfiguration config; + + @Override + public String getExtractorName() { + return ""; + } + + public BaseMergedDateTimeExtractor(IMergedExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List ret = new ArrayList<>(); + String originInput = input; + Iterable> superfluousWordMatches = null; + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + ProcessedSuperfluousWords processedSuperfluousWords = MatchingUtil.preProcessTextRemoveSuperfluousWords(input, this.config.getSuperfluousWordMatcher()); + input = processedSuperfluousWords.getText(); + superfluousWordMatches = processedSuperfluousWords.getSuperfluousWordMatches(); + } + + // The order is important, since there is a problem in merging + addTo(ret, this.config.getDateExtractor().extract(input, reference), input); + addTo(ret, this.config.getTimeExtractor().extract(input, reference), input); + addTo(ret, this.config.getDatePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDurationExtractor().extract(input, reference), input); + addTo(ret, this.config.getTimePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDateTimePeriodExtractor().extract(input, reference), input); + addTo(ret, this.config.getDateTimeExtractor().extract(input, reference), input); + addTo(ret, this.config.getSetExtractor().extract(input, reference), input); + addTo(ret, this.config.getHolidayExtractor().extract(input, reference), input); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + addTo(ret, this.config.getTimeZoneExtractor().extract(input, reference), input); + ret = this.config.getTimeZoneExtractor().removeAmbiguousTimezone(ret); + } + + // This should be at the end since if need the extractor to determine the previous text contains time or not + addTo(ret, numberEndingRegexMatch(input, ret), input); + + // modify time entity to an alternative DateTime expression if it follows a DateTime entity + if (this.config.getOptions().match(DateTimeOptions.ExtendedTypes)) { + ret = this.config.getDateTimeAltExtractor().extract(ret, input, reference); + } + + ret = filterUnspecificDatePeriod(ret, input); + + // Remove common ambiguous cases + ret = filterAmbiguity(ret, input); + + ret = addMod(ret, input); + + // filtering + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + checkCalendarFilterList(ret, input); + } + + ret.sort(Comparator.comparingInt(r -> r.getStart())); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + ret = MatchingUtil.posProcessExtractionRecoverSuperfluousWords(ret, superfluousWordMatches, originInput); + } + + return ret; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List filterAmbiguity(List extractResults, String input) { + if (config.getAmbiguityFiltersDict() != null) { + for (Pair pair : config.getAmbiguityFiltersDict()) { + final Pattern key = pair.getValue0(); + final Pattern value = pair.getValue1(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toList()); + } + } + } + } + + return extractResults; + } + + private void addTo(List dst, List src, String text) { + for (ExtractResult result : src) { + if (config.getOptions().match(DateTimeOptions.SkipFromToMerge)) { + if (shouldSkipFromToMerge(result)) { + continue; + } + } + + boolean isFound = false; + List overlapIndexes = new ArrayList<>(); + int firstIndex = -1; + for (int i = 0; i < dst.size(); i++) { + if (dst.get(i).isOverlap(result)) { + isFound = true; + if (dst.get(i).isCover(result)) { + if (firstIndex == -1) { + firstIndex = i; + } + + overlapIndexes.add(i); + } else { + break; + } + } + } + + if (!isFound) { + dst.add(result); + } else if (overlapIndexes.size() > 0) { + List tempDst = new ArrayList<>(); + for (int i = 0; i < dst.size(); i++) { + if (!overlapIndexes.contains(i)) { + tempDst.add(dst.get(i)); + } + } + + // insert at the first overlap occurrence to keep the order + tempDst.add(firstIndex, result); + dst.clear(); + dst.addAll(tempDst); + } + + dst.sort(Comparator.comparingInt(a -> a.getStart())); + } + } + + private boolean shouldSkipFromToMerge(ExtractResult er) { + return Arrays.stream(RegExpUtility.getMatches(config.getFromToRegex(), er.getText())).findFirst().isPresent(); + } + + private List numberEndingRegexMatch(String text, List extractResults) { + List tokens = new ArrayList<>(); + + for (ExtractResult extractResult : extractResults) { + if (extractResult.getType().equals(Constants.SYS_DATETIME_TIME) || extractResult.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + String stringAfter = text.substring(extractResult.getStart() + extractResult.getLength()); + Pattern numberEndingPattern = this.config.getNumberEndingPattern(); + Optional match = Arrays.stream(RegExpUtility.getMatches(numberEndingPattern, stringAfter)).findFirst(); + if (match.isPresent()) { + MatchGroup newTime = match.get().getGroup("newTime"); + List numRes = this.config.getIntegerExtractor().extract(newTime.value); + if (numRes.size() == 0) { + continue; + } + + int startPosition = extractResult.getStart() + extractResult.getLength() + newTime.index; + tokens.add(new Token(startPosition, startPosition + newTime.length)); + } + + } + } + + return Token.mergeAllTokens(tokens, text, Constants.SYS_DATETIME_TIME); + } + + private List filterUnspecificDatePeriod(List ers, String text) { + ers.removeIf(er -> Arrays.stream(RegExpUtility.getMatches(config.getUnspecificDatePeriodRegex(), er.getText())).findFirst().isPresent()); + return ers; + } + + private List addMod(List ers, String text) { + int index = 0; + + for (ExtractResult er : ers.toArray(new ExtractResult[0])) { + MergeModifierResult modifiedToken = tryMergeModifierToken(er, config.getBeforeRegex(), text); + + if (!modifiedToken.result) { + modifiedToken = tryMergeModifierToken(er, config.getAfterRegex(), text); + } + + if (!modifiedToken.result) { + // SinceRegex in English contains the term "from" which is potentially ambiguous with ranges in the form "from X to Y" + modifiedToken = tryMergeModifierToken(er, config.getSinceRegex(), text, true); + } + + if (!modifiedToken.result) { + modifiedToken = tryMergeModifierToken(er, config.getAroundRegex(), text); + } + + ers.set(index, modifiedToken.er); + + final ExtractResult newEr = modifiedToken.er; + + if (newEr.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) || newEr.getType().equals(Constants.SYS_DATETIME_DATE) || + newEr.getType().equals(Constants.SYS_DATETIME_TIME)) { + + // 2012 or after/above, 3 pm or later + String afterStr = text.substring(newEr.getStart() + newEr.getLength()).toLowerCase(); + + ConditionalMatch match = RegexExtension.matchBegin(config.getSuffixAfterRegex(), StringUtility.trimStart(afterStr), true); + + if (match.getSuccess()) { + boolean isFollowedByOtherEntity = true; + + if (match.getMatch().get().length == afterStr.trim().length()) { + isFollowedByOtherEntity = false; + + } else { + String nextStr = afterStr.trim().substring(match.getMatch().get().length).trim(); + ExtractResult nextEr = ers.stream().filter(t -> t.getStart() > newEr.getStart()).findFirst().orElse(null); + + if (nextEr == null || !nextStr.startsWith(nextEr.getText())) { + isFollowedByOtherEntity = false; + } + } + + if (!isFollowedByOtherEntity) { + int modLength = match.getMatch().get().length + afterStr.indexOf(match.getMatch().get().value); + int length = newEr.getLength() + modLength; + String newText = text.substring(newEr.getStart(), newEr.getStart() + length); + + er.setMetadata(assignModMetadata(er.getMetadata())); + + ers.set(index, new ExtractResult(er.getStart(), length, newText, er.getType(), er.getData(), er.getMetadata())); + } + } + } + + index++; + } + + return ers; + } + + private MergeModifierResult tryMergeModifierToken(ExtractResult er, Pattern tokenRegex, String text) { + return tryMergeModifierToken(er, tokenRegex, text, false); + } + + private MergeModifierResult tryMergeModifierToken(ExtractResult er, Pattern tokenRegex, String text, boolean potentialAmbiguity) { + String beforeStr = text.substring(0, er.getStart()).toLowerCase(); + + // Avoid adding mod for ambiguity cases, such as "from" in "from ... to ..." should not add mod + if (potentialAmbiguity && config.getAmbiguousRangeModifierPrefix() != null && + Arrays.stream(RegExpUtility.getMatches(config.getAmbiguousRangeModifierPrefix(), text)).findFirst().isPresent()) { + final Match[] matches = RegExpUtility.getMatches(config.getPotentialAmbiguousRangeRegex(), text); + if (Arrays.stream(matches).anyMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) { + return new MergeModifierResult(false, er); + } + } + + ResultIndex result = hasTokenIndex(StringUtility.trimEnd(beforeStr), tokenRegex); + if (result.getResult()) { + int modLength = beforeStr.length() - result.getIndex(); + int length = er.getLength() + modLength; + int start = er.getStart() - modLength; + String newText = text.substring(start, start + length); + + er.setText(newText); + er.setLength(length); + er.setStart(start); + + er.setMetadata(assignModMetadata(er.getMetadata())); + + return new MergeModifierResult(true, er); + } + + return new MergeModifierResult(false, er); + } + + private Metadata assignModMetadata(Metadata metadata) { + + if (metadata == null) { + metadata = new Metadata() { + { + setHasMod(true); + } + }; + } else { + metadata.setHasMod(true); + } + + return metadata; + } + + private ResultIndex hasTokenIndex(String text, Pattern pattern) { + Match[] matches = RegExpUtility.getMatches(pattern, text); + + // Support cases has two or more specific tokens + // For example, "show me sales after 2010 and before 2018 or before 2000" + // When extract "before 2000", we need the second "before" which will be matched in the second Regex match + for (Match match : matches) { + if (StringUtility.isNullOrWhiteSpace(text.substring(match.index + match.length))) { + return new ResultIndex(true, match.index); + } + } + + return new ResultIndex(false, -1); + } + + private void checkCalendarFilterList(List ers, String text) { + List shallowCopy = new ArrayList<>(ers); + Collections.reverse(shallowCopy); + for (ExtractResult er : shallowCopy) { + for (Pattern negRegex : this.config.getFilterWordRegexList()) { + Optional match = Arrays.stream(RegExpUtility.getMatches(negRegex, er.getText())).findFirst(); + if (match.isPresent()) { + ers.remove(er); + } + } + } + } + + private class MergeModifierResult { + public final boolean result; + public final ExtractResult er; + + private MergeModifierResult(boolean result, ExtractResult er) { + this.result = result; + this.er = er; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java new file mode 100644 index 000000000..dbf346bd7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseSetExtractor.java @@ -0,0 +1,160 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseSetExtractor implements IDateTimeExtractor { + + private final ISetExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_SET; + } + + public BaseSetExtractor(ISetExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + List tokens = new ArrayList<>(); + tokens.addAll(matchEachUnit(input)); + tokens.addAll(timeEveryday(input, reference)); + tokens.addAll(matchEachDuration(input, reference)); + tokens.addAll(matchEach(this.config.getDateExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getTimeExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDateTimeExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDatePeriodExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getTimePeriodExtractor(), input, reference)); + tokens.addAll(matchEach(this.config.getDateTimePeriodExtractor(), input, reference)); + + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + public final List matchEachUnit(String text) { + List ret = new ArrayList<>(); + + // handle "daily", "monthly" + Pattern pattern = this.config.getPeriodicRegex(); + Match[] matches = RegExpUtility.getMatches(pattern, text); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + // handle "each month" + pattern = this.config.getEachUnitRegex(); + matches = RegExpUtility.getMatches(pattern, text); + + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } + + public final List matchEachDuration(String text, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getDurationExtractor().extract(text, reference); + + for (ExtractResult er : ers) { + // "each last summer" doesn't make sense + Pattern lastRegex = this.config.getLastRegex(); + if (RegExpUtility.getMatches(lastRegex, er.getText()).length > 0) { + continue; + } + + String beforeStr = text.substring(0, (er.getStart() != null) ? er.getStart() : 0); + Pattern eachPrefixRegex = this.config.getEachPrefixRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(eachPrefixRegex, beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } + return ret; + } + + public final List timeEveryday(String text, LocalDateTime reference) { + List ret = new ArrayList<>(); + List ers = this.config.getTimeExtractor().extract(text, reference); + for (ExtractResult er : ers) { + String afterStr = text.substring(er.getStart() + er.getLength()); + if (StringUtility.isNullOrEmpty(afterStr) && this.config.getBeforeEachDayRegex() != null) { + String beforeStr = text.substring(0, er.getStart()); + Pattern beforeEachDayRegex = this.config.getBeforeEachDayRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(beforeEachDayRegex, beforeStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(match.get().index, er.getStart() + er.getLength())); + } + } else { + Pattern eachDayRegex = this.config.getEachDayRegex(); + Optional match = Arrays.stream(RegExpUtility.getMatches(eachDayRegex, afterStr)).findFirst(); + if (match.isPresent()) { + ret.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.get().length)); + } + } + } + + return ret; + } + + public final List matchEach(IDateTimeExtractor extractor, String input, LocalDateTime reference) { + StringBuilder sb = new StringBuilder(input); + List ret = new ArrayList<>(); + Pattern setEachRegex = this.config.getSetEachRegex(); + Match[] matches = RegExpUtility.getMatches(setEachRegex, input); + for (Match match : matches) { + if (match != null) { + String trimedText = sb.delete(match.index, match.index + match.length).toString(); + List ers = extractor.extract(trimedText, reference); + for (ExtractResult er : ers) { + if (er.getStart() <= match.index && (er.getStart() + er.getLength()) > match.index) { + ret.add(new Token(er.getStart(), er.getStart() + er.getLength() + match.length)); + } + } + } + } + + // handle "Mondays" + Pattern setWeekDayRegex = this.config.getSetWeekDayRegex(); + matches = RegExpUtility.getMatches(setWeekDayRegex, input); + for (Match match : matches) { + if (match != null) { + sb = new StringBuilder(input); + sb.delete(match.index, match.index + match.length); + String trimedText = sb.insert(match.index, match.getGroup("weekday").value).toString(); + + List ers = extractor.extract(trimedText, reference); + for (ExtractResult er : ers) { + if (er.getStart() <= match.index && er.getText().contains(match.getGroup("weekday").value)) { + int len = er.getLength() + 1; + if (match.getGroup(Constants.PrefixGroupName).value != "") { + len += match.getGroup(Constants.PrefixGroupName).value.length(); + } + ret.add(new Token(er.getStart(), er.getStart() + len)); + } + } + } + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java new file mode 100644 index 000000000..3bdcbf876 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeExtractor.java @@ -0,0 +1,149 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class BaseTimeExtractor implements IDateTimeExtractor { + + private final ITimeExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIME; + } + + public BaseTimeExtractor(ITimeExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + tokens.addAll(basicRegexMatch(input)); + tokens.addAll(atRegexMatch(input)); + tokens.addAll(beforeAfterRegexMatch(input)); + tokens.addAll(specialCasesRegexMatch(input)); + + List timeErs = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + timeErs = mergeTimeZones(timeErs, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return timeErs; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + private List mergeTimeZones(List timeErs, List timeZoneErs, String text) { + int erIndex = 0; + for (ExtractResult er : timeErs.toArray(new ExtractResult[0])) { + for (ExtractResult timeZoneEr : timeZoneErs) { + int begin = er.getStart() + er.getLength(); + int end = timeZoneEr.getStart(); + + if (begin < end) { + String gapText = text.substring(begin, end); + + if (StringUtility.isNullOrWhiteSpace(gapText)) { + int newLenght = timeZoneEr.getStart() + timeZoneEr.getLength(); + String newText = text.substring(er.getStart(), newLenght); + Map newData = new HashMap<>(); + newData.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + + er.setData(newData); + er.setText(newText); + er.setLength(newLenght - er.getStart()); + timeErs.set(erIndex, er); + } + } + } + erIndex++; + } + return timeErs; + } + + public final List basicRegexMatch(String text) { + + List ret = new ArrayList<>(); + + for (Pattern regex : this.config.getTimeRegexList()) { + + Match[] matches = RegExpUtility.getMatches(regex, text); + for (Match match : matches) { + + // @TODO Workaround to avoid incorrect partial-only matches. Remove after time regex reviews across languages. + String lth = match.getGroup("lth").value; + + if ((lth == null || lth.length() == 0) || + (lth.length() != match.length && !(match.length == lth.length() + 1 && match.value.endsWith(" ")))) { + + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + + return ret; + } + + private List atRegexMatch(String text) { + List ret = new ArrayList<>(); + // handle "at 5", "at seven" + Pattern pattern = this.config.getAtRegex(); + Match[] matches = RegExpUtility.getMatches(pattern, text); + if (matches.length > 0) { + for (Match match : matches) { + if (match.index + match.length < text.length() && text.charAt(match.index + match.length) == '%') { + continue; + } + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } + + private List beforeAfterRegexMatch(String text) { + List ret = new ArrayList<>(); + // only enabled in CalendarMode + if (this.config.getOptions().match(DateTimeOptions.CalendarMode)) { + // handle "before 3", "after three" + Pattern beforeAfterRegex = this.config.getTimeBeforeAfterRegex(); + Match[] matches = RegExpUtility.getMatches(beforeAfterRegex, text); + if (matches.length > 0) { + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + return ret; + } + + private List specialCasesRegexMatch(String text) { + List ret = new ArrayList<>(); + // handle "ish" + if (this.config.getIshRegex() != null && RegExpUtility.getMatches(this.config.getIshRegex(), text).length > 0) { + Match[] matches = RegExpUtility.getMatches(this.config.getIshRegex(), text); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java new file mode 100644 index 000000000..d1ee998ec --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimePeriodExtractor.java @@ -0,0 +1,275 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseTimePeriodExtractor implements IDateTimeExtractor { + + private final ITimePeriodExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIMEPERIOD; + } + + public BaseTimePeriodExtractor(ITimePeriodExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input, LocalDateTime reference) { + + List tokens = new ArrayList<>(); + tokens.addAll(matchSimpleCases(input)); + tokens.addAll(mergeTwoTimePoints(input, reference)); + tokens.addAll(matchTimeOfDay(input)); + + List timePeriodErs = Token.mergeAllTokens(tokens, input, getExtractorName()); + + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + timePeriodErs = TimeZoneUtility.mergeTimeZones(timePeriodErs, config.getTimeZoneExtractor().extract(input, reference), input); + } + + return timePeriodErs; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + // Cases like "from 3 to 5am" or "between 3:30 and 5" are extracted here + // Note that cases like "from 3 to 5" will not be extracted here because no "am/pm" or "hh:mm" to infer it's a time period + // Also cases like "from 3:30 to 4 people" should not be extracted as a time period + private List matchSimpleCases(String input) { + + List ret = new ArrayList<>(); + + for (Pattern regex : this.config.getSimpleCasesRegex()) { + Match[] matches = RegExpUtility.getMatches(regex, input); + + for (Match match : matches) { + // Cases like "from 10:30 to 11", don't necessarily need "am/pm" + if (!match.getGroup(Constants.MinuteGroupName).value.equals("") || !match.getGroup(Constants.SecondGroupName).value.equals("")) { + // Cases like "from 3:30 to 4" should be supported + // Cases like "from 3:30 to 4 on 1/1/2015" should be supported + // Cases like "from 3:30 to 4 people" is considered not valid + Boolean endWithValidToken = false; + + // "No extra tokens after the time period" + if (match.index + match.length == input.length()) { + endWithValidToken = true; + } else { + String afterStr = input.substring(match.index + match.length); + + // "End with general ending tokens or "TokenBeforeDate" (like "on") + boolean endWithGeneralEndings = Arrays.stream(RegExpUtility.getMatches(this.config.getGeneralEndingRegex(), afterStr)) + .findFirst().isPresent(); + boolean endWithAmPm = !match.getGroup(Constants.RightAmPmGroupName).value.equals(""); + if (endWithGeneralEndings || endWithAmPm || afterStr.trim().startsWith(this.config.getTokenBeforeDate())) { + endWithValidToken = true; + } else if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + endWithValidToken = startsWithTimeZone(afterStr); + } + } + + if (endWithValidToken) { + ret.add(new Token(match.index, match.index + match.length)); + } + } else { + // Is there Constants.PmGroupName or Constants.AmGroupName ? + String pmStr = match.getGroup(Constants.PmGroupName).value; + String amStr = match.getGroup(Constants.AmGroupName).value; + String descStr = match.getGroup(Constants.DescGroupName).value; + + // Check "pm", "am" + if (!StringUtility.isNullOrEmpty(pmStr) || !StringUtility.isNullOrEmpty(amStr) || !StringUtility.isNullOrEmpty(descStr)) { + ret.add(new Token(match.index, match.index + match.length)); + } else { + String afterStr = input.substring(match.index + match.length); + + if ((this.config.getOptions().match(DateTimeOptions.EnablePreview)) && startsWithTimeZone(afterStr)) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + } + } + } + + return ret; + } + + private boolean startsWithTimeZone(String afterText) { + boolean startsWithTimeZone = false; + + List timeZoneErs = config.getTimeZoneExtractor().extract(afterText); + Optional firstTimeZone = timeZoneErs.stream().sorted(Comparator.comparingInt(t -> t.getStart())).findFirst(); + + if (firstTimeZone.isPresent()) { + String beforeText = afterText.substring(0, firstTimeZone.get().getStart()); + + if (StringUtility.isNullOrWhiteSpace(beforeText)) { + startsWithTimeZone = true; + } + } + + return startsWithTimeZone; + } + + private List mergeTwoTimePoints(String input, LocalDateTime reference) { + + List ret = new ArrayList<>(); + List ers = this.config.getSingleTimeExtractor().extract(input, reference); + + // Handling ending number as a time point. + List numErs = this.config.getIntegerExtractor().extract(input); + + // Check if it is an ending number + if (numErs.size() > 0) { + List timeNumbers = new ArrayList<>(); + + // check if it is a ending number + boolean endingNumber = false; + ExtractResult num = numErs.get(numErs.size() - 1); + if (num.getStart() + num.getLength() == input.length()) { + endingNumber = true; + } else { + String afterStr = input.substring(num.getStart() + num.getLength()); + Pattern generalEndingRegex = this.config.getGeneralEndingRegex(); + Optional endingMatch = Arrays.stream(RegExpUtility.getMatches(generalEndingRegex, input)).findFirst(); + if (endingMatch.isPresent()) { + endingNumber = true; + } + } + if (endingNumber) { + timeNumbers.add(num); + } + + int i = 0; + int j = 0; + + while (i < numErs.size()) { + // find subsequent time point + int numEndPoint = numErs.get(i).getStart() + numErs.get(i).getLength(); + while (j < ers.size() && ers.get(j).getStart() <= numEndPoint) { + j++; + } + + if (j >= ers.size()) { + break; + } + + // check connector string + String midStr = input.substring(numEndPoint, ers.get(j).getStart()); + Pattern tillRegex = this.config.getTillRegex(); + if (RegexExtension.isExactMatch(tillRegex, midStr, true) || config.hasConnectorToken(midStr.trim())) { + timeNumbers.add(numErs.get(i)); + } + + i++; + } + + // check overlap + for (ExtractResult timeNum : timeNumbers) { + boolean overlap = false; + for (ExtractResult er : ers) { + if (er.getStart() <= timeNum.getStart() && er.getStart() + er.getLength() >= timeNum.getStart()) { + overlap = true; + } + } + + if (!overlap) { + ers.add(timeNum); + } + } + + ers.sort((x, y) -> x.getStart() - y.getStart()); + } + + int idx = 0; + while (idx < ers.size() - 1) { + int middleBegin = ers.get(idx).getStart() + ers.get(idx).getLength(); + int middleEnd = ers.get(idx + 1).getStart(); + + if (middleEnd - middleBegin <= 0) { + idx++; + continue; + } + + String middleStr = input.substring(middleBegin, middleEnd).trim().toLowerCase(java.util.Locale.ROOT); + Pattern tillRegex = this.config.getTillRegex(); + + // Handle "{TimePoint} to {TimePoint}" + if (RegexExtension.isExactMatch(tillRegex, middleStr, true)) { + int periodBegin = ers.get(idx).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + // Handle "from" + String beforeStr = StringUtility.trimEnd(input.substring(0, periodBegin)).toLowerCase(); + ResultIndex fromIndex = this.config.getFromTokenIndex(beforeStr); + ResultIndex betweenIndex = this.config.getBetweenTokenIndex(beforeStr); + if (fromIndex.getResult()) { + // Handle "from" + periodBegin = fromIndex.getIndex(); + } else if (betweenIndex.getResult()) { + // Handle "between" + periodBegin = betweenIndex.getIndex(); + } + + ret.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + + // Handle "between {TimePoint} and {TimePoint}" + if (this.config.hasConnectorToken(middleStr)) { + int periodBegin = ers.get(idx).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + // Handle "between" + String beforeStr = input.substring(0, periodBegin).trim().toLowerCase(java.util.Locale.ROOT); + ResultIndex betweenIndex = this.config.getBetweenTokenIndex(beforeStr); + if (betweenIndex.getResult()) { + periodBegin = betweenIndex.getIndex(); + ret.add(new Token(periodBegin, periodEnd)); + idx += 2; + continue; + } + } + + idx++; + } + + return ret; + } + + private List matchTimeOfDay(String input) { + + List ret = new ArrayList<>(); + Pattern timeOfDayRegex = this.config.getTimeOfDayRegex(); + Match[] matches = RegExpUtility.getMatches(timeOfDayRegex, input); + for (Match match : matches) { + ret.add(new Token(match.index, match.index + match.length)); + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java new file mode 100644 index 000000000..e1401d428 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/BaseTimeZoneExtractor.java @@ -0,0 +1,133 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.Token; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class BaseTimeZoneExtractor implements IDateTimeZoneExtractor { + + private final ITimeZoneExtractorConfiguration config; + + @Override + public String getExtractorName() { + return Constants.SYS_DATETIME_TIMEZONE; + } + + public BaseTimeZoneExtractor(ITimeZoneExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String input) { + return this.extract(input, LocalDateTime.now()); + } + + @Override + public List extract(String input, LocalDateTime reference) { + String normalizedText = QueryProcessor.removeDiacritics(input); + List tokens = new ArrayList<>(); + tokens.addAll(matchTimeZones(normalizedText)); + tokens.addAll(matchLocationTimes(normalizedText, tokens)); + return Token.mergeAllTokens(tokens, input, getExtractorName()); + } + + @Override + public List removeAmbiguousTimezone(List extractResults) { + return extractResults.stream().filter(o -> !config.getAmbiguousTimezoneList().contains(o.getText().toLowerCase())).collect(Collectors.toList()); + } + + private List matchLocationTimes(String text, List tokens) { + List ret = new ArrayList<>(); + + if (config.getLocationTimeSuffixRegex() == null) { + return ret; + } + + Match[] timeMatch = RegExpUtility.getMatches(config.getLocationTimeSuffixRegex(), text); + + // Before calling a Find() in location matcher, check if all the matched suffixes by + // LocationTimeSuffixRegex are already inside tokens extracted by TimeZone matcher. + // If so, don't call the Find() as they have been extracted by TimeZone matcher, otherwise, call it. + + boolean isAllSuffixInsideTokens = true; + + for (Match match : timeMatch) { + boolean isInside = false; + for (Token token : tokens) { + if (token.getStart() <= match.index && token.getEnd() >= match.index + match.length) { + isInside = true; + break; + } + } + + if (!isInside) { + isAllSuffixInsideTokens = false; + } + + if (!isAllSuffixInsideTokens) { + break; + } + } + + if (timeMatch.length != 0 && !isAllSuffixInsideTokens) { + int lastMatchIndex = timeMatch[timeMatch.length - 1].index; + Iterable> matches = config.getLocationMatcher().find(text.substring(0, lastMatchIndex).toLowerCase()); + List> locationMatches = MatchingUtil.removeSubMatches(matches); + + int i = 0; + for (Match match : timeMatch) { + boolean hasCityBefore = false; + + while (i < locationMatches.size() && locationMatches.get(i).getEnd() <= match.index) { + hasCityBefore = true; + i++; + + if (i == locationMatches.size()) { + break; + } + } + + if (hasCityBefore && locationMatches.get(i - 1).getEnd() == match.index) { + ret.add(new Token(locationMatches.get(i - 1).getStart(), match.index + match.length)); + } + + if (i == locationMatches.size()) { + break; + } + } + } + + return ret; + } + + private List matchTimeZones(String text) { + List ret = new ArrayList<>(); + + // Direct UTC matches + Match[] directUtcMatches = RegExpUtility.getMatches(config.getDirectUtcRegex(), text.toLowerCase()); + if (directUtcMatches.length > 0) { + for (Match match : directUtcMatches) { + ret.add(new Token(match.index, match.index + match.length)); + } + } + + Iterable> matches = config.getTimeZoneMatcher().find(text.toLowerCase()); + for (MatchResult match : matches) { + ret.add(new Token(match.getStart(), match.getStart() + match.getLength())); + } + + return ret; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java new file mode 100644 index 000000000..78d19f42e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateExtractor.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.utilities.Match; + +public interface IDateExtractor extends IDateTimeExtractor { + int getYearFromText(Match match); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java new file mode 100644 index 000000000..f21f49c41 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeExtractor.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeExtractor extends IExtractor { + String getExtractorName(); + + List extract(String input, LocalDateTime reference); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java new file mode 100644 index 000000000..4d7ab6d5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeListExtractor.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeListExtractor { + String getExtractorName(); + + List extract(List extractResults, String text, LocalDateTime reference); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java new file mode 100644 index 000000000..8b9b1a151 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/IDateTimeZoneExtractor.java @@ -0,0 +1,9 @@ +package com.microsoft.recognizers.text.datetime.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.List; + +public interface IDateTimeZoneExtractor extends IDateTimeExtractor { + List removeAmbiguousTimezone(List extractResults); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java new file mode 100644 index 000000000..4498324bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateExtractorConfiguration.java @@ -0,0 +1,62 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateExtractorConfiguration extends IOptionsConfiguration { + Iterable getDateRegexList(); + + Iterable getImplicitDateList(); + + Pattern getOfMonth(); + + Pattern getMonthEnd(); + + Pattern getWeekDayEnd(); + + Pattern getDateUnitRegex(); + + Pattern getForTheRegex(); + + Pattern getWeekDayAndDayOfMonthRegex(); + + Pattern getRelativeMonthRegex(); + + Pattern getStrictRelativeRegex(); + + Pattern getWeekDayRegex(); + + Pattern getPrefixArticleRegex(); + + Pattern getYearSuffix(); + + Pattern getMoreThanRegex(); + + Pattern getLessThanRegex(); + + Pattern getInConnectorRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getRangeConnectorSymbolRegex(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getMonthOfYear(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..6c7996691 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDatePeriodExtractorConfiguration.java @@ -0,0 +1,79 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDatePeriodExtractorConfiguration { + Iterable getSimpleCasesRegexes(); + + Pattern getIllegalYearRegex(); + + Pattern getYearRegex(); + + Pattern getTillRegex(); + + Pattern getDateUnitRegex(); + + Pattern getTimeUnitRegex(); + + Pattern getFollowedDateUnit(); + + Pattern getNumberCombinedWithDateUnit(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getWeekOfRegex(); + + Pattern getMonthOfRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getYearPeriodRegex(); + + Pattern getRelativeDecadeRegex(); + + Pattern getComplexDatePeriodRegex(); + + Pattern getReferenceDatePeriodRegex(); + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + Pattern getCenturySuffixRegex(); + + Pattern getNowRegex(); + + IDateTimeExtractor getDatePointExtractor(); + + IExtractor getCardinalExtractor(); + + IExtractor getOrdinalExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IParser getNumberParser(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); + + String[] getDurationDateRestrictions(); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..8bddc5321 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeAltExtractorConfiguration.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDateTimeAltExtractorConfiguration { + IDateExtractor getDateExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + Iterable getRelativePrefixList(); + + Iterable getAmPmRegexList(); + + Pattern getOrRegex(); + + Pattern getThisPrefixRegex(); + + Pattern getDayRegex(); + + Pattern getRangePrefixRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..e26f83509 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimeExtractorConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateTimeExtractorConfiguration extends IOptionsConfiguration { + Pattern getNowRegex(); + + Pattern getSuffixRegex(); + + Pattern getTimeOfTodayAfterRegex(); + + Pattern getSimpleTimeOfTodayAfterRegex(); + + Pattern getTimeOfTodayBeforeRegex(); + + Pattern getSimpleTimeOfTodayBeforeRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getSpecificEndOfRegex(); + + Pattern getUnspecificEndOfRegex(); + + Pattern getUnitRegex(); + + Pattern getNumberAsTimeRegex(); + + Pattern getDateNumberConnectorRegex(); + + Pattern getSuffixAfterRegex(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePointExtractor(); + + IDateTimeExtractor getTimePointExtractor(); + + IExtractor getIntegerExtractor(); + + boolean isConnector(String text); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..b0cc61ad7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,81 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface IDateTimePeriodExtractorConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + Iterable getSimpleCasesRegex(); + + Pattern getPrepositionRegex(); + + Pattern getTillRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getFollowedUnit(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getTimeUnitRegex(); + + Pattern getPastPrefixRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getWeekDayRegex(); + + Pattern getPeriodTimeOfDayWithDateRegex(); + + Pattern getRelativeTimeUnitRegex(); + + Pattern getRestOfDateTimeRegex(); + + Pattern getGeneralEndingRegex(); + + Pattern getMiddlePauseRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getDateUnitRegex(); + + Pattern getPrefixDayRegex(); + + Pattern getSuffixRegex(); + + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + IExtractor getCardinalExtractor(); + + IDateTimeExtractor getSingleDateExtractor(); + + IDateTimeExtractor getSingleTimeExtractor(); + + IDateTimeExtractor getSingleDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getTimeZoneExtractor(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java new file mode 100644 index 000000000..b68aee159 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IDurationExtractorConfiguration.java @@ -0,0 +1,45 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.util.regex.Pattern; + +public interface IDurationExtractorConfiguration extends IOptionsConfiguration { + Pattern getFollowedUnit(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getAnUnitRegex(); + + Pattern getDuringRegex(); + + Pattern getAllRegex(); + + Pattern getHalfRegex(); + + Pattern getSuffixAndRegex(); + + Pattern getConjunctionRegex(); + + Pattern getInexactNumberRegex(); + + Pattern getInexactNumberUnitRegex(); + + Pattern getRelativeDurationUnitRegex(); + + Pattern getDurationUnitRegex(); + + Pattern getDurationConnectorRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + IExtractor getCardinalExtractor(); + + ImmutableMap getUnitMap(); + + ImmutableMap getUnitValueMap(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java new file mode 100644 index 000000000..f071ed03f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IHolidayExtractorConfiguration.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import java.util.regex.Pattern; + +public interface IHolidayExtractorConfiguration { + Iterable getHolidayRegexes(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java new file mode 100644 index 000000000..ad1c14367 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/IMergedExtractorConfiguration.java @@ -0,0 +1,68 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public interface IMergedExtractorConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getSetExtractor(); + + IDateTimeExtractor getHolidayExtractor(); + + IDateTimeZoneExtractor getTimeZoneExtractor(); + + IDateTimeListExtractor getDateTimeAltExtractor(); + + IExtractor getIntegerExtractor(); + + Iterable getFilterWordRegexList(); + + Pattern getAfterRegex(); + + Pattern getBeforeRegex(); + + Pattern getSinceRegex(); + + Pattern getAroundRegex(); + + Pattern getFromToRegex(); + + Pattern getSingleAmbiguousMonthRegex(); + + Pattern getAmbiguousRangeModifierPrefix(); + + Pattern getPotentialAmbiguousRangeRegex(); + + Pattern getPrepositionSuffixRegex(); + + Pattern getNumberEndingPattern(); + + Pattern getSuffixAfterRegex(); + + Pattern getUnspecificDatePeriodRegex(); + + StringMatcher getSuperfluousWordMatcher(); + + Iterable> getAmbiguityFiltersDict(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java new file mode 100644 index 000000000..35620d82a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ISetExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ISetExtractorConfiguration extends IOptionsConfiguration { + Pattern getLastRegex(); + + Pattern getEachDayRegex(); + + Pattern getSetEachRegex(); + + Pattern getPeriodicRegex(); + + Pattern getEachUnitRegex(); + + Pattern getEachPrefixRegex(); + + Pattern getSetWeekDayRegex(); + + Pattern getBeforeEachDayRegex(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java new file mode 100644 index 000000000..9c025d24e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeExtractorConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ITimeExtractorConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getTimeZoneExtractor(); + + Iterable getTimeRegexList(); + + Pattern getAtRegex(); + + Pattern getIshRegex(); + + Pattern getTimeBeforeAfterRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..9f0a0cbf9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimePeriodExtractorConfiguration.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; + +import java.util.regex.Pattern; + +public interface ITimePeriodExtractorConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IExtractor getIntegerExtractor(); + + Iterable getSimpleCasesRegex(); + + Pattern getTillRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getGeneralEndingRegex(); + + IDateTimeExtractor getSingleTimeExtractor(); + + ResultIndex getFromTokenIndex(String text); + + boolean hasConnectorToken(String text); + + ResultIndex getBetweenTokenIndex(String text); + + IDateTimeExtractor getTimeZoneExtractor(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..ce877754c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ITimeZoneExtractorConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.List; +import java.util.regex.Pattern; + +public interface ITimeZoneExtractorConfiguration extends IOptionsConfiguration { + Pattern getDirectUtcRegex(); + + Pattern getLocationTimeSuffixRegex(); + + StringMatcher getLocationMatcher(); + + StringMatcher getTimeZoneMatcher(); + + List getAmbiguousTimezoneList(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java new file mode 100644 index 000000000..42e59194e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ProcessedSuperfluousWords.java @@ -0,0 +1,23 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +import com.microsoft.recognizers.text.matcher.MatchResult; + +import java.util.List; + +public class ProcessedSuperfluousWords { + private String text; + private Iterable> superfluousWordMatches; + + public ProcessedSuperfluousWords(String text, Iterable> superfluousWordMatches) { + this.text = text; + this.superfluousWordMatches = superfluousWordMatches; + } + + public String getText() { + return text; + } + + public Iterable> getSuperfluousWordMatches() { + return superfluousWordMatches; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java new file mode 100644 index 000000000..b9d90d992 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultIndex.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +public class ResultIndex { + private boolean result; + private int index; + + public ResultIndex(boolean result, int index) { + this.result = result; + this.index = index; + } + + public boolean getResult() { + return result; + } + + public int getIndex() { + return index; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setIndex(int index) { + this.index = index; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java new file mode 100644 index 000000000..eaff0b5b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/extractors/config/ResultTimex.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.extractors.config; + +public class ResultTimex { + private boolean result; + private String timex; + + public ResultTimex(boolean result, String timex) { + this.result = result; + this.timex = timex; + } + + public boolean getResult() { + return result; + } + + public String getTimex() { + return timex; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setTimex(String timex) { + this.timex = timex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java new file mode 100644 index 000000000..a90a1f5e9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateExtractorConfiguration.java @@ -0,0 +1,245 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class FrenchDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayRegex); + public static final Pattern SingleWeekDayRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SingleAmbiguousMonthRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextDateRegex); + public static final Pattern StrictWeekDay = RegExpUtility.getSafeRegExp(FrenchDateTime.StrictWeekDay); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecialDayRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayOfMonthRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeWeekDayRegex); + public static final Pattern SpecialDate = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecialDate); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecialDayWithNumRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrefixArticleRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility + .getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + + public static final List DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor2)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor3)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor5) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor4)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor4) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor5)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor7) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor6)); + add(FrenchDateTime.DefaultLanguageFallback == "DMY" ? + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor6) : + RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor7)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor8)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractor9)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.DateExtractorA)); + } + }; + + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(StrictWeekDay); + add(WeekDayOfMonthRegex); + add(SpecialDate); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(FrenchDateTime.OfMonth); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthEnd); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(FrenchDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + + public static final ImmutableMap DayOfWeek = FrenchDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = FrenchDateTime.MonthOfYear; + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final List implicitDateList; + + public FrenchDateExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = new IntegerExtractor(); + ordinalExtractor = new OrdinalExtractor(); + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + + implicitDateList = new ArrayList<>(ImplicitDateList); + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return implicitDateList; + } + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..0f87a53c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDatePeriodExtractorConfiguration.java @@ -0,0 +1,328 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TillRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeMonthRegex); + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthRegex); + public static final Pattern MonthSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthSuffixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern PastRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PastSuffixRegex); + public static final Pattern FutureRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextSuffixRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FutureSuffixRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SimpleCasesRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthFrontBetweenRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthWithYear); + public static final Pattern MonthNumWithYearRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.MonthNumWithYear); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfYearRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.NumberCombinedWithDateUnit); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility + .getSafeRegExp(FrenchDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WhichWeekRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.LaterEarlyPeriodRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RestOfDateRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.DecadeWithCenturyRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.YearPeriodRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.ComplexDatePeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDecadeRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.CenturySuffixRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NowRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(BetweenRegex); + add(OneWordPeriodRegex); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.MonthWithYear)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.MonthNumWithYear)); + add(YearRegex); + add(YearPeriodRegex); + add(WeekOfYearRegex); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayOfMonthRegex)); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(SeasonRegex); + add(LaterEarlyPeriodRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + } + }; + + private static final Pattern fromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex); + private static final Pattern betweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + private final String[] durationDateRestrictions; + + public FrenchDatePeriodExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = new OrdinalExtractor(); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + + durationDateRestrictions = FrenchDateTime.DurationDateRestrictions.toArray(new String[0]); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PastRegex; + } + + @Override + public Pattern getFutureRegex() { + return FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = fromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = betweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays.stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..a07c59c04 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeAltExtractorConfiguration.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + public static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangePrefixRegex); + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DayRegex); + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public FrenchDateTimeAltExtractorConfiguration(final IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override + public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..da47f1fbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimeExtractorConfiguration.java @@ -0,0 +1,171 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.Arrays; +import java.util.regex.Pattern; + +public class FrenchDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixRegex); + + //TODO: modify it according to the corresponding English regex + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificEndOfRegex); + + //TODO: add this for french + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAfterRegex); + private final IExtractor integerExtractor; + private final IDateExtractor datePointExtractor; + private final IDateTimeExtractor timePointExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateTimeExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + } + + public FrenchDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + final boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + final boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..bbd3c23e1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,283 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodExtractorConfiguration { + + public static final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WeekDayRegex); + public static final Pattern NumberCombinedWithUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeNumberCombinedWithUnit); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RestOfDateTimeRegex); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.RelativeTimeUnitRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AfterRegex); + public static final Pattern FromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex2); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BetweenRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeFollowedUnit); + public static final Iterable SimpleCases = new ArrayList() { + { + add(FrenchTimePeriodExtractorConfiguration.PureNumFromTo); + add(FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd); + add(FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex); + } + }; + private final String tokenBeforeDate; + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public FrenchDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchDateTimePeriodExtractorConfiguration(final DateTimeOptions options) { + + super(options); + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + + singleDateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return FrenchDateTimeExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return FrenchTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return FrenchDateTimeExtractorConfiguration.TimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return FrenchDatePeriodExtractorConfiguration.PastRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return FrenchDatePeriodExtractorConfiguration.FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays.stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java new file mode 100644 index 000000000..400d02329 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchDurationExtractorConfiguration.java @@ -0,0 +1,139 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + // TODO: Investigate if required + // public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.UnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAndRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationFollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberCombinedWithDurationUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public FrenchDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchDurationExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = FrenchDateTime.UnitMap; + unitValueMap = FrenchDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return FollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java new file mode 100644 index 000000000..fc3cef61e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchHolidayExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern H1 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex1); + + public static final Pattern H2 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex2); + + public static final Pattern H3 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex3); + + public static final Pattern H4 = RegExpUtility.getSafeRegExp(FrenchDateTime.HolidayRegex4); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H1); + add(H2); + add(H3); + add(H4); + } + }; + + public FrenchHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + @Override + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java new file mode 100644 index 000000000..f49ec1307 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchMergedExtractorConfiguration.java @@ -0,0 +1,194 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class FrenchMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AroundRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromToRegex); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.SingleAmbiguousMonthRegex); + public static final Pattern PrepositionSuffixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility + .getSafeRegExp(FrenchDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(FrenchDateTime.NumberEndingPattern); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SuffixAfterRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificDatePeriodRegex); + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + public final Iterable> ambiguityFiltersDict = FrenchDateTime.AmbiguityFiltersDict.entrySet().stream().map(pair -> { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + return new Pair(key, val); + }).collect(Collectors.toList()); + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor setExtractor; + private final IDateTimeExtractor holidayExtractor; + private final IDateTimeZoneExtractor timeZoneExtractor; + private final IDateTimeListExtractor dateTimeAltExtractor; + private final IExtractor integerExtractor; + + public FrenchMergedExtractorConfiguration(final DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new FrenchSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new FrenchHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new FrenchDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + integerExtractor = new IntegerExtractor(); + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + @Override + public Iterable getFilterWordRegexList() { + return null; + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return null; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return null; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java new file mode 100644 index 000000000..64e4ce397 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchSetExtractorConfiguration.java @@ -0,0 +1,113 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachPrefixRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.EachDayRegex); + // TODO + public static final Pattern BeforeEachDayRegex = null; + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SetWeekDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.SetEachRegex); + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + public FrenchSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchSetExtractorConfiguration(final DateTimeOptions options) { + super(options); + + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return FrenchDateExtractorConfiguration.LastDateRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return BeforeEachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java new file mode 100644 index 000000000..cd823eb07 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeExtractorConfiguration.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchTimeExtractorConfiguration extends BaseOptionsConfiguration implements ITimeExtractorConfiguration { + + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.MinuteNumRegex); + + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.OclockRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex); + + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(FrenchDateTime.LessThanOneHour); + // public static final Pattern TensTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TensTimeRegex); + + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.WrittenTimeRegex); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(FrenchDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeSuffix); + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(FrenchDateTime.BasicTime); + public static final Pattern IshRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.IshRegex); + + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AtRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeBeforeAfterRegex); + public static final Iterable TimeRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex1)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex2)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex3)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex4)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex5)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex6)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex7)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex8)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex9)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.TimeRegex10)); + add(ConnectNumRegex); + } + }; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public FrenchTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchTimeExtractorConfiguration(final DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + public final Pattern getIshRegex() { + return IshRegex; + } + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..b8f093757 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimePeriodExtractorConfiguration.java @@ -0,0 +1,150 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.HourNumRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(FrenchDateTime.PureNumFromTo); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(FrenchDateTime.PureNumBetweenAnd); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(FrenchDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility + .getSafeRegExp(FrenchDateTime.SpecificTimeBetweenAnd); + // TODO: What are these? + // public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.UnitRegex); + // public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(FrenchDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility + .getSafeRegExp(FrenchDateTime.TimeNumberCombinedWithUnit); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeOfDayRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.GeneralEndingRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TillRegex); + private static final Pattern FromRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.FromRegex2); + private static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeConnectorRegex); + private static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.BeforeRegex2); + public final IDateTimeExtractor timeZoneExtractor; + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex)); + add(RegExpUtility.getSafeRegExp(FrenchDateTime.AmRegex)); + } + }; + public final Iterable getPureNumberRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + private final String tokenBeforeDate; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeExtractor singleTimeExtractor; + private final IExtractor integerExtractor; + + public FrenchTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public FrenchTimePeriodExtractorConfiguration(final DateTimeOptions options) { + + super(options); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + integerExtractor = new IntegerExtractor(); + timeZoneExtractor = new BaseTimeZoneExtractor(new FrenchTimeZoneExtractorConfiguration(options)); + } + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public Iterable getPureNumberRegex() { + return getPureNumberRegex; + } + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public ResultIndex getFromTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(final String text) { + int index = -1; + boolean result = false; + final Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(final String text) { + final Optional match = Arrays + .stream(RegExpUtility.getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.ConnectorAndRegex), text)) + .findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..4ad339474 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/extractors/FrenchTimeZoneExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.datetime.french.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class FrenchTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + public FrenchTimeZoneExtractorConfiguration(final DateTimeOptions options) { + super(options); + + } + + private Pattern directUtcRegex; + + public final Pattern getDirectUtcRegex() { + return directUtcRegex; + } + + private Pattern locationTimeSuffixRegex; + + public final Pattern getLocationTimeSuffixRegex() { + return locationTimeSuffixRegex; + } + + private StringMatcher locationMatcher; + + public final StringMatcher getLocationMatcher() { + return locationMatcher; + } + + private StringMatcher timeZoneMatcher; + + public final StringMatcher getTimeZoneMatcher() { + return timeZoneMatcher; + } + + public final ArrayList getAmbiguousTimezoneList() { + return new ArrayList<>(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..580fa2346 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchCommonDateTimeParserConfiguration.java @@ -0,0 +1,292 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.utilities.FrenchDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.french.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.french.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; + +public class FrenchCommonDateTimeParserConfiguration extends BaseDateParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser timeZoneParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + + public FrenchCommonDateTimeParserConfiguration(final DateTimeOptions options) { + + super(options); + + utilityConfiguration = new FrenchDatetimeUtilityConfiguration(); + + unitMap = FrenchDateTime.UnitMap; + unitValueMap = FrenchDateTime.UnitValueMap; + seasonMap = FrenchDateTime.SeasonMap; + specialYearPrefixesMap = FrenchDateTime.SpecialYearPrefixesMap; + cardinalMap = FrenchDateTime.CardinalMap; + dayOfWeek = FrenchDateTime.DayOfWeek; + monthOfYear = FrenchDateTime.MonthOfYear; + numbers = FrenchDateTime.Numbers; + doubleNumbers = FrenchDateTime.DoubleNumbers; + writtenDecades = FrenchDateTime.WrittenDecades; + specialDecadeCases = FrenchDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = new IntegerExtractor(); + ordinalExtractor = new OrdinalExtractor(); + + numberParser = new BaseNumberParser(new FrenchNumberParserConfiguration()); + + dateExtractor = new BaseDateExtractor(new FrenchDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new FrenchTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new FrenchDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new FrenchDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new FrenchTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor( + new FrenchDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + durationParser = new BaseDurationParser(new FrenchDurationParserConfiguration(this)); + dateParser = new BaseDateParser(new FrenchDateParserConfiguration(this)); + timeParser = new FrenchTimeParser(new FrenchTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new FrenchDateTimeParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new FrenchDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new FrenchTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new FrenchDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new FrenchDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return ImmutableMap.builder() + .putAll(BaseDateTime.DayOfMonthDictionary) + .putAll(FrenchDateTime.DayOfMonth).build(); + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java new file mode 100644 index 000000000..0dfa50978 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateParserConfiguration.java @@ -0,0 +1,336 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.StringExtension; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class FrenchDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final ImmutableMap unitMap; + private final Iterable dateRegexes; + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = FrenchDateTime.DateTokenPrefix; + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateRegexes = Collections.unmodifiableList(FrenchDateExtractorConfiguration.DateRegexList); + onRegex = FrenchDateExtractorConfiguration.OnRegex; + specialDayRegex = FrenchDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = FrenchDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = FrenchDateExtractorConfiguration.NextDateRegex; + thisRegex = FrenchDateExtractorConfiguration.ThisRegex; + lastRegex = FrenchDateExtractorConfiguration.LastDateRegex; + unitRegex = FrenchDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = FrenchDateExtractorConfiguration.WeekDayRegex; + monthRegex = FrenchDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = FrenchDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = FrenchDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = FrenchDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = FrenchDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = FrenchDateExtractorConfiguration.StrictRelativeRegex; + yearSuffix = FrenchDateExtractorConfiguration.YearSuffix; + relativeWeekDayRegex = FrenchDateExtractorConfiguration.RelativeWeekDayRegex; + relativeDayRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + unitMap = config.getUnitMap(); + utilityConfiguration = config.getUtilityConfiguration(); + sameDayTerms = Collections.unmodifiableList(FrenchDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(FrenchDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(FrenchDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(FrenchDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(FrenchDateTime.MinusTwoDayTerms); + } + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(final String text) { + final String trimmedText = text.trim().toLowerCase(Locale.ROOT); + int swift = 0; + + Matcher regexMatcher = nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = 1; + } + + regexMatcher = previousPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return trimmedText.endsWith("dernière") || trimmedText.endsWith("dernières") || trimmedText.endsWith( + "derniere") || trimmedText.endsWith("dernieres"); + } + + @Override + public String normalize(final String text) { + return StringExtension.normalize(text, ImmutableMap.of()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java new file mode 100644 index 000000000..f3c1e4514 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDatePeriodParserConfiguration.java @@ -0,0 +1,559 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class FrenchDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public static final Pattern nextPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextPrefixRegex); + public static final Pattern previousPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PreviousPrefixRegex); + public static final Pattern thisPrefixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.ThisPrefixRegex); + public static final Pattern nextSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.NextSuffixRegex); + public static final Pattern pastSuffixRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PastSuffixRegex); + public static final Pattern relativeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RelativeRegex); + public static final Pattern unspecificEndOfRangeRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.UnspecificEndOfRangeRegex); + private final String tokenBeforeDate; + + // Regex + private final IDateExtractor dateExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor integerExtractor; + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser durationParser; + private final Pattern monthFrontBetweenRegex; + private final Pattern betweenRegex; + private final Pattern monthFrontSimpleCasesRegex; + private final Pattern simpleCasesRegex; + private final Pattern oneWordPeriodRegex; + private final Pattern monthWithYear; + private final Pattern monthNumWithYear; + private final Pattern yearRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnit; + private final Pattern weekOfMonthRegex; + private final Pattern weekOfYearRegex; + private final Pattern quarterRegex; + private final Pattern quarterRegexYearFront; + private final Pattern allHalfYearRegex; + private final Pattern seasonRegex; + private final Pattern whichWeekRegex; + private final Pattern weekOfRegex; + private final Pattern monthOfRegex; + private final Pattern inConnectorRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern restOfDateRegex; + private final Pattern laterEarlyPeriodRegex; + private final Pattern weekWithWeekDayRangeRegex; + private final Pattern yearPlusNumberRegex; + private final Pattern decadeWithCenturyRegex; + private final Pattern yearPeriodRegex; + private final Pattern complexDatePeriodRegex; + private final Pattern relativeDecadeRegex; + private final Pattern referenceDatePeriodRegex; + private final Pattern agoRegex; + private final Pattern laterRegex; + private final Pattern lessThanRegex; + private final Pattern moreThanRegex; + private final Pattern centurySuffixRegex; + private final Pattern nowRegex; + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + public FrenchDatePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + monthFrontBetweenRegex = FrenchDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = FrenchDatePeriodExtractorConfiguration.BetweenRegex; + monthFrontSimpleCasesRegex = FrenchDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = FrenchDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = FrenchDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = FrenchDatePeriodExtractorConfiguration.MonthWithYearRegex; + monthNumWithYear = FrenchDatePeriodExtractorConfiguration.MonthNumWithYearRegex; + yearRegex = FrenchDatePeriodExtractorConfiguration.YearRegex; + pastRegex = FrenchDatePeriodExtractorConfiguration.PastRegex; + futureRegex = FrenchDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = FrenchDurationExtractorConfiguration.NumberCombinedWithUnit; + weekOfMonthRegex = FrenchDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = FrenchDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = FrenchDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = FrenchDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = FrenchDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = FrenchDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = FrenchDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = FrenchDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = FrenchDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = FrenchDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = FrenchDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = FrenchDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = FrenchDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = FrenchDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = FrenchDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = FrenchDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = FrenchDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = FrenchDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = FrenchDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = FrenchDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = FrenchDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = FrenchDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = FrenchDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = FrenchDatePeriodExtractorConfiguration.CenturySuffixRegex; + nowRegex = FrenchDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + numbers = config.getNumbers(); + writtenDecades = config.getWrittenDecades(); + specialDecadeCases = config.getSpecialDecadeCases(); + } + + @Override + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public final IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public final IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public final IParser getNumberParser() { + return numberParser; + } + + @Override + public final IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public final Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public final Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public final Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public final Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public final Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public final Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public final Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public final Pattern getYearRegex() { + return yearRegex; + } + + @Override + public final Pattern getPastRegex() { + return pastRegex; + } + + @Override + public final Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public final Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public final Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public final Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public final Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public final Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public final Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public final Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public final Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public final Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public final Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public final Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public final Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public final Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getWeekWithWeekDayRangeRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public final Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public final Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public final Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getRelativeDecadeRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public final Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public final Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public final Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public final Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public final Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public final Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public final Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public final Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public final Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public final Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + if (trimmedText.endsWith("prochain") || trimmedText.endsWith("prochaine")) { + swift = 1; + } + + if (trimmedText.endsWith("dernière") || + trimmedText.endsWith("dernières") || + trimmedText.endsWith("derniere") || + trimmedText.endsWith("dernieres") + ) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + if (trimmedText.endsWith("prochain") || trimmedText.endsWith("prochaine")) { + swift = 1; + } + + if (trimmedText.endsWith("dernière") || trimmedText.endsWith("dernières") || trimmedText + .endsWith("derniere") || trimmedText.endsWith("dernieres")) { + swift = -1; + } else if (trimmedText.startsWith("cette")) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return FrenchDateTime.FutureStartTerms.stream().anyMatch(o -> trimmedText.startsWith(o)) || FrenchDateTime.FutureEndTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isLastCardinal(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + final Optional matchLast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)) + .findFirst(); + return matchLast.isPresent(); + } + + @Override + public boolean isMonthOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isMonthToDate(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.MonthToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekend(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + final boolean nextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)) + .findFirst().isPresent(); + final boolean pastSuffix = Arrays.stream(RegExpUtility.getMatches(pastSuffixRegex, trimmedText)) + .findFirst().isPresent(); + + return (FrenchDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + (FrenchDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.contains(o)) && (nextSuffix || pastSuffix))) && + !FrenchDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearOnly(final String text) { + final String trimmedText = text.trim().toLowerCase(); + return FrenchDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearToDate(final String text) { + final String trimmedText = text.trim().toLowerCase(); + + return FrenchDateTime.YearToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..1911f9926 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class FrenchDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public FrenchDateTimeAltParserConfiguration(final ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java new file mode 100644 index 000000000..911f0436e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimeParserConfiguration.java @@ -0,0 +1,258 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + public final String tokenBeforeDate; + public final String tokenBeforeTime; + + public final IDateTimeExtractor dateExtractor; + public final IDateTimeExtractor timeExtractor; + public final IDateTimeParser dateParser; + public final IDateTimeParser timeParser; + public final IExtractor cardinalExtractor; + public final IExtractor integerExtractor; + public final IParser numberParser; + public final IDateTimeExtractor durationExtractor; + public final IDateTimeParser durationParser; + + public final ImmutableMap unitMap; + public final ImmutableMap numbers; + + public final Pattern nowRegex; + public final Pattern amTimeRegex; + public final Pattern pmTimeRegex; + public final Pattern simpleTimeOfTodayAfterRegex; + public final Pattern simpleTimeOfTodayBeforeRegex; + public final Pattern specificTimeOfDayRegex; + public final Pattern specificEndOfRegex; + public final Pattern unspecificEndOfRegex; + public final Pattern unitRegex; + public final Pattern dateNumberConnectorRegex; + + public final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchDateTimeParserConfiguration(final ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationParser = config.getDurationParser(); + integerExtractor = config.getIntegerExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + utilityConfiguration = config.getUtilityConfiguration(); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + tokenBeforeTime = FrenchDateTime.TokenBeforeTime; + + nowRegex = FrenchDateTimeExtractorConfiguration.NowRegex; + unitRegex = FrenchDateTimeExtractorConfiguration.UnitRegex; + specificEndOfRegex = FrenchDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = FrenchDateTimeExtractorConfiguration.UnspecificEndOfRegex; + specificTimeOfDayRegex = FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + dateNumberConnectorRegex = FrenchDateTimeExtractorConfiguration.DateNumberConnectorRegex; + simpleTimeOfTodayAfterRegex = FrenchDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = FrenchDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + + pmTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmRegex); + amTimeRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AMTimeRegex); + } + + @Override + public int getHour(final String text, final int hour) { + int result = hour; + + final String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("matin") && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!trimmedText.endsWith("matin") && hour < Constants.HalfDayHourCount) { + result += Constants.HalfDayHourCount; + } + + return result; + } + + @Override + public ResultTimex getMatchedNowTimex(final String text) { + + final String trimmedText = text.trim().toLowerCase(); + + final String timex; + if (trimmedText.endsWith("maintenant")) { + timex = "PRESENT_REF"; + } else if (trimmedText.equals("récemment") || trimmedText.equals("précédemment") || trimmedText + .equals("auparavant")) { + timex = "PAST_REF"; + } else if (trimmedText.equals("dès que possible") || trimmedText.equals("dqp")) { + timex = "FUTURE_REF"; + } else { + timex = null; + return new ResultTimex(false, null); + } + + return new ResultTimex(true, timex); + } + + @Override + public int getSwiftDay(final String text) { + int swift = 0; + + final String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.startsWith("prochain") || trimmedText.startsWith("prochain") || + trimmedText.startsWith("prochaine") || trimmedText.startsWith("prochaine")) { + swift = 1; + } else if (trimmedText.startsWith("dernier") || trimmedText.startsWith("dernière") || + trimmedText.startsWith("dernier") || trimmedText.startsWith("dernière")) { + swift = -1; + } + + return swift; + + } + + @Override + public boolean containsAmbiguousToken(final String text, final String matchedText) { + return false; + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..b4c67d729 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDateTimePeriodParserConfiguration.java @@ -0,0 +1,331 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + public FrenchDateTimePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = FrenchDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = FrenchTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = FrenchDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + timeOfDayRegex = FrenchDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = FrenchDatePeriodExtractorConfiguration.PastRegex; + futureRegex = FrenchDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = FrenchDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = FrenchDateTimePeriodExtractorConfiguration.NumberCombinedWithUnit; + unitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + periodTimeOfDayWithDateRegex = FrenchDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = FrenchDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = FrenchDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = FrenchDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = FrenchDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = FrenchDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = FrenchDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = FrenchDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = FrenchDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(final String text, + String timeStr, + int beginHour, + int endHour, + int endMin) { + beginHour = 0; + endHour = 0; + endMin = 0; + + final String trimmedText = text.trim().toLowerCase(); + + if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.MorningStartEndRegex), trimmedText).length > 0) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.AfternoonStartEndRegex), trimmedText).length + > 0) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.EveningStartEndRegex), trimmedText).length > 0) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + } else if (RegExpUtility + .getMatches(RegExpUtility.getSafeRegExp(FrenchDateTime.NightStartEndRegex), trimmedText).length > 0) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + } else { + return new MatchedTimeRangeResult(false, null, beginHour, endHour, endMin); + } + + return new MatchedTimeRangeResult(true, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(final String text) { + final String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + if (trimmedText.startsWith("prochain") || trimmedText.endsWith("prochain") || + trimmedText.startsWith("prochaine") || trimmedText.endsWith("prochaine")) { + swift = 1; + } else if (trimmedText.startsWith("derniere") || trimmedText.startsWith("dernier") || + trimmedText.endsWith("derniere") || trimmedText.endsWith("dernier")) { + swift = -1; + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java new file mode 100644 index 000000000..cf69ffcbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchDurationParserConfiguration.java @@ -0,0 +1,144 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import java.util.regex.Pattern; + +public class FrenchDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public FrenchDurationParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new FrenchDurationExtractorConfiguration(), false); + numberCombinedWithUnit = FrenchDurationExtractorConfiguration.NumberCombinedWithUnit; + + anUnitRegex = FrenchDurationExtractorConfiguration.AnUnitRegex; + duringRegex = FrenchDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = FrenchDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = FrenchDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = FrenchDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = FrenchDurationExtractorConfiguration.FollowedUnit; + conjunctionRegex = FrenchDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = FrenchDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = FrenchDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = FrenchDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java new file mode 100644 index 000000000..d1cae9337 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchHolidayParserConfiguration.java @@ -0,0 +1,226 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.function.IntFunction; + +public class FrenchHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public FrenchHolidayParserConfiguration() { + + setHolidayRegexList(FrenchHolidayExtractorConfiguration.HolidayRegexList); + + final HashMap> holidayNamesMap = new HashMap<>(); + for (final Map.Entry entry : FrenchDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + holidayNamesMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + setHolidayNames(ImmutableMap.copyOf(holidayNamesMap)); + } + + private static LocalDateTime newYear(final int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime newYearEve(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime christmasDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime christmasEve(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime valentinesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 14); + } + + private static LocalDateTime whiteLoverDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 14); + } + + private static LocalDateTime foolDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 1); + } + + private static LocalDateTime girlsDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 7); + } + + private static LocalDateTime treePlantDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 12); + } + + private static LocalDateTime femaleDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime youthDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 4); + } + + private static LocalDateTime teacherDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime singlesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + private static LocalDateTime maoBirthday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 26); + } + + private static LocalDateTime inaugurationDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 20); + } + + private static LocalDateTime groundhogDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 2, 2); + } + + private static LocalDateTime stPatrickDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 17); + } + + private static LocalDateTime stGeorgeDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 4, 23); + } + + private static LocalDateTime mayday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + private static LocalDateTime cincoDeMayoday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 5); + } + + private static LocalDateTime baptisteDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 24); + } + + private static LocalDateTime usaIndependenceDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 4); + } + + private static LocalDateTime bastilleDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 7, 14); + } + + private static LocalDateTime halloweenDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime allHallowDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 1); + } + + private static LocalDateTime allSoulsday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 2); + } + + private static LocalDateTime guyFawkesDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 5); + } + + private static LocalDateTime veteransday(final int year) { + return DateUtil.safeCreateFromMinValue(year, 11, 11); + } + + protected static LocalDateTime fathersDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 17); + } + + protected static LocalDateTime mothersDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 27); + } + + protected static LocalDateTime labourDay(final int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + @Override + public int getSwiftYear(final String text) { + final String trimmedText = text.trim(); + int swift = -10; + if (trimmedText.endsWith("prochain")) { + // next - 'l'annee prochain' + swift = 1; + } else if (trimmedText.endsWith("dernier")) { + // last - 'l'annee dernier' + swift = -1; + } else if (trimmedText.endsWith("cette")) { + // this - 'cette annees' + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(final String holiday) { + return holiday + .replaceAll(" ", "") + .replaceAll("'", ""); + } + + protected HashMap> initHolidayFuncs() { + return new HashMap>(super.initHolidayFuncs()) {{ + put("maosbirthday", FrenchHolidayParserConfiguration::maoBirthday); + put("yuandan", FrenchHolidayParserConfiguration::newYear); + put("teachersday", FrenchHolidayParserConfiguration::teacherDay); + put("singleday", FrenchHolidayParserConfiguration::singlesDay); + put("allsaintsday", FrenchHolidayParserConfiguration::halloweenDay); + put("youthday", FrenchHolidayParserConfiguration::youthDay); + put("childrenday", FrenchHolidayParserConfiguration::childrenDay); + put("femaleday", FrenchHolidayParserConfiguration::femaleDay); + put("treeplantingday", FrenchHolidayParserConfiguration::treePlantDay); + put("arborday", FrenchHolidayParserConfiguration::treePlantDay); + put("girlsday", FrenchHolidayParserConfiguration::girlsDay); + put("whiteloverday", FrenchHolidayParserConfiguration::whiteLoverDay); + put("loverday", FrenchHolidayParserConfiguration::valentinesDay); + put("christmas", FrenchHolidayParserConfiguration::christmasDay); + put("xmas", FrenchHolidayParserConfiguration::christmasDay); + put("newyear", FrenchHolidayParserConfiguration::newYear); + put("newyearday", FrenchHolidayParserConfiguration::newYear); + put("newyearsday", FrenchHolidayParserConfiguration::newYear); + put("inaugurationday", FrenchHolidayParserConfiguration::inaugurationDay); + put("groundhougday", FrenchHolidayParserConfiguration::groundhogDay); + put("valentinesday", FrenchHolidayParserConfiguration::valentinesDay); + put("stpatrickday", FrenchHolidayParserConfiguration::stPatrickDay); + put("aprilfools", FrenchHolidayParserConfiguration::foolDay); + put("stgeorgeday", FrenchHolidayParserConfiguration::stGeorgeDay); + put("mayday", FrenchHolidayParserConfiguration::mayday); + put("cincodemayoday", FrenchHolidayParserConfiguration::cincoDeMayoday); + put("baptisteday", FrenchHolidayParserConfiguration::baptisteDay); + put("usindependenceday", FrenchHolidayParserConfiguration::usaIndependenceDay); + put("independenceday", FrenchHolidayParserConfiguration::usaIndependenceDay); + put("bastilleday", FrenchHolidayParserConfiguration::bastilleDay); + put("halloweenday", FrenchHolidayParserConfiguration::halloweenDay); + put("allhallowday", FrenchHolidayParserConfiguration::allHallowDay); + put("allsoulsday", FrenchHolidayParserConfiguration::allSoulsday); + put("guyfawkesday", FrenchHolidayParserConfiguration::guyFawkesDay); + put("veteransday", FrenchHolidayParserConfiguration::veteransday); + put("christmaseve", FrenchHolidayParserConfiguration::christmasEve); + put("newyeareve", FrenchHolidayParserConfiguration::newYearEve); + put("fathersday", FrenchHolidayParserConfiguration::fathersDay); + put("mothersday", FrenchHolidayParserConfiguration::mothersDay); + put("labourday", FrenchHolidayParserConfiguration::labourDay); + } + }; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java new file mode 100644 index 000000000..afd508109 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchMergedParserConfiguration.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import java.util.regex.Pattern; + +public class FrenchMergedParserConfiguration extends FrenchCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public FrenchMergedParserConfiguration(final DateTimeOptions options) { + super(options); + + beforeRegex = FrenchMergedExtractorConfiguration.BeforeRegex; + afterRegex = FrenchMergedExtractorConfiguration.AfterRegex; + sinceRegex = FrenchMergedExtractorConfiguration.SinceRegex; + aroundRegex = FrenchMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = FrenchMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = FrenchDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = FrenchMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new FrenchSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new FrenchHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java new file mode 100644 index 000000000..b688497b0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchSetParserConfiguration.java @@ -0,0 +1,195 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import java.util.regex.Pattern; + +public class FrenchSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private final IDateTimeExtractor durationExtractor; + private final IDateTimeParser durationParser; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IDateExtractor dateExtractor; + private final IDateTimeParser dateParser; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeParser dateTimeParser; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeParser datePeriodParser; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeParser timePeriodParser; + private final IDateTimeExtractor dateTimePeriodExtractor; + private final IDateTimeParser dateTimePeriodParser; + private final ImmutableMap unitMap; + private final Pattern eachPrefixRegex; + private final Pattern periodicRegex; + private final Pattern eachUnitRegex; + private final Pattern eachDayRegex; + private final Pattern setWeekDayRegex; + private final Pattern setEachRegex; + + public FrenchSetParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + durationExtractor = config.getDurationExtractor(); + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + durationParser = config.getDurationParser(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + unitMap = config.getUnitMap(); + + eachPrefixRegex = FrenchSetExtractorConfiguration.EachPrefixRegex; + periodicRegex = FrenchSetExtractorConfiguration.PeriodicRegex; + eachUnitRegex = FrenchSetExtractorConfiguration.EachUnitRegex; + eachDayRegex = FrenchSetExtractorConfiguration.EachDayRegex; + setWeekDayRegex = FrenchSetExtractorConfiguration.SetWeekDayRegex; + setEachRegex = FrenchSetExtractorConfiguration.SetEachRegex; + } + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(final String text) { + final String trimmedText = text.trim(); + final String timex; + if (trimmedText.equals("quotidien") || trimmedText.equals("quotidienne") || + trimmedText.equals("jours") || trimmedText.equals("journellement")) { + // daily + timex = "P1D"; + } else if (trimmedText.equals("hebdomadaire")) { + // weekly + timex = "P1W"; + } else if (trimmedText.equals("bihebdomadaire")) { + // bi weekly + timex = "P2W"; + } else if (trimmedText.equals("mensuel") || trimmedText.equals("mensuelle")) { + // monthly + timex = "P1M"; + } else if (trimmedText.equals("annuel") || trimmedText.equals("annuellement")) { + // yearly/annually + timex = "P1Y"; + } else { + return new MatchedTimexResult(false, null); + } + + return new MatchedTimexResult(true, timex); + } + + public MatchedTimexResult getMatchedUnitTimex(final String text) { + final String trimmedText = text.trim(); + final String timex; + if (trimmedText.equals("jour") || trimmedText.equals("journee")) { + timex = "P1D"; + } else if (trimmedText.equals("semaine")) { + timex = "P1W"; + } else if (trimmedText.equals("mois")) { + timex = "P1M"; + } else if (trimmedText.equals("an") || trimmedText.equals("annee")) { + // year + timex = "P1Y"; + } else { + return new MatchedTimexResult(false, null); + } + + return new MatchedTimexResult(true, timex); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java new file mode 100644 index 000000000..c66df2ae4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParser.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.time.LocalDateTime; + +public class FrenchTimeParser extends BaseTimeParser { + + public FrenchTimeParser(final ITimeParserConfiguration config) { + super(config); + } + + @Override + protected DateTimeResolutionResult internalParse(final String text, final LocalDateTime referenceTime) { + DateTimeResolutionResult innerResult = super.internalParse(text, referenceTime); + + if (!innerResult.getSuccess()) { + innerResult = parseIsh(text, referenceTime); + } + + return innerResult; + } + + // parse "noonish", "11-ish" + private DateTimeResolutionResult parseIsh(final String text, final LocalDateTime referenceTime) { + final DateTimeResolutionResult result = new DateTimeResolutionResult(); + + final ConditionalMatch match = RegexExtension.matchExact(FrenchTimeExtractorConfiguration.IshRegex, text, true); + if (match.getSuccess()) { + final String hourStr = match.getMatch().get().getGroup(Constants.HourGroupName).value; + int hour = Constants.HalfDayHourCount; + + if (!StringUtility.isNullOrEmpty(hourStr)) { + hour = Integer.parseInt(hourStr); + } + + result.setTimex(String.format("T%02d", hour)); + final LocalDateTime resultTime = DateUtil.safeCreateFromMinValue( + referenceTime.getYear(), + referenceTime.getMonthValue(), + referenceTime.getDayOfMonth(), + hour, 0, 0); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + result.setSuccess(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java new file mode 100644 index 000000000..c33e542ba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimeParserConfiguration.java @@ -0,0 +1,156 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.regex.Pattern; + +public class FrenchTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + public final Pattern atRegex; + private final Iterable timeRegexes; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + public String timeTokenPrefix = FrenchDateTime.TimeTokenPrefix; + public Pattern mealTimeRegex; + + public FrenchTimeParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = FrenchTimeExtractorConfiguration.AtRegex; + timeRegexes = FrenchTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return timeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(final String prefix, int hour, int min, final boolean hasMin) { + + int deltaMin = 0; + final String trimmedPrefix = prefix.trim(); + + // c'este 8 heures et demie, - "it's half past 8" + if (trimmedPrefix.endsWith("demie")) { + deltaMin = 30; + } else if (trimmedPrefix.endsWith("un quart") || trimmedPrefix.endsWith("quart")) { + deltaMin = 15; + } else if (trimmedPrefix.endsWith("trois quarts")) { + deltaMin = 45; + } else { + final Match[] match = RegExpUtility + .getMatches(FrenchTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix); + String minStr; + if (match.length > 0) { + minStr = match[0].getGroup("deltamin").value; + if (!StringUtility.isNullOrEmpty(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match[0].getGroup("deltaminnum").value; + deltaMin = numbers.get(minStr); + } + } + + } + + // 'to' i.e 'one to five' = 'un à cinq' + if (trimmedPrefix.endsWith("à")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + return new PrefixAdjustResult(hour, min, true); + } + + @Override + public SuffixAdjustResult adjustBySuffix(final String suffix, + int hour, + final int min, + final boolean hasMin, + boolean hasAm, + boolean hasPm) { + + int deltaHour = 0; + final ConditionalMatch match = RegexExtension + .matchExact(FrenchTimeExtractorConfiguration.TimeSuffix, suffix, true); + + if (match.getSuccess()) { + final String oclockStr = match.getMatch().get().getGroup("heures").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + final String matchAmStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(matchAmStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } + + hasAm = true; + } + + final String matchPmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(matchPmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + + hasPm = true; + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java new file mode 100644 index 000000000..acb8080a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/parsers/FrenchTimePeriodParserConfiguration.java @@ -0,0 +1,158 @@ +package com.microsoft.recognizers.text.datetime.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.french.extractors.FrenchTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import java.util.regex.Pattern; + +public class FrenchTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public FrenchTimePeriodParserConfiguration(final ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + pureNumberFromToRegex = FrenchTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = FrenchTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = FrenchTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = FrenchTimePeriodExtractorConfiguration.TimeOfDayRegex; + generalEndingRegex = FrenchTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = FrenchTimePeriodExtractorConfiguration.TillRegex; + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(final String text, + final String timex, + int beginHour, + int endHour, + int endMin) { + String mutatedText = text.trim(); + if (mutatedText.endsWith("s")) { + mutatedText = mutatedText.substring(0, mutatedText.length() - 1); + } + + final String trimmedText = mutatedText; + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + if (FrenchDateTime.MorningTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Morning; + } else if (FrenchDateTime.AfternoonTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Afternoon; + } else if (FrenchDateTime.EveningTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Evening; + } else if (FrenchDateTime.DaytimeTermList.stream().anyMatch(o -> trimmedText.equals(o))) { + timeOfDay = Constants.Daytime; + } else if (FrenchDateTime.NightTermList.stream().anyMatch(o -> trimmedText.endsWith(o))) { + timeOfDay = Constants.Night; + } else { + return new MatchedTimeRangeResult(false, null, beginHour, endHour, endMin); + } + + final TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), + result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..7dba9ecea --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/french/utilities/FrenchDatetimeUtilityConfiguration.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.french.utilities; + +import com.microsoft.recognizers.text.datetime.resources.FrenchDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.regex.Pattern; + +public class FrenchDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AgoRegex); + + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.LaterRegex); + + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.InConnectorRegex); + + public static final Pattern WithinNextPrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.WithinNextPrefixRegex); + + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmDescRegex); + + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.PmDescRegex); + + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.AmPmDescRegex); + + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.RangeUnitRegex); + + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.TimeUnitRegex); + + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(FrenchDateTime.DateUnitRegex); + + public static final Pattern CommonDatePrefixRegex = RegExpUtility + .getSafeRegExp(FrenchDateTime.CommonDatePrefixRegex); + + @Override + public final Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public final Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public final Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public final Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public final Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public final Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public final Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public final Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public final Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java new file mode 100644 index 000000000..249271539 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/models/DateTimeModel.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.datetime.models; + +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.utilities.FormatUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.stream.Collectors; + +public class DateTimeModel implements IModel { + + protected final IDateTimeExtractor extractor; + protected final IDateTimeParser parser; + + @Override + public String getModelTypeName() { + return Constants.MODEL_DATETIME; + } + + public DateTimeModel(IDateTimeParser parser, IDateTimeExtractor extractor) { + this.extractor = extractor; + this.parser = parser; + } + + @Override + public List parse(String query) { + return this.parse(query, LocalDateTime.now()); + } + + public List parse(String query, LocalDateTime reference) { + query = FormatUtility.preprocess(query); + + List parsedDateTimes = new ArrayList<>(); + + try { + List extractResults = extractor.extract(query, reference); + + for (ExtractResult result : extractResults) { + DateTimeParseResult parseResult = parser.parse(result, reference); + + if (parseResult.getValue() instanceof List) { + parsedDateTimes.addAll((List)parseResult.getValue()); + } else { + parsedDateTimes.add(parseResult); + } + } + + // Filter out ambiguous cases. Naïve approach. + parsedDateTimes = parser.filterResults(query, parsedDateTimes); + + } catch (Exception e) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + e.getMessage(); + } + + return parsedDateTimes.stream().map(this::getModelResult).collect(Collectors.toList()); + } + + private ModelResult getModelResult(DateTimeParseResult parsedDateTime) { + + int start = parsedDateTime.getStart(); + int end = parsedDateTime.getStart() + parsedDateTime.getLength() - 1; + String typeName = parsedDateTime.getType(); + SortedMap resolution = (SortedMap)parsedDateTime.getValue(); + String text = parsedDateTime.getText(); + + ModelResult result = new ModelResult(text, start, end, typeName, resolution); + + String[] types = parsedDateTime.getType().split("\\."); + String type = types[types.length - 1]; + if (type.equals(Constants.SYS_DATETIME_DATETIMEALT)) { + result = new ExtendedModelResult(result, getParentText(parsedDateTime)); + } + + return result; + } + + private String getParentText(DateTimeParseResult parsedDateTime) { + Map map = (Map)parsedDateTime.getData(); + Object result = map.get(ExtendedModelResult.ParentTextKey); + return String.valueOf(result); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java new file mode 100644 index 000000000..97a09e77b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateParser.java @@ -0,0 +1,739 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateContext; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDateParser implements IDateTimeParser { + + private final IDateParserConfiguration config; + + public BaseDateParser(IDateParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATE; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + return parse(extResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = this.parseBasicRegexMatch(er.getText(), referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = this.parseImplicitDate(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseWeekdayOfMonth(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDurationWithAgoAndLater(er.getText(), referenceDate); + } + + // NumberWithMonth must be the second last one, because it only need to find a number and a month to get a "success" + if (!innerResult.getSuccess()) { + innerResult = this.parseNumberWithMonth(er.getText(), referenceDate); + } + + // SingleNumber last one + if (!innerResult.getSuccess()) { + innerResult = this.parseSingleNumber(er.getText(), referenceDate); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + // parse basic patterns in DateRegexList + private DateTimeResolutionResult parseBasicRegexMatch(String text, LocalDateTime referenceDate) { + String trimmedText = text.trim(); + + for (Pattern regex : this.config.getDateRegexes()) { + int offset = 0; + String relativeStr = null; + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(regex, this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + // Handing cases like "(this)? 5.12" which only be recognized in "on (this)? 5.12" + if (match.isPresent()) { + offset = this.config.getDateTokenPrefix().length(); + relativeStr = match.get().getGroup("order").value.toLowerCase(); + } + } + + if (match.isPresent()) { + ConditionalMatch relativeRegex = RegexExtension.matchEnd(config.getStrictRelativeRegex(), text.substring(0, match.get().index), true); + boolean isContainRelative = relativeRegex.getSuccess() && match.get().index + match.get().length == trimmedText.length(); + if ((match.get().index == offset && match.get().length == trimmedText.length()) || isContainRelative) { + // Handing cases which contain relative term like "this 5/12" + if (match.get().index != offset) { + relativeStr = relativeRegex.getMatch().get().value; + } + + // LUIS value string will be set in Match2Date method + DateTimeResolutionResult ret = this.match2Date(match, referenceDate, relativeStr); + return ret; + } + } + } + + return new DateTimeResolutionResult(); + } + + // match several other cases + // including 'today', 'the day after tomorrow', 'on 13' + private DateTimeResolutionResult parseImplicitDate(String text, LocalDateTime referenceDate) { + String trimmedText = text.trim(); + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // handle "on 12" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getOnRegex(), this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + if (match.isPresent() && match.get().index == 3 && match.get().length == trimmedText.length()) { + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("day").value.toLowerCase(); + int day = this.config.getDayOfMonth().get(dayStr); + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + + LocalDateTime futureDate; + LocalDateTime pastDate; + String tryStr = DateTimeFormatUtil.luisDate(year, month, day); + if (DateUtil.tryParse(tryStr) != null) { + futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusMonths(1); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusMonths(1); + } + } else { + futureDate = DateUtil.safeCreateFromMinValue(year, month + 1, day); + pastDate = DateUtil.safeCreateFromMinValue(year, month - 1, day); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle "today", "the day before yesterday" + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getSpecialDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + int swift = getSwiftDay(exactMatch.getMatch().get().value); + + LocalDateTime value = referenceDate.toLocalDate().atStartOfDay().plusDays(swift); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "two days from tomorrow" + exactMatch = RegexExtension.matchExact(this.config.getSpecialDayWithNumRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + + int swift = getSwiftDay(exactMatch.getMatch().get().getGroup("day").value); + List numErs = this.config.getIntegerExtractor().extract(trimmedText); + Object numberParsed = this.config.getNumberParser().parse(numErs.get(0)).getValue(); + int numOfDays = Math.round(((Double)numberParsed).floatValue()); + + LocalDateTime value = referenceDate.plusDays(numOfDays + swift); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "two sundays from now" + exactMatch = RegexExtension.matchExact(this.config.getRelativeWeekDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + List numErs = this.config.getIntegerExtractor().extract(trimmedText); + Object numberParsed = this.config.getNumberParser().parse(numErs.get(0)).getValue(); + int num = Math.round(((Double)numberParsed).floatValue()); + + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = referenceDate; + + // Check whether the determined day of this week has passed. + if (value.getDayOfWeek().getValue() > this.config.getDayOfWeek().get(weekdayStr)) { + num--; + } + + while (num-- > 0) { + value = DateUtil.next(value, this.config.getDayOfWeek().get(weekdayStr)); + } + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "next Sunday" + exactMatch = RegexExtension.matchExact(this.config.getNextRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.next(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "this Friday" + exactMatch = RegexExtension.matchExact(this.config.getThisRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.thisDate(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "last Friday", "last mon" + exactMatch = RegexExtension.matchExact(this.config.getLastRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + LocalDateTime value = DateUtil.last(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + ret.setTimex(DateTimeFormatUtil.luisDate(value)); + ret.setFutureValue(value); + ret.setPastValue(value); + ret.setSuccess(true); + + return ret; + } + + // handle "Friday" + exactMatch = RegexExtension.matchExact(this.config.getWeekDayRegex(), trimmedText, true); + + if (exactMatch.getSuccess()) { + String weekdayStr = exactMatch.getMatch().get().getGroup("weekday").value.toLowerCase(); + int weekDay = this.config.getDayOfWeek().get(weekdayStr); + LocalDateTime value = DateUtil.thisDate(referenceDate, this.config.getDayOfWeek().get(weekdayStr)); + + if (weekDay == 0) { + weekDay = 7; + } + + if (weekDay < referenceDate.getDayOfWeek().getValue()) { + value = DateUtil.next(referenceDate, weekDay); + } + + ret.setTimex("XXXX-WXX-" + weekDay); + LocalDateTime futureDate = value; + LocalDateTime pastDate = value; + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusDays(7); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusDays(7); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle "for the 27th." + match = Arrays.stream(RegExpUtility.getMatches(this.config.getForTheRegex(), text)).findFirst(); + if (match.isPresent()) { + int day; + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("DayOfMonth").value.toLowerCase(); + + int start = match.get().getGroup("DayOfMonth").index; + int length = match.get().getGroup("DayOfMonth").length; + + // create a extract comments which content ordinal string of text + ExtractResult er = new ExtractResult(start, length, dayStr, null, null); + + Object numberParsed = this.config.getNumberParser().parse(er).getValue(); + day = Math.round(((Double)numberParsed).floatValue()); + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + + LocalDateTime futureDate; + String tryStr = DateTimeFormatUtil.luisDate(year, month, day); + + if (DateUtil.tryParse(tryStr) != null) { + futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + } else { + futureDate = DateUtil.safeCreateFromMinValue(year, month + 1, day); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(ret.getFutureValue()); + ret.setSuccess(true); + + return ret; + } + + // handling cases like 'Thursday the 21st', which both 'Thursday' and '21st' refer to a same date + match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayAndDayOfMonthRegex(), text)).findFirst(); + if (match.isPresent()) { + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + String dayStr = match.get().getGroup("DayOfMonth").value.toLowerCase(); + + int start = match.get().getGroup("DayOfMonth").index; + int length = match.get().getGroup("DayOfMonth").length; + + // create a extract comments which content ordinal string of text + ExtractResult erTmp = new ExtractResult(start, length, dayStr, null, null); + + Object numberParsed = this.config.getNumberParser().parse(erTmp).getValue(); + int day = Math.round(((Double)numberParsed).floatValue()); + + // the validity of the phrase is guaranteed in the Date Extractor + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + ret.setFutureValue(LocalDateTime.of(year, month, day, 0, 0)); + ret.setPastValue(LocalDateTime.of(year, month, day, 0, 0)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseWeekdayOfMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayOfMonthRegex(), this.config.getDateTokenPrefix() + trimmedText)).findFirst(); + if (!match.isPresent()) { + return ret; + } + + String cardinalStr = match.get().getGroup("cardinal").value; + String weekdayStr = match.get().getGroup("weekday").value; + String monthStr = match.get().getGroup("month").value; + Boolean noYear = false; + int year; + + int cardinal = this.config.isCardinalLast(cardinalStr) ? 5 : this.config.getCardinalMap().get(cardinalStr); + + int weekday = this.config.getDayOfWeek().get(weekdayStr); + int month; + if (StringUtility.isNullOrEmpty(monthStr)) { + int swift = this.config.getSwiftMonthOrYear(trimmedText); + + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + } else { + month = this.config.getMonthOfYear().get(monthStr); + year = referenceDate.getYear(); + noYear = true; + } + + LocalDateTime value = computeDate(cardinal, weekday, month, year); + if (value.getMonthValue() != month) { + cardinal -= 1; + value = value.minusDays(7); + } + + LocalDateTime futureDate = value; + LocalDateTime pastDate = value; + if (noYear && futureDate.isBefore(referenceDate)) { + futureDate = computeDate(cardinal, weekday, month, year + 1); + if (futureDate.getMonthValue() != month) { + futureDate = futureDate.minusDays(7); + } + } + + if (noYear && (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate))) { + pastDate = computeDate(cardinal, weekday, month, year - 1); + if (pastDate.getMonthValue() != month) { + pastDate = pastDate.minusDays(7); + } + } + + // here is a very special case, timeX followe future date + ret.setTimex("XXXX-" + String.format("%02d", month) + "-WXX-" + weekday + "-#" + cardinal); + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // Handle cases like "two days ago" + private DateTimeResolutionResult parseDurationWithAgoAndLater(String text, LocalDateTime referenceDate) { + + return AgoLaterUtil.parseDurationWithAgoAndLater(text, referenceDate, + config.getDurationExtractor(), config.getDurationParser(), config.getUnitMap(), config.getUnitRegex(), + config.getUtilityConfiguration(), this::getSwiftDay); + } + + // handle cases like "January first", "twenty-two of August" + // handle cases like "20th of next month" + private DateTimeResolutionResult parseNumberWithMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + int month = 0; + int day = 0; + int year = referenceDate.getYear(); + Boolean ambiguous = true; + + List er = this.config.getOrdinalExtractor().extract(trimmedText); + if (er.size() == 0) { + er = this.config.getIntegerExtractor().extract(trimmedText); + } + + if (er.size() == 0) { + return ret; + } + + Object numberParsed = this.config.getNumberParser().parse(er.get(0)).getValue(); + int num = Math.round(((Double)numberParsed).floatValue()); + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getMonthRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + month = this.config.getMonthOfYear().get(match.get().value.trim()); + day = num; + + String suffix = trimmedText.substring((er.get(0).getStart() + er.get(0).getLength())); + + Optional matchYear = Arrays.stream(RegExpUtility.getMatches(this.config.getYearSuffix(), suffix)).findFirst(); + if (matchYear.isPresent()) { + year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matchYear.get()); + if (year != Constants.InvalidYear) { + ambiguous = false; + } + } + } + + // handling relatived month + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeMonthRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + String monthStr = match.get().getGroup("order").value; + int swift = this.config.getSwiftMonthOrYear(monthStr); + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + day = num; + ambiguous = false; + } + } + + // handling casesd like 'second Sunday' + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekDayRegex(), trimmedText)).findFirst(); + if (match.isPresent()) { + month = referenceDate.getMonthValue(); + // resolve the date of wanted week day + int wantedWeekDay = this.config.getDayOfWeek().get(match.get().getGroup("weekday").value); + LocalDateTime firstDate = DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1); + int firstWeekDay = firstDate.getDayOfWeek().getValue(); + LocalDateTime firstWantedWeekDay = firstDate.plusDays(wantedWeekDay > firstWeekDay ? wantedWeekDay - firstWeekDay : wantedWeekDay - firstWeekDay + 7); + int answerDay = firstWantedWeekDay.getDayOfMonth() + (num - 1) * 7; + day = answerDay; + ambiguous = false; + } + } + + if (!match.isPresent()) { + return ret; + } + + // for LUIS format value string + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (ambiguous) { + ret.setTimex(DateTimeFormatUtil.luisDate(-1, month, day)); + if (futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusYears(1); + } + + if (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate)) { + pastDate = pastDate.minusYears(1); + } + } else { + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + // handle cases like "the 27th". In the extractor, only the unmatched weekday and date will output this date. + private DateTimeResolutionResult parseSingleNumber(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + int day = 0; + + List er = this.config.getOrdinalExtractor().extract(trimmedText); + if (er.size() == 0) { + er = this.config.getIntegerExtractor().extract(trimmedText); + } + + if (er.size() == 0) { + return ret; + } + + Object numberParsed = this.config.getNumberParser().parse(er.get(0)).getValue(); + day = Math.round(((Double)numberParsed).floatValue()); + + int month = referenceDate.getMonthValue(); + int year = referenceDate.getYear(); + + // for LUIS format value string + ret.setTimex(DateTimeFormatUtil.luisDate(-1, -1, day)); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + + if (!futureDate.isEqual(LocalDateTime.MIN) && futureDate.isBefore(referenceDate)) { + futureDate = futureDate.plusMonths(1); + } + + if (!pastDate.isEqual(LocalDateTime.MIN) && (pastDate.isEqual(referenceDate) || pastDate.isAfter(referenceDate))) { + pastDate = pastDate.minusMonths(1); + } + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + private static LocalDateTime computeDate(int cardinal, int weekday, int month, int year) { + LocalDateTime firstDay = DateUtil.safeCreateFromMinValue(year, month, 1); + LocalDateTime firstWeekday = DateUtil.thisDate(firstDay, weekday); + int dayOfWeekOfFirstDay = firstDay.getDayOfWeek().getValue(); + + if (weekday == 0) { + weekday = 7; + } + + if (dayOfWeekOfFirstDay == 0) { + dayOfWeekOfFirstDay = 7; + } + + if (weekday < dayOfWeekOfFirstDay) { + firstWeekday = DateUtil.next(firstDay, weekday); + } + + return firstWeekday.plusDays(7 * (cardinal - 1)); + } + + // parse a regex match which includes 'day', 'month' and 'year' (optional) group + private DateTimeResolutionResult match2Date(Optional match, LocalDateTime referenceDate, String relativeStr) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int month = 0; + int day = 0; + int year = 0; + + String monthStr = match.get().getGroup("month").value.toLowerCase(); + String dayStr = match.get().getGroup("day").value.toLowerCase(); + String weekdayStr = match.get().getGroup("weekday").value.toLowerCase(); + String yearStr = match.get().getGroup("year").value.toLowerCase(); + String writtenYearStr = match.get().getGroup("fullyear").value.toLowerCase(); + + if (this.config.getMonthOfYear().containsKey(monthStr) && this.config.getDayOfMonth().containsKey(dayStr)) { + + month = this.config.getMonthOfYear().get(monthStr); + day = this.config.getDayOfMonth().get(dayStr); + + if (!StringUtility.isNullOrEmpty(yearStr)) { + + year = this.config.getDateExtractor().getYearFromText(match.get()); + + } else if (!StringUtility.isNullOrEmpty(yearStr)) { + + year = Integer.parseInt(yearStr); + if (year < 100 && year >= Constants.MinTwoDigitYearPastNum) { + year += 1900; + } else if (year >= 0 && year < Constants.MaxTwoDigitYearFutureNum) { + year += 2000; + } + } + } + + Boolean noYear = false; + if (year == 0) { + year = referenceDate.getYear(); + if (!StringUtility.isNullOrEmpty(relativeStr)) { + int swift = config.getSwiftMonthOrYear(relativeStr); + + // @TODO Improve handling of next/last in particular cases "next friday 5/12" when the next friday is not 5/12. + if (!StringUtility.isNullOrEmpty(weekdayStr)) { + swift = 0; + } + year += swift; + } else { + noYear = true; + } + + ret.setTimex(DateTimeFormatUtil.luisDate(-1, month, day)); + } else { + ret.setTimex(DateTimeFormatUtil.luisDate(year, month, day)); + } + + HashMap futurePastDates = DateContext.generateDates(noYear, referenceDate, year, month, day); + LocalDateTime futureDate = futurePastDates.get(Constants.FutureDate); + LocalDateTime pastDate = futurePastDates.get(Constants.PastDate); + + ret.setFutureValue(futureDate); + ret.setPastValue(pastDate); + ret.setSuccess(true); + + return ret; + } + + private int getSwiftDay(String text) { + String trimmedText = this.config.normalize(text.trim().toLowerCase()); + int swift = 0; + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeDayRegex(), text)).findFirst(); + + // The sequence here is important + // As suffix "day before yesterday" should be matched before suffix "day before" or "yesterday" + if (config.getSameDayTerms().contains(trimmedText)) { + swift = 0; + } else if (endsWithTerms(trimmedText, config.getPlusTwoDayTerms())) { + swift = 2; + } else if (endsWithTerms(trimmedText, config.getMinusTwoDayTerms())) { + swift = -2; + } else if (endsWithTerms(trimmedText, config.getPlusOneDayTerms())) { + swift = 1; + } else if (endsWithTerms(trimmedText, config.getMinusOneDayTerms())) { + swift = -1; + } else if (match.isPresent()) { + swift = getSwift(text); + } + + return swift; + } + + private int getSwift(String text) { + String trimmedText = text.trim().toLowerCase(); + + int swift = 0; + if (RegExpUtility.getMatches(this.config.getNextPrefixRegex(), trimmedText).length > 0) { + swift = 1; + } else if (RegExpUtility.getMatches(this.config.getPastPrefixRegex(), trimmedText).length > 0) { + swift = -1; + } + + return swift; + } + + private boolean endsWithTerms(String text, List terms) { + boolean result = false; + + for (String term : terms) { + if (text.endsWith(term)) { + result = true; + break; + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java new file mode 100644 index 000000000..f3d5c94a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDatePeriodParser.java @@ -0,0 +1,1897 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateContext; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.DurationParsingUtil; +import com.microsoft.recognizers.text.datetime.utilities.GetModAndDateResult; +import com.microsoft.recognizers.text.datetime.utilities.NthBusinessDayResult; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.sql.Time; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.Month; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.javatuples.Pair; + +public class BaseDatePeriodParser implements IDateTimeParser { + + private static final String parserName = Constants.SYS_DATETIME_DATEPERIOD; //"DatePeriod"; + private static boolean inclusiveEndPeriod = false; + + private final IDatePeriodParserConfiguration config; + + public BaseDatePeriodParser(IDatePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime refDate) { + + DateTimeResolutionResult value = null; + if (er.getType().equals(parserName)) { + DateTimeResolutionResult innerResult = parseBaseDatePeriod(er.getText(), refDate); + + if (!innerResult.getSuccess()) { + innerResult = parseComplexDatePeriod(er.getText(), refDate); + } + + if (innerResult.getSuccess()) { + if (innerResult.getMod() != null && innerResult.getMod().equals(Constants.BEFORE_MOD)) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())) + .build()); + } else if (innerResult.getMod() != null && innerResult.getMod().equals(Constants.AFTER_MOD)) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())) + .build()); + } else if (innerResult.getFutureValue() != null && innerResult.getPastValue() != null) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getFutureValue()).getValue0())) + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getFutureValue()).getValue1())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.START_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getPastValue()).getValue0())) + .put(TimeTypeConstants.END_DATE, + DateTimeFormatUtil.formatDate(((Pair)innerResult.getPastValue()).getValue1())) + .build()); + } else { + innerResult.setFutureResolution(new HashMap<>()); + innerResult.setPastResolution(new HashMap<>()); + } + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), value, "", "", er.getMetadata()); + + if (value != null) { + ret.setTimexStr(value.getTimex()); + } + + return ret; + } + + // Process case like "from|between START to|and END" where START/END can be dateRange or datePoint + private DateTimeResolutionResult parseComplexDatePeriod(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getComplexDatePeriodRegex(), text)).findFirst(); + + if (match.isPresent()) { + LocalDateTime futureBegin = LocalDateTime.MIN; + LocalDateTime futureEnd = LocalDateTime.MIN; + LocalDateTime pastBegin = LocalDateTime.MIN; + LocalDateTime pastEnd = LocalDateTime.MIN; + boolean isSpecificDate = false; + boolean isStartByWeek = false; + boolean isEndByWeek = false; + DateContext dateContext = getYearContext(match.get().getGroup("start").value.trim(), match.get().getGroup("end").value.trim(), text); + + DateTimeResolutionResult startResolution = parseSingleTimePoint(match.get().getGroup("start").value.trim(), referenceDate, dateContext); + + if (startResolution.getSuccess()) { + futureBegin = (LocalDateTime)startResolution.getFutureValue(); + pastBegin = (LocalDateTime)startResolution.getPastValue(); + isSpecificDate = true; + } else { + startResolution = parseBaseDatePeriod(match.get().getGroup("start").value.trim(), referenceDate, dateContext); + if (startResolution.getSuccess()) { + futureBegin = ((Pair)startResolution.getFutureValue()).getValue0(); + pastBegin = ((Pair)startResolution.getPastValue()).getValue0(); + + if (startResolution.getTimex().contains("-W")) { + isStartByWeek = true; + } + } + } + + if (startResolution.getSuccess()) { + DateTimeResolutionResult endResolution = parseSingleTimePoint(match.get().getGroup("end").value.trim(), referenceDate, dateContext); + + if (endResolution.getSuccess()) { + futureEnd = (LocalDateTime)endResolution.getFutureValue(); + pastEnd = (LocalDateTime)endResolution.getPastValue(); + isSpecificDate = true; + } else { + endResolution = parseBaseDatePeriod(match.get().getGroup("end").value.trim(), referenceDate, dateContext); + + if (endResolution.getSuccess()) { + futureEnd = ((Pair)endResolution.getFutureValue()).getValue0(); + pastEnd = ((Pair)endResolution.getPastValue()).getValue0(); + + if (endResolution.getTimex().contains("-W")) { + isEndByWeek = true; + } + } + } + + if (endResolution.getSuccess()) { + if (futureBegin.isAfter(futureEnd)) { + if (dateContext == null || dateContext.isEmpty()) { + futureBegin = pastBegin; + } else { + futureBegin = dateContext.swiftDateObject(futureBegin, futureEnd); + } + } + + if (pastEnd.isBefore(pastBegin)) { + if (dateContext == null || dateContext.isEmpty()) { + pastEnd = futureEnd; + } else { + pastBegin = dateContext.swiftDateObject(pastBegin, pastEnd); + } + } + + // If both begin/end are date ranges in "Month", the Timex should be ByMonth + // The year period case should already be handled in Basic Cases + DatePeriodTimexType datePeriodTimexType = DatePeriodTimexType.ByMonth; + + if (isSpecificDate) { + // If at least one of the begin/end is specific date, the Timex should be ByDay + datePeriodTimexType = DatePeriodTimexType.ByDay; + } else if (isStartByWeek && isEndByWeek) { + // If both begin/end are date ranges in "Week", the Timex should be ByWeek + datePeriodTimexType = DatePeriodTimexType.ByWeek; + } + + ret.setTimex(TimexUtility.generateDatePeriodTimex(futureBegin, futureEnd, datePeriodTimexType, pastBegin, pastEnd)); + + ret.setFutureValue(new Pair<>(futureBegin, futureEnd)); + ret.setPastValue(new Pair<>(pastBegin, pastEnd)); + ret.setSuccess(true); + } + } + } + return ret; + } + + private DateTimeResolutionResult parseBaseDatePeriod(String text, LocalDateTime referenceDate) { + return parseBaseDatePeriod(text, referenceDate, null); + } + + private DateTimeResolutionResult parseBaseDatePeriod(String text, LocalDateTime referenceDate, DateContext dateContext) { + DateTimeResolutionResult innerResult = parseMonthWithYear(text, referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = parseSimpleCases(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseOneWordPeriod(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = mergeTwoTimePoints(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfMonth(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseHalfYear(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseQuarter(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseSeason(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWhichWeek(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseWeekOfDate(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseMonthOfDate(text, referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = parseDecade(text, referenceDate); + } + + // Cases like "within/less than/more than x weeks from/before/after today" + if (!innerResult.getSuccess()) { + innerResult = parseDatePointWithAgoAndLater(text, referenceDate); + } + + // Parse duration should be at the end since it will extract "the last week" from "the last week of July" + if (!innerResult.getSuccess()) { + innerResult = parseDuration(text, referenceDate); + } + + // Cases like "21st century" + if (!innerResult.getSuccess()) { + innerResult = parseOrdinalNumberWithCenturySuffix(text, referenceDate); + } + + if (innerResult.getSuccess() && dateContext != null) { + innerResult = dateContext.processDatePeriodEntityResolution(innerResult); + } + + return innerResult; + } + + private DateTimeResolutionResult parseOrdinalNumberWithCenturySuffix(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional er = this.config.getOrdinalExtractor().extract(text).stream().findFirst(); + + if (er.isPresent() && er.get().getStart() + er.get().getLength() < text.length()) { + String afterString = text.substring(er.get().getStart() + er.get().getLength()).trim(); + + // It falls into the cases like "21st century" + if (Arrays.stream(RegExpUtility.getMatches(this.config.getCenturySuffixRegex(), afterString)).findFirst().isPresent()) { + ParseResult number = this.config.getNumberParser().parse(er.get()); + + if (number.getValue() != null) { + // Note that 1st century means from year 0 - 100 + int startYear = (Math.round(((Double)number.getValue()).floatValue()) - 1) * Constants.CenturyYearsCount; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(startYear, 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(startYear + Constants.CenturyYearsCount, 1, 1); + + String startLuisStr = DateTimeFormatUtil.luisDate(startDate); + String endLuisStr = DateTimeFormatUtil.luisDate(endDate); + String durationTimex = "P" + Constants.CenturyYearsCount + "Y"; + + ret.setTimex(String.format("(%s,%s,%s)", startLuisStr, endLuisStr, durationTimex)); + ret.setFutureValue(new Pair<>(startDate, endDate)); + ret.setPastValue(new Pair<>(startDate, endDate)); + ret.setSuccess(true); + } + } + } + + return ret; + } + + private DateTimeResolutionResult parseDatePointWithAgoAndLater(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional er = this.config.getDateExtractor().extract(text, referenceDate).stream().findFirst(); + + if (er.isPresent()) { + String beforeString = text.substring(0, er.get().getStart()); + boolean isAgo = Arrays.stream(RegExpUtility.getMatches(this.config.getAgoRegex(), er.get().getText())).findFirst().isPresent(); + boolean isLater = Arrays.stream(RegExpUtility.getMatches(this.config.getLaterRegex(), er.get().getText())).findFirst().isPresent(); + + if (!StringUtility.isNullOrEmpty(beforeString) && (isAgo || isLater)) { + boolean isLessThanOrWithIn = false; + boolean isMoreThan = false; + + // cases like "within 3 days from yesterday/tomorrow" does not make any sense + if (er.get().getText().contains("today") || er.get().getText().contains("now")) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWithinNextPrefixRegex(), beforeString)).findFirst(); + if (match.isPresent()) { + boolean isNext = !StringUtility.isNullOrEmpty(match.get().getGroup("next").value); + + // cases like "within the next 5 days before today" is not acceptable + if (!(isNext && isAgo)) { + isLessThanOrWithIn = true; + } + } + } + + isLessThanOrWithIn = isLessThanOrWithIn || (Arrays.stream(RegExpUtility.getMatches(this.config.getLessThanRegex(), beforeString)).findFirst().isPresent()); + isMoreThan = Arrays.stream(RegExpUtility.getMatches(this.config.getMoreThanRegex(), beforeString)).findFirst().isPresent(); + + DateTimeParseResult pr = this.config.getDateParser().parse(er.get(), referenceDate); + Optional durationExtractionResult = this.config.getDurationExtractor().extract(er.get().getText()).stream().findFirst(); + + if (durationExtractionResult.isPresent()) { + ParseResult duration = this.config.getDurationParser().parse(durationExtractionResult.get()); + long durationInSeconds = Math.round((Double)((DateTimeResolutionResult)(duration.getValue())).getPastValue()); + + if (isLessThanOrWithIn) { + LocalDateTime startDate; + LocalDateTime endDate; + + if (isAgo) { + startDate = (LocalDateTime)((DateTimeResolutionResult)(pr.getValue())).getPastValue(); + endDate = startDate.plusSeconds(durationInSeconds); + } else { + endDate = (LocalDateTime)((DateTimeResolutionResult)(pr.getValue())).getFutureValue(); + startDate = endDate.minusSeconds(durationInSeconds); + } + + if (startDate != LocalDateTime.MIN) { + String startLuisStr = DateTimeFormatUtil.luisDate(startDate); + String endLuisStr = DateTimeFormatUtil.luisDate(endDate); + String durationTimex = ((DateTimeResolutionResult)(duration.getValue())).getTimex(); + + ret.setTimex(String.format("(%s,%s,%s)", startLuisStr, endLuisStr, durationTimex)); + ret.setFutureValue(new Pair<>(startDate, endDate)); + ret.setPastValue(new Pair<>(startDate, endDate)); + ret.setSuccess(true); + } + } else if (isMoreThan) { + ret.setMod(isAgo ? Constants.BEFORE_MOD : Constants.AFTER_MOD); + + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue(((DateTimeResolutionResult)(pr.getValue())).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)(pr.getValue())).getPastValue()); + ret.setSuccess(true); + } + } + } + } + + return ret; + } + + private DateTimeResolutionResult parseSingleTimePoint(String text, LocalDateTime referenceDate, DateContext dateContext) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ExtractResult er = this.config.getDateExtractor().extract(text, referenceDate).stream().findFirst().orElse(null); + + if (er != null) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekWithWeekDayRangeRegex(), text)).findFirst(); + String weekPrefix = null; + if (match.isPresent()) { + weekPrefix = match.get().getGroup("week").value; + } + + if (!StringUtility.isNullOrEmpty(weekPrefix)) { + er.setText(weekPrefix + " " + er.getText()); + } + + ParseResult pr = this.config.getDateParser().parse(er, referenceDate); + + if (pr != null) { + ret.setTimex("(" + ((DateTimeParseResult)pr).getTimexStr()); + ret.setFutureValue(((DateTimeResolutionResult)pr.getValue()).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)pr.getValue()).getPastValue()); + ret.setSuccess(true); + } + + if (dateContext != null) { + ret = dateContext.processDateEntityResolution(ret); + } + } + + return ret; + } + + private DateTimeResolutionResult parseSimpleCases(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = referenceDate.getYear(); + int month = referenceDate.getMonthValue(); + int beginDay; + int endDay; + boolean noYear = true; + + ConditionalMatch match = RegexExtension.matchExact(this.config.getMonthFrontBetweenRegex(), text, true); + String beginLuisStr; + String endLuisStr; + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getBetweenRegex(), text, true); + } + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getMonthFrontSimpleCasesRegex(), text, true); + } + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getSimpleCasesRegex(), text, true); + } + + if (match.getSuccess()) { + MatchGroup days = match.getMatch().get().getGroup("day"); + beginDay = this.config.getDayOfMonth().get(days.captures[0].value.toLowerCase()); + endDay = this.config.getDayOfMonth().get(days.captures[1].value.toLowerCase()); + + // parse year + year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year != Constants.InvalidYear) { + noYear = false; + } else { + year = referenceDate.getYear(); + } + + String monthStr = match.getMatch().get().getGroup("month").value; + if (!StringUtility.isNullOrEmpty(monthStr)) { + month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + } else { + monthStr = match.getMatch().get().getGroup("relmonth").value.trim().toLowerCase(); + int swiftMonth = this.config.getSwiftDayOrMonth(monthStr); + switch (swiftMonth) { + case 1: + if (month != 12) { + month += 1; + } else { + month = 1; + year += 1; + } + break; + case -1: + if (month != 1) { + month -= 1; + } else { + month = 12; + year -= 1; + } + break; + default: + break; + } + + if (this.config.isFuture(monthStr)) { + noYear = false; + } + } + } else { + return ret; + } + + if (noYear) { + beginLuisStr = DateTimeFormatUtil.luisDate(-1, month, beginDay); + endLuisStr = DateTimeFormatUtil.luisDate(-1, month, endDay); + } else { + beginLuisStr = DateTimeFormatUtil.luisDate(year, month, beginDay); + endLuisStr = DateTimeFormatUtil.luisDate(year, month, endDay); + } + + int futureYear = year; + int pastYear = year; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(year, month, beginDay); + + if (noYear && startDate.isBefore(referenceDate)) { + futureYear++; + } + + if (noYear && (startDate.isAfter(referenceDate) || startDate.isEqual(referenceDate))) { + pastYear--; + } + + HashMap futurePastBeginDates = DateContext.generateDates(noYear, referenceDate, year, month, beginDay); + HashMap futurePastEndDates = DateContext.generateDates(noYear, referenceDate, year, month, endDay); + + ret.setTimex(String.format("(%s,%s,P%sD)", beginLuisStr, endLuisStr, (endDay - beginDay))); + ret.setFutureValue(new Pair<>(futurePastBeginDates.get(Constants.FutureDate), + futurePastEndDates.get(Constants.FutureDate))); + ret.setPastValue(new Pair<>(futurePastBeginDates.get(Constants.PastDate), + futurePastEndDates.get(Constants.PastDate))); + ret.setSuccess(true); + + return ret; + } + + private boolean isPresent(int swift) { + return swift == 0; + } + + private DateTimeResolutionResult parseOneWordPeriod(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = referenceDate.getYear(); + int month = referenceDate.getMonthValue(); + int futureYear = year; + int pastYear = year; + boolean earlyPrefix = false; + boolean latePrefix = false; + boolean midPrefix = false; + boolean isRef = false; + + boolean earlierPrefix = false; + boolean laterPrefix = false; + + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getOneWordPeriodRegex(), trimmedText, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getLaterEarlyPeriodRegex(), trimmedText, true); + } + + // For cases "that week|month|year" + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getReferenceDatePeriodRegex(), trimmedText, true); + isRef = true; + ret.setMod(Constants.REF_UNDEF_MOD); + } + + if (match.getSuccess()) { + if (!match.getMatch().get().getGroup("EarlyPrefix").value.equals("")) { + earlyPrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.EARLY_MOD); + } else if (!match.getMatch().get().getGroup("LatePrefix").value.equals("")) { + latePrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.LATE_MOD); + } else if (!match.getMatch().get().getGroup("MidPrefix").value.equals("")) { + midPrefix = true; + trimmedText = match.getMatch().get().getGroup(Constants.SuffixGroupName).value; + ret.setMod(Constants.MID_MOD); + } + + int swift = 0; + String monthStr = match.getMatch().get().getGroup("month").value; + if (!StringUtility.isNullOrEmpty(monthStr)) { + swift = this.config.getSwiftYear(trimmedText); + } else { + swift = this.config.getSwiftDayOrMonth(trimmedText); + } + + // Handle the abbreviation of DatePeriod, e.g., 'eoy(end of year)', the behavior of 'eoy' should be the same as 'end of year' + Optional unspecificEndOfRangeMatch = Arrays.stream(RegExpUtility.getMatches(config.getUnspecificEndOfRangeRegex(), match.getMatch().get().value)).findFirst(); + if (unspecificEndOfRangeMatch.isPresent()) { + latePrefix = true; + trimmedText = match.getMatch().get().value; + ret.setMod(Constants.LATE_MOD); + } + + if (!match.getMatch().get().getGroup("RelEarly").value.equals("")) { + earlierPrefix = true; + if (isPresent(swift)) { + ret.setMod(null); + } + } else if (!match.getMatch().get().getGroup("RelLate").value.equals("")) { + laterPrefix = true; + if (isPresent(swift)) { + ret.setMod(null); + } + } + + if (this.config.isYearToDate(trimmedText)) { + ret.setTimex(String.format("%04d", referenceDate.getYear())); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), 1, 1), referenceDate)); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), 1, 1), referenceDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isMonthToDate(trimmedText)) { + ret.setTimex(String.format("%04d-%02d", referenceDate.getYear(), referenceDate.getMonthValue())); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1), + referenceDate)); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(referenceDate.getYear(), referenceDate.getMonthValue(), 1), + referenceDate)); + + ret.setSuccess(true); + return ret; + } + + if (!StringUtility.isNullOrEmpty(monthStr)) { + swift = this.config.getSwiftYear(trimmedText); + + month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + + if (swift >= -1) { + ret.setTimex(String.format("%04d-%02d", referenceDate.getYear() + swift, month)); + year = year + swift; + futureYear = pastYear = year; + } else { + ret.setTimex(String.format("XXXX-%02d", month)); + if (month < referenceDate.getMonthValue()) { + futureYear++; + } + + if (month >= referenceDate.getMonthValue()) { + pastYear--; + } + } + } else { + swift = this.config.getSwiftDayOrMonth(trimmedText); + + if (this.config.isWeekOnly(trimmedText)) { + LocalDateTime thursday = DateUtil.thisDate(referenceDate, DayOfWeek.THURSDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + ret.setTimex(isRef ? TimexUtility.generateWeekTimex() : TimexUtility.generateWeekTimex(thursday)); + + LocalDateTime beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.MONDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + LocalDateTime endValue = DateUtil.thisDate(referenceDate, DayOfWeek.SUNDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + if (earlyPrefix) { + endValue = DateUtil.thisDate(referenceDate, DayOfWeek.WEDNESDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (midPrefix) { + beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.TUESDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endValue = DateUtil.thisDate(referenceDate, DayOfWeek.FRIDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (latePrefix) { + beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.THURSDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + } + + if (earlierPrefix && swift == 0) { + if (endDate.isAfter(referenceDate)) { + endDate = referenceDate; + } + } else if (laterPrefix && swift == 0) { + if (beginDate.isBefore(referenceDate)) { + beginDate = referenceDate; + } + } + + if (latePrefix && swift != 0) { + ret.setMod(Constants.LATE_MOD); + } + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isWeekend(trimmedText)) { + LocalDateTime beginDate = DateUtil.thisDate(referenceDate, DayOfWeek.SATURDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + LocalDateTime endValue = DateUtil.thisDate(referenceDate, DayOfWeek.SUNDAY.getValue()).plusDays(Constants.WeekDayCount * swift); + + ret.setTimex(isRef ? TimexUtility.generateWeekendTimex() : TimexUtility.generateWeekendTimex(beginDate)); + + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + + if (this.config.isMonthOnly(trimmedText)) { + LocalDateTime date = referenceDate.plusMonths(swift); + month = date.getMonthValue(); + year = date.getYear(); + + ret.setTimex(isRef ? TimexUtility.generateMonthTimex() : TimexUtility.generateMonthTimex(date)); + + futureYear = pastYear = year; + } else if (this.config.isYearOnly(trimmedText)) { + LocalDateTime date = referenceDate.plusYears(swift); + year = date.getYear(); + + if (!StringUtility.isNullOrEmpty(match.getMatch().get().getGroup("special").value)) { + String specialYearPrefixes = this.config.getSpecialYearPrefixesMap().get(match.getMatch().get().getGroup("special").value.toLowerCase()); + swift = this.config.getSwiftYear(trimmedText); + year = swift < -1 ? Constants.InvalidYear : year; + ret.setTimex(TimexUtility.generateYearTimex(year, specialYearPrefixes)); + ret.setSuccess(true); + return ret; + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, 1, 1); + + LocalDateTime endValue = DateUtil.safeCreateFromMinValue(year, 12, 31); + LocalDateTime endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + + if (earlyPrefix) { + endValue = DateUtil.safeCreateFromMinValue(year, 6, 30); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (midPrefix) { + beginDate = DateUtil.safeCreateFromMinValue(year, 4, 1); + endValue = DateUtil.safeCreateFromMinValue(year, 9, 30); + endDate = inclusiveEndPeriod ? endValue : endValue.plusDays(1); + } else if (latePrefix) { + beginDate = DateUtil.safeCreateFromMinValue(year, 7, 1); + } + + if (earlierPrefix && swift == 0) { + if (endDate.isAfter(referenceDate)) { + endDate = referenceDate; + } + } else if (laterPrefix && swift == 0) { + if (beginDate.isBefore(referenceDate)) { + beginDate = referenceDate; + } + } + + year = isRef ? Constants.InvalidYear : year; + ret.setTimex(TimexUtility.generateYearTimex(year)); + + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + + ret.setSuccess(true); + return ret; + } + } + } else { + return ret; + } + + // only "month" will come to here + LocalDateTime futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 1); + LocalDateTime futureEnd = inclusiveEndPeriod ? futureStart.plusMonths(1).minusDays(1) : futureStart.plusMonths(1); + + + LocalDateTime pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 1); + LocalDateTime pastEnd = inclusiveEndPeriod ? pastStart.plusMonths(1).minusDays(1) : pastStart.plusMonths(1); + + if (earlyPrefix) { + futureEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(futureYear, month, 15) : + DateUtil.safeCreateFromMinValue(futureYear, month, 15).plusDays(1); + pastEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(pastYear, month, 15) : + DateUtil.safeCreateFromMinValue(pastYear, month, 15).plusDays(1); + } else if (midPrefix) { + futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 10); + pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 10); + futureEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(futureYear, month, 20) : + DateUtil.safeCreateFromMinValue(futureYear, month, 20).plusDays(1); + pastEnd = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(pastYear, month, 20) : + DateUtil.safeCreateFromMinValue(pastYear, month, 20).plusDays(1); + } else if (latePrefix) { + futureStart = DateUtil.safeCreateFromMinValue(futureYear, month, 16); + pastStart = DateUtil.safeCreateFromMinValue(pastYear, month, 16); + } + + if (earlierPrefix && futureEnd.isEqual(pastEnd)) { + if (futureEnd.isAfter(referenceDate)) { + futureEnd = pastEnd = referenceDate; + } + } else if (laterPrefix && futureStart.isEqual(pastStart)) { + if (futureStart.isBefore(referenceDate)) { + futureStart = pastStart = referenceDate; + } + } + + ret.setFutureValue(new Pair<>(futureStart, futureEnd)); + + ret.setPastValue(new Pair<>(pastStart, pastEnd)); + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseMonthWithYear(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + ConditionalMatch match = RegexExtension.matchExact(this.config.getMonthWithYear(), text, true); + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getMonthNumWithYear(), text, true); + } + + if (match.getSuccess()) { + String monthStr = match.getMatch().get().getGroup("month").value.toLowerCase(); + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + + int month = this.config.getMonthOfYear().get(monthStr.toLowerCase()); + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + LocalDateTime startValue = DateUtil.safeCreateFromMinValue(year, month, 1); + LocalDateTime endValue = inclusiveEndPeriod ? + DateUtil.safeCreateFromMinValue(year, month, 1).plusMonths(1).minusDays(1) : + DateUtil.safeCreateFromMinValue(year, month, 1).plusMonths(1); + + ret.setFutureValue(new Pair<>(startValue, endValue)); + ret.setPastValue(new Pair<>(startValue, endValue)); + + ret.setTimex(String.format("%04d-%02d", year, month)); + + ret.setSuccess(true); + } + + return ret; + } + + private DateTimeResolutionResult parseYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int year = Constants.InvalidYear; + + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getYearPeriodRegex(), text)).findFirst(); + Optional matchMonth = Arrays.stream(RegExpUtility.getMatches(this.config.getMonthWithYear(), text)).findFirst(); + ; + + if (match.isPresent() && !matchMonth.isPresent()) { + int beginYear = Constants.InvalidYear; + int endYear = Constants.InvalidYear; + + Match[] matches = RegExpUtility.getMatches(this.config.getYearRegex(), text); + if (matches.length == 2) { + // (from|during|in|between)? 2012 (till|to|until|through|-) 2015 + if (!matches[0].value.equals("")) { + beginYear = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matches[0]); + if (!(beginYear >= Constants.MinYearNum && beginYear <= Constants.MaxYearNum)) { + beginYear = Constants.InvalidYear; + } + } + + if (!matches[1].value.equals("")) { + endYear = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(matches[1]); + if (!(endYear >= Constants.MinYearNum && endYear <= Constants.MaxYearNum)) { + endYear = Constants.InvalidYear; + } + } + } + + if (beginYear != Constants.InvalidYear && endYear != Constants.InvalidYear) { + LocalDateTime beginDay = DateUtil.safeCreateFromMinValue(beginYear, 1, 1); + + LocalDateTime endDayValue = DateUtil.safeCreateFromMinValue(endYear, 1, 1); + LocalDateTime endDay = inclusiveEndPeriod ? endDayValue.minusDays(1) : endDayValue; + + ret.setTimex(String.format("(%s,%s,P%sY)", DateTimeFormatUtil.luisDate(beginDay), DateTimeFormatUtil.luisDate(endDay), (endYear - beginYear))); + ret.setFutureValue(new Pair<>(beginDay, endDay)); + ret.setPastValue(new Pair<>(beginDay, endDay)); + ret.setSuccess(true); + + return ret; + } + } else { + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getYearRegex(), text, true); + if (exactMatch.getSuccess()) { + year = this.config.getDateExtractor().getYearFromText(exactMatch.getMatch().get()); + if (!(year >= Constants.MinYearNum && year <= Constants.MaxYearNum)) { + year = Constants.InvalidYear; + } + } else { + exactMatch = RegexExtension.matchExact(this.config.getYearPlusNumberRegex(), text, true); + if (exactMatch.getSuccess()) { + year = this.config.getDateExtractor().getYearFromText(exactMatch.getMatch().get()); + if (!StringUtility.isNullOrEmpty(exactMatch.getMatch().get().getGroup("special").value)) { + String specialYearPrefixes = this.config.getSpecialYearPrefixesMap().get(exactMatch.getMatch().get().getGroup("special").value.toLowerCase()); + ret.setTimex(TimexUtility.generateYearTimex(year, specialYearPrefixes)); + ret.setSuccess(true); + return ret; + } + } + } + + if (year != Constants.InvalidYear) { + LocalDateTime beginDay = DateUtil.safeCreateFromMinValue(year, 1, 1); + + LocalDateTime endDayValue = DateUtil.safeCreateFromMinValue(year + 1, 1, 1); + LocalDateTime endDay = inclusiveEndPeriod ? endDayValue.minusDays(1) : endDayValue; + + ret.setTimex(TimexUtility.generateYearTimex(year)); + ret.setFutureValue(new Pair<>(beginDay, endDay)); + ret.setPastValue(new Pair<>(beginDay, endDay)); + ret.setSuccess(true); + + return ret; + } + } + + return ret; + } + + // parse entities that made up by two time points + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + List er = this.config.getDateExtractor().extract(text, referenceDate); + DateTimeParseResult pr1 = null; + DateTimeParseResult pr2 = null; + if (er.size() < 2) { + er = this.config.getDateExtractor().extract(this.config.getTokenBeforeDate() + text, referenceDate); + if (er.size() >= 2) { + er.get(0).setStart(er.get(0).getStart() - this.config.getTokenBeforeDate().length()); + er.get(1).setStart(er.get(1).getStart() - this.config.getTokenBeforeDate().length()); + er.set(0, er.get(0)); + er.set(1, er.get(1)); + } else { + DateTimeParseResult nowPr = parseNowAsDate(text, referenceDate); + if (nowPr == null || er.size() < 1) { + return ret; + } + + DateTimeParseResult datePr = this.config.getDateParser().parse(er.get(0), referenceDate); + pr1 = datePr.getStart() < nowPr.getStart() ? datePr : nowPr; + pr2 = datePr.getStart() < nowPr.getStart() ? nowPr : datePr; + } + + } + if (er.size() >= 2) { + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getWeekWithWeekDayRangeRegex(), text)).findFirst(); + String weekPrefix = null; + if (match.isPresent()) { + weekPrefix = match.get().getGroup("week").value; + } + + if (!StringUtility.isNullOrEmpty(weekPrefix)) { + er.get(0).setText(String.format("%s %s", weekPrefix, er.get(0).getText())); + er.get(1).setText(String.format("%s %s", weekPrefix, er.get(1).getText())); + er.set(0, er.get(0)); + er.set(1, er.get(1)); + } + + DateContext dateContext = getYearContext(er.get(0).getText(), er.get(1).getText(), text); + + pr1 = this.config.getDateParser().parse(er.get(0), referenceDate); + pr2 = this.config.getDateParser().parse(er.get(1), referenceDate); + + if (pr1.getValue() == null || pr2.getValue() == null) { + return ret; + } + + pr1 = dateContext.processDateEntityParsingResult(pr1); + pr2 = dateContext.processDateEntityParsingResult(pr2); + + // When the case has no specified year, we should sync the future/past year due to invalid date Feb 29th. + if (dateContext.isEmpty() && (DateContext.isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue()) || + DateContext.isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue()))) { + + HashMap parseResultHashMap = dateContext.syncYear(pr1, pr2); + pr1 = parseResultHashMap.get(Constants.ParseResult1); + pr2 = parseResultHashMap.get(Constants.ParseResult2); + } + } + + List subDateTimeEntities = new ArrayList(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + ret.setSubDateTimeEntities(subDateTimeEntities); + + LocalDateTime futureBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime futureEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + LocalDateTime pastBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue(); + LocalDateTime pastEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue(); + + if (futureBegin.isAfter(futureEnd)) { + futureBegin = pastBegin; + } + + if (pastEnd.isBefore(pastBegin)) { + pastEnd = futureEnd; + } + + ret.setTimex(TimexUtility.generateDatePeriodTimexStr(futureBegin, futureEnd, DatePeriodTimexType.ByDay, pr1.getTimexStr(), pr2.getTimexStr())); + + if (pr1.getTimexStr().startsWith(Constants.TimexFuzzyYear) && futureBegin.compareTo(DateUtil.safeCreateFromMinValue(futureBegin.getYear(), 2, 28)) <= 0 && + futureEnd.compareTo(DateUtil.safeCreateFromMinValue(futureBegin.getYear(), 3, 1)) >= 0) { + + // Handle cases like "Feb 29th to March 1st". + // There may be different timexes for FutureValue and PastValue due to the different validity of Feb 29th. + ret.setComment(Constants.Comment_DoubleTimex); + String pastTimex = TimexUtility.generateDatePeriodTimexStr(pastBegin, pastEnd, DatePeriodTimexType.ByDay, pr1.getTimexStr(), pr2.getTimexStr()); + ret.setTimex(TimexUtility.mergeTimexAlternatives(ret.getTimex(), pastTimex)); + } + ret.setFutureValue(new Pair<>(futureBegin, futureEnd)); + ret.setPastValue(new Pair<>(pastBegin, pastEnd)); + ret.setSuccess(true); + + return ret; + } + + // parse entities that made up by two time points with now + private DateTimeParseResult parseNowAsDate(String text, LocalDateTime referenceDate) { + DateTimeParseResult nowPr = null; + LocalDateTime value = referenceDate.toLocalDate().atStartOfDay(); + Match[] matches = RegExpUtility.getMatches(this.config.getNowRegex(), text); + for (Match match : matches) { + DateTimeResolutionResult retNow = new DateTimeResolutionResult(); + retNow.setTimex(DateTimeFormatUtil.luisDate(value)); + retNow.setFutureValue(value); + retNow.setPastValue(value); + + nowPr = new DateTimeParseResult( + match.index, + match.length, + match.value, + Constants.SYS_DATETIME_DATE, + null, + retNow, + "", + value == null ? "" : ((DateTimeResolutionResult)retNow).getTimex()); + } + + return nowPr; + } + + private DateTimeResolutionResult parseDuration(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + LocalDateTime beginDate = referenceDate; + LocalDateTime endDate = referenceDate; + String timex = ""; + boolean restNowSunday = false; + List dateList = null; + + List durationErs = config.getDurationExtractor().extract(text, referenceDate); + if (durationErs.size() == 1) { + ParseResult durationPr = config.getDurationParser().parse(durationErs.get(0)); + String beforeStr = text.substring(0, (durationPr.getStart() != null) ? durationPr.getStart() : 0).trim().toLowerCase(); + String afterStr = text.substring( + ((durationPr.getStart() != null) ? durationPr.getStart() : 0) + ((durationPr.getLength() != null) ? durationPr.getLength() : 0)) + .trim().toLowerCase(); + + List numbersInSuffix = config.getCardinalExtractor().extract(beforeStr); + List numbersInDuration = config.getCardinalExtractor().extract(durationErs.get(0).getText()); + + // Handle cases like "2 upcoming days", "5 previous years" + if (!numbersInSuffix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult numberEr = numbersInSuffix.stream().findFirst().get(); + String numberText = numberEr.getText(); + String durationText = durationErs.get(0).getText(); + String combinedText = String.format("%s %s", numberText, durationText); + List combinedDurationEr = config.getDurationExtractor().extract(combinedText, referenceDate); + + if (!combinedDurationEr.isEmpty()) { + durationPr = config.getDurationParser().parse(combinedDurationEr.stream().findFirst().get()); + int startIndex = numberEr.getStart() + numberEr.getLength(); + beforeStr = beforeStr.substring(startIndex).trim(); + } + } + + GetModAndDateResult getModAndDateResult = new GetModAndDateResult(); + + if (durationPr.getValue() != null) { + DateTimeResolutionResult durationResult = (DateTimeResolutionResult)durationPr.getValue(); + + if (StringUtility.isNullOrEmpty(durationResult.getTimex())) { + return ret; + } + + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), beforeStr)).findFirst(); + Optional suffixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), afterStr)).findFirst(); + if (prefixMatch.isPresent() || suffixMatch.isPresent()) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), false); + beginDate = getModAndDateResult.beginDate; + } + + // Handle the "within two weeks" case which means from today to the end of next two weeks + // Cases like "within 3 days before/after today" is not handled here (4th condition) + boolean isMatch = false; + if (RegexExtension.isExactMatch(config.getWithinNextPrefixRegex(), beforeStr, true)) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + + // In GetModAndDate, this "future" resolution will add one day to beginDate/endDate, but for the "within" case it should start from the current day. + beginDate = beginDate.minusDays(1); + endDate = endDate.minusDays(1); + isMatch = true; + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), beforeStr, true)) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + isMatch = true; + } + + Optional futureSuffixMatch = Arrays.stream(RegExpUtility.getMatches(config.getFutureSuffixRegex(), afterStr)).findFirst(); + if (futureSuffixMatch.isPresent()) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + } + + // Handle the "in two weeks" case which means the second week + if (RegexExtension.isExactMatch(config.getInConnectorRegex(), beforeStr, true) && + !DurationParsingUtil.isMultipleDuration(durationResult.getTimex()) && !isMatch) { + getModAndDateResult = getModAndDate(beginDate, endDate, referenceDate, durationResult.getTimex(), true); + beginDate = getModAndDateResult.beginDate; + endDate = getModAndDateResult.endDate; + + // Change the duration value and the beginDate + String unit = durationResult.getTimex().substring(durationResult.getTimex().length() - 1); + + durationResult.setTimex(String.format("P1%s", unit)); + beginDate = DurationParsingUtil.shiftDateTime(durationResult.getTimex(), endDate, false); + } + + if (!StringUtility.isNullOrEmpty(getModAndDateResult.mod)) { + ((DateTimeResolutionResult)durationPr.getValue()).setMod(getModAndDateResult.mod); + } + + timex = durationResult.getTimex(); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(durationPr); + ret.setSubDateTimeEntities(subDateTimeEntities); + + if (getModAndDateResult.dateList != null) { + ret.setList(getModAndDateResult.dateList.stream().map(e -> (Object)e).collect(Collectors.toList())); + } + } + } + + // Parse "rest of" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getRestOfDateRegex(), text)).findFirst(); + if (match.isPresent()) { + String durationStr = match.get().getGroup("duration").value; + String durationUnit = this.config.getUnitMap().get(durationStr); + switch (durationUnit) { + case "W": + int diff = Constants.WeekDayCount - ((beginDate.getDayOfWeek().getValue()) == 0 ? Constants.WeekDayCount : beginDate.getDayOfWeek().getValue()); + endDate = beginDate.plusDays(diff); + timex = String.format("P%s%s", diff, Constants.TimexDay); + if (diff == 0) { + restNowSunday = true; + } + break; + + case "MON": + endDate = DateUtil.safeCreateFromMinValue(beginDate.getYear(), beginDate.getMonthValue(), 1); + endDate = endDate.plusMonths(1).minusDays(1); + diff = (int)ChronoUnit.DAYS.between(beginDate, endDate) + 1; + timex = String.format("P%s%s", diff, Constants.TimexDay); + break; + + case "Y": + endDate = DateUtil.safeCreateFromMinValue(beginDate.getYear(), 12, 1); + endDate = endDate.plusMonths(1).minusDays(1); + diff = (int)ChronoUnit.DAYS.between(beginDate, endDate) + 1; + timex = String.format("P%s%s", diff, Constants.TimexDay); + break; + default: + break; + } + } + + if (!beginDate.equals(endDate) || restNowSunday) { + endDate = inclusiveEndPeriod ? endDate.minusDays(1) : endDate; + + ret.setTimex(String.format("(%s,%s,%s)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate), timex)); + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private GetModAndDateResult getModAndDate(LocalDateTime beginDate, LocalDateTime endDate, LocalDateTime referenceDate, String timex, boolean future) { + LocalDateTime beginDateResult = beginDate; + LocalDateTime endDateResult = endDate; + boolean isBusinessDay = timex.endsWith(Constants.TimexBusinessDay); + int businessDayCount = 0; + + if (isBusinessDay) { + businessDayCount = Integer.parseInt(timex.substring(1, timex.length() - 2)); + } + + if (future) { + String mod = Constants.AFTER_MOD; + + // For future the beginDate should add 1 first + if (isBusinessDay) { + beginDateResult = DurationParsingUtil.getNextBusinessDay(referenceDate); + NthBusinessDayResult nthBusinessDayResult = DurationParsingUtil.getNthBusinessDay(beginDateResult, businessDayCount - 1, true); + endDateResult = nthBusinessDayResult.result.plusDays(1); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, nthBusinessDayResult.dateList); + } else { + beginDateResult = referenceDate.plusDays(1); + endDateResult = DurationParsingUtil.shiftDateTime(timex, beginDateResult, true); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, null); + } + + } else { + String mod = Constants.BEFORE_MOD; + + if (isBusinessDay) { + endDateResult = DurationParsingUtil.getNextBusinessDay(endDateResult, false); + NthBusinessDayResult nthBusinessDayResult = DurationParsingUtil.getNthBusinessDay(endDateResult, businessDayCount - 1, false); + endDateResult = endDateResult.plusDays(1); + beginDateResult = nthBusinessDayResult.result; + return new GetModAndDateResult(beginDateResult, endDateResult, mod, nthBusinessDayResult.dateList); + } else { + beginDateResult = DurationParsingUtil.shiftDateTime(timex, endDateResult, false); + return new GetModAndDateResult(beginDateResult, endDateResult, mod, null); + } + } + } + + // To be consistency, we follow the definition of "week of year": + // "first week of the month" - it has the month's first Thursday in it + // "last week of the month" - it has the month's last Thursday in it + private DateTimeResolutionResult parseWeekOfMonth(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWeekOfMonthRegex(), trimmedText, true); + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value; + String monthStr = match.getMatch().get().getGroup("month").value; + boolean noYear = false; + int year; + + int month; + if (StringUtility.isNullOrEmpty(monthStr)) { + int swift = this.config.getSwiftDayOrMonth(trimmedText); + + month = referenceDate.plusMonths(swift).getMonthValue(); + year = referenceDate.plusMonths(swift).getYear(); + } else { + month = this.config.getMonthOfYear().get(monthStr); + year = config.getDateExtractor().getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + year = referenceDate.getYear(); + noYear = true; + } + } + + ret = getWeekOfMonth(cardinalStr, month, year, referenceDate, noYear); + + return ret; + } + + private DateTimeResolutionResult parseWeekOfYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWeekOfYearRegex(), trimmedText, true); + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value; + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + + int year = this.config.getDateExtractor().getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + LocalDateTime targetWeekMonday; + if (this.config.isLastCardinal(cardinalStr)) { + targetWeekMonday = DateUtil.thisDate(getLastThursday(year), DayOfWeek.MONDAY.getValue()); + + ret.setTimex(TimexUtility.generateWeekTimex(targetWeekMonday)); + } else { + int weekNum = this.config.getCardinalMap().get(cardinalStr); + targetWeekMonday = DateUtil.thisDate(getFirstThursday(year), DayOfWeek.MONDAY.getValue()) + .plusDays(Constants.WeekDayCount * (weekNum - 1)); + + ret.setTimex(TimexUtility.generateWeekOfYearTimex(year, weekNum)); + } + + ret.setFutureValue(inclusiveEndPeriod ? + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount))); + + ret.setPastValue(inclusiveEndPeriod ? + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(targetWeekMonday, targetWeekMonday.plusDays(Constants.WeekDayCount))); + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseHalfYear(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getAllHalfYearRegex(), text, true); + + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value.toLowerCase(); + String orderStr = match.getMatch().get().getGroup("order").value.toLowerCase(); + String numberStr = match.getMatch().get().getGroup("number").value; + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(orderStr); + if (swift < -1) { + return ret; + } + year = referenceDate.getYear() + swift; + } + + int halfNum; + if (!StringUtility.isNullOrEmpty(numberStr)) { + halfNum = Integer.parseInt(numberStr); + } else { + halfNum = this.config.getCardinalMap().get(cardinalStr); + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, (halfNum - 1) * Constants.SemesterMonthCount + 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(year, halfNum * Constants.SemesterMonthCount, 1).plusMonths(1); + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setTimex(String.format("(%s,%s,P6M)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate))); + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseQuarter(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getQuarterRegex(), text, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchExact(this.config.getQuarterRegexYearFront(), text, true); + } + + if (!match.getSuccess()) { + return ret; + } + + String cardinalStr = match.getMatch().get().getGroup("cardinal").value.toLowerCase(); + String orderQuarterStr = match.getMatch().get().getGroup("orderQuarter").value.toLowerCase(); + String orderStr = StringUtility.isNullOrEmpty(orderQuarterStr) ? match.getMatch().get().getGroup("order").value.toLowerCase() : null; + String numberStr = match.getMatch().get().getGroup("number").value; + + boolean noSpecificYear = false; + int year = this.config.getDateExtractor().getYearFromText(match.getMatch().get()); + + if (year == Constants.InvalidYear) { + int swift = StringUtility.isNullOrEmpty(orderQuarterStr) ? this.config.getSwiftYear(orderStr) : 0; + if (swift < -1) { + swift = 0; + noSpecificYear = true; + } + year = referenceDate.getYear() + swift; + } + + int quarterNum; + if (!StringUtility.isNullOrEmpty(numberStr)) { + quarterNum = Integer.parseInt(numberStr); + } else if (!StringUtility.isNullOrEmpty(orderQuarterStr)) { + int month = referenceDate.getMonthValue(); + quarterNum = (int)Math.ceil((double)month / Constants.TrimesterMonthCount); + int swift = this.config.getSwiftYear(orderQuarterStr); + quarterNum += swift; + if (quarterNum <= 0) { + quarterNum += Constants.QuarterCount; + year -= 1; + } else if (quarterNum > Constants.QuarterCount) { + quarterNum -= Constants.QuarterCount; + year += 1; + } + } else { + quarterNum = this.config.getCardinalMap().get(cardinalStr); + } + + LocalDateTime beginDate = DateUtil.safeCreateFromMinValue(year, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime endDate = DateUtil.safeCreateFromMinValue(year, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + + if (noSpecificYear) { + if (endDate.compareTo(referenceDate) < 0) { + ret.setPastValue(new Pair<>(beginDate, endDate)); + + LocalDateTime futureBeginDate = DateUtil.safeCreateFromMinValue(year + 1, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime futureEndDate = DateUtil.safeCreateFromMinValue(year + 1, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + ret.setFutureValue(new Pair<>(futureBeginDate, futureEndDate)); + } else if (endDate.compareTo(referenceDate) > 0) { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + + LocalDateTime pastBeginDate = DateUtil.safeCreateFromMinValue(year - 1, (quarterNum - 1) * Constants.TrimesterMonthCount + 1, 1); + LocalDateTime pastEndDate = DateUtil.safeCreateFromMinValue(year - 1, quarterNum * Constants.TrimesterMonthCount, 1).plusMonths(1); + ret.setPastValue(new Pair<>(pastBeginDate, pastEndDate)); + } else { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + } + + ret.setTimex(String.format("(%s,%s,P3M)", DateTimeFormatUtil.luisDate(-1, beginDate.getMonthValue(), 1), DateTimeFormatUtil.luisDate(-1, endDate.getMonthValue(), 1))); + } else { + ret.setFutureValue(new Pair<>(beginDate, endDate)); + ret.setPastValue(new Pair<>(beginDate, endDate)); + ret.setTimex(String.format("(%s,%s,P3M)", DateTimeFormatUtil.luisDate(beginDate), DateTimeFormatUtil.luisDate(endDate))); + } + + ret.setSuccess(true); + + return ret; + } + + private DateTimeResolutionResult parseSeason(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getSeasonRegex(), text, true); + if (match.getSuccess()) { + String seasonStr = this.config.getSeasonMap().get(match.getMatch().get().getGroup("seas").value.toLowerCase()); + + if (!match.getMatch().get().getGroup("EarlyPrefix").value.equals("")) { + ret.setMod(Constants.EARLY_MOD); + } else if (!match.getMatch().get().getGroup("MidPrefix").value.equals("")) { + ret.setMod(Constants.MID_MOD); + } else if (!match.getMatch().get().getGroup("LatePrefix").value.equals("")) { + ret.setMod(Constants.LATE_MOD); + } + + int year = ((BaseDateExtractor)this.config.getDateExtractor()).getYearFromText(match.getMatch().get()); + if (year == Constants.InvalidYear) { + int swift = this.config.getSwiftYear(text); + if (swift < -1) { + ret.setTimex(seasonStr); + ret.setSuccess(true); + return ret; + } + year = referenceDate.getYear() + swift; + } + + String yearStr = String.format("%04d", year); + ret.setTimex(String.format("%s-%s", yearStr, seasonStr)); + + ret.setSuccess(true); + return ret; + } + return ret; + } + + private DateTimeResolutionResult parseWeekOfDate(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getWeekOfRegex(), text)).findFirst(); + List dateErs = config.getDateExtractor().extract(text, referenceDate); + + if (dateErs.isEmpty()) { + // For cases like "week of the 18th" + dateErs.addAll( + config.getCardinalExtractor().extract(text).stream() + .peek(o -> o.setType(Constants.SYS_DATETIME_DATE)) + .filter(o -> dateErs.stream().noneMatch(er -> er.isOverlap(o))) + .collect(Collectors.toList())); + } + + if (match.isPresent() && dateErs.size() == 1) { + DateTimeResolutionResult pr = (DateTimeResolutionResult)config.getDateParser().parse(dateErs.get(0), referenceDate).getValue(); + if (config.getOptions().match(DateTimeOptions.CalendarMode)) { + LocalDateTime monday = DateUtil.thisDate((LocalDateTime)pr.getFutureValue(), DayOfWeek.MONDAY.getValue()); + ret.setTimex(DateTimeFormatUtil.toIsoWeekTimex(monday)); + } else { + ret.setTimex(pr.getTimex()); + } + ret.setComment(Constants.Comment_WeekOf); + ret.setFutureValue(getWeekRangeFromDate((LocalDateTime)pr.getFutureValue())); + ret.setPastValue(getWeekRangeFromDate((LocalDateTime)pr.getPastValue())); + ret.setSuccess(true); + } + return ret; + } + + private DateTimeResolutionResult parseMonthOfDate(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getMonthOfRegex(), text)).findFirst(); + List ex = config.getDateExtractor().extract(text, referenceDate); + + if (match.isPresent() && ex.size() == 1) { + DateTimeResolutionResult pr = (DateTimeResolutionResult)config.getDateParser().parse(ex.get(0), referenceDate).getValue(); + ret.setTimex(pr.getTimex()); + ret.setComment(Constants.Comment_MonthOf); + ret.setFutureValue(getMonthRangeFromDate((LocalDateTime)pr.getFutureValue())); + ret.setPastValue(getMonthRangeFromDate((LocalDateTime)pr.getPastValue())); + ret.setSuccess(true); + } + return ret; + } + + private Pair getWeekRangeFromDate(LocalDateTime date) { + LocalDateTime startDate = DateUtil.thisDate(date, DayOfWeek.MONDAY.getValue()); + LocalDateTime endDate = inclusiveEndPeriod ? startDate.plusDays(Constants.WeekDayCount - 1) : startDate.plusDays(Constants.WeekDayCount); + return new Pair<>(startDate, endDate); + } + + private Pair getMonthRangeFromDate(LocalDateTime date) { + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue(), 1); + LocalDateTime endDate; + + if (date.getMonthValue() < 12) { + endDate = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue() + 1, 1); + } else { + endDate = DateUtil.safeCreateFromMinValue(date.getYear() + 1, 1, 1); + } + + endDate = inclusiveEndPeriod ? endDate.minusDays(1) : endDate; + return new Pair<>(startDate, endDate); + } + + private DateTimeResolutionResult parseWhichWeek(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getWhichWeekRegex(), text, true); + if (match.getSuccess()) { + int num = Integer.parseInt(match.getMatch().get().getGroup("number").value); + int year = referenceDate.getYear(); + ret.setTimex(String.format("%04d-W%02d", year, num)); + LocalDateTime firstDay = DateUtil.safeCreateFromMinValue(year, 1, 1); + LocalDateTime firstThursday = DateUtil.thisDate(firstDay, DayOfWeek.of(4).getValue()); + + if (DateUtil.weekOfYear(firstThursday) == 1) { + num -= 1; + } + + LocalDateTime value = firstThursday.plusDays(Constants.WeekDayCount * num - 3); + ret.setFutureValue(new Pair<>(value, value.plusDays(7))); + ret.setPastValue(new Pair<>(value, value.plusDays(7))); + ret.setSuccess(true); + } + return ret; + } + + private DateTimeResolutionResult getWeekOfMonth(String cardinalStr, int month, int year, LocalDateTime referenceDate, boolean noYear) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + LocalDateTime targetMonday = getMondayOfTargetWeek(cardinalStr, month, year); + + LocalDateTime futureDate = targetMonday; + LocalDateTime pastDate = targetMonday; + + if (noYear && futureDate.isBefore(referenceDate)) { + futureDate = getMondayOfTargetWeek(cardinalStr, month, year + 1); + } + + if (noYear && pastDate.compareTo(referenceDate) >= 0) { + pastDate = getMondayOfTargetWeek(cardinalStr, month, year - 1); + } + + if (noYear) { + year = Constants.InvalidYear; + } + + // Note that if the cardinalStr equals to "last", the weekNumber would be fixed at "5" + // This may lead to some inconsistency between Timex and Resolution + // the StartDate and EndDate of the resolution would always be correct (following ISO week definition) + // But week number for "last week" might be inconsistency with the resolution as we only have one Timex, + // but we may have past and future resolution which may have different week number + int weekNum = getWeekNumberForMonth(cardinalStr); + + String timex = TimexUtility.generateWeekOfMonthTimex(year, month, weekNum); + ret.setTimex(timex); + + ret.setFutureValue(inclusiveEndPeriod ? + new Pair<>(futureDate, futureDate.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(futureDate, futureDate.plusDays(Constants.WeekDayCount))); + ret.setPastValue(inclusiveEndPeriod ? + new Pair<>(pastDate, pastDate.plusDays(Constants.WeekDayCount - 1)) : + new Pair<>(pastDate, pastDate.plusDays(Constants.WeekDayCount))); + + ret.setSuccess(true); + + return ret; + } + + private LocalDateTime getFirstThursday(int year) { + return getFirstThursday(year, Constants.InvalidMonth); + } + + private LocalDateTime getFirstThursday(int year, int month) { + int targetMonth = month; + + if (month == Constants.InvalidMonth) { + targetMonth = Month.JANUARY.getValue(); + } + + LocalDateTime firstDay = LocalDateTime.of(year, targetMonth, 1, 0, 0); + LocalDateTime firstThursday = DateUtil.thisDate(firstDay, DayOfWeek.THURSDAY.getValue()); + + // Thursday fall into next year or next month + if (firstThursday.getMonthValue() != targetMonth) { + firstThursday = firstThursday.plusDays(Constants.WeekDayCount); + } + + return firstThursday; + } + + private LocalDateTime getLastThursday(int year) { + return getLastThursday(year, Constants.InvalidMonth); + } + + private LocalDateTime getLastThursday(int year, int month) { + int targetMonth = month; + + if (month == Constants.InvalidMonth) { + targetMonth = Month.DECEMBER.getValue(); + } + + LocalDateTime lastDay = getLastDay(year, targetMonth); + LocalDateTime lastThursday = DateUtil.thisDate(lastDay, DayOfWeek.THURSDAY.getValue()); + + // Thursday fall into next year or next month + if (lastThursday.getMonthValue() != targetMonth) { + lastThursday = lastThursday.minusDays(Constants.WeekDayCount); + } + + return lastThursday; + } + + private LocalDateTime getLastDay(int year, int month) { + month++; + if (month == 13) { + year++; + month = 1; + } + + LocalDateTime firstDayOfNextMonth = LocalDateTime.of(year, month, 1, 0, 0); + return firstDayOfNextMonth.minusDays(1); + } + + private LocalDateTime getMondayOfTargetWeek(String cardinalStr, int month, int year) { + LocalDateTime result; + if (config.isLastCardinal(cardinalStr)) { + LocalDateTime lastThursday = getLastThursday(year, month); + result = DateUtil.thisDate(lastThursday, DayOfWeek.MONDAY.getValue()); + } else { + int cardinal = getWeekNumberForMonth(cardinalStr); + LocalDateTime firstThursday = getFirstThursday(year, month); + + result = DateUtil.thisDate(firstThursday, DayOfWeek.MONDAY.getValue()).plusDays(Constants.WeekDayCount * (cardinal - 1)); + } + + return result; + } + + private int getWeekNumberForMonth(String cardinalStr) { + int cardinal; + + if (config.isLastCardinal(cardinalStr)) { + // "last week of month" might not be "5th week of month" + // Sometimes it can also be "4th week of month" depends on specific year and month + // But as we only have one Timex, so we use "5" to indicate last week of month + cardinal = Constants.MaxWeekOfMonth; + } else { + cardinal = config.getCardinalMap().get(cardinalStr); + } + + return cardinal; + } + + private DateTimeResolutionResult parseDecade(String text, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + int firstTwoNumOfYear = referenceDate.getYear() / 100; + int decade = 0; + int decadeLastYear = 10; + int swift = 1; + boolean inputCentury = false; + + String trimmedText = text.trim(); + ConditionalMatch match = RegexExtension.matchExact(this.config.getDecadeWithCenturyRegex(), text, true); + String beginLuisStr; + String endLuisStr; + + if (match.getSuccess()) { + + String decadeStr = match.getMatch().get().getGroup("decade").value.toLowerCase(); + if (!IntegerUtility.canParse(decadeStr)) { + if (this.config.getWrittenDecades().containsKey(decadeStr)) { + decade = this.config.getWrittenDecades().get(decadeStr); + } else if (this.config.getSpecialDecadeCases().containsKey(decadeStr)) { + firstTwoNumOfYear = this.config.getSpecialDecadeCases().get(decadeStr) / 100; + decade = this.config.getSpecialDecadeCases().get(decadeStr) % 100; + inputCentury = true; + } + } else { + decade = Integer.parseInt(decadeStr); + } + + String centuryStr = match.getMatch().get().getGroup("century").value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(centuryStr)) { + if (!IntegerUtility.canParse(centuryStr)) { + if (this.config.getNumbers().containsKey(centuryStr)) { + firstTwoNumOfYear = this.config.getNumbers().get(centuryStr); + } else { + // handle the case like "one/two thousand", "one/two hundred", etc. + List er = this.config.getIntegerExtractor().extract(centuryStr); + + if (er.size() == 0) { + return ret; + } + + firstTwoNumOfYear = Math.round(((Double)(this.config.getNumberParser().parse(er.get(0)).getValue() != null ? + this.config.getNumberParser().parse(er.get(0)).getValue() : + 0)).floatValue()); + if (firstTwoNumOfYear >= 100) { + firstTwoNumOfYear = firstTwoNumOfYear / 100; + } + } + } else { + firstTwoNumOfYear = Integer.parseInt(centuryStr); + } + + inputCentury = true; + } + } else { + // handle cases like "the last 2 decades" "the next decade" + match = RegexExtension.matchExact(this.config.getRelativeDecadeRegex(), trimmedText, true); + if (match.getSuccess()) { + inputCentury = true; + + swift = this.config.getSwiftDayOrMonth(trimmedText); + + String numStr = match.getMatch().get().getGroup("number").value.toLowerCase(); + List er = this.config.getIntegerExtractor().extract(numStr); + if (er.size() == 1) { + int swiftNum = Math.round(((Double)(this.config.getNumberParser().parse(er.get(0)).getValue() != null ? + this.config.getNumberParser().parse(er.get(0)).getValue() : + 0)).floatValue()); + swift = swift * swiftNum; + } + + int beginDecade = (referenceDate.getYear() % 100) / 10; + if (swift < 0) { + beginDecade += swift; + } else if (swift > 0) { + beginDecade += 1; + } + + decade = beginDecade * 10; + } else { + return ret; + } + } + + int beginYear = firstTwoNumOfYear * 100 + decade; + // swift = 0 corresponding to the/this decade + int totalLastYear = decadeLastYear * Math.abs(swift == 0 ? 1 : swift); + + if (inputCentury) { + beginLuisStr = DateTimeFormatUtil.luisDate(beginYear, 1, 1); + endLuisStr = DateTimeFormatUtil.luisDate(beginYear + totalLastYear, 1, 1); + } else { + String beginYearStr = String.format("XX%s", decade); + beginLuisStr = DateTimeFormatUtil.luisDate(-1, 1, 1); + beginLuisStr = beginLuisStr.replace("XXXX", beginYearStr); + + String endYearStr = String.format("XX%s", (decade + totalLastYear)); + endLuisStr = DateTimeFormatUtil.luisDate(-1, 1, 1); + endLuisStr = endLuisStr.replace("XXXX", endYearStr); + } + ret.setTimex(String.format("(%s,%s,P%sY)", beginLuisStr, endLuisStr, totalLastYear)); + + int futureYear = beginYear; + int pastYear = beginYear; + LocalDateTime startDate = DateUtil.safeCreateFromMinValue(beginYear, 1, 1); + if (!inputCentury && startDate.isBefore(referenceDate)) { + futureYear += 100; + } + + if (!inputCentury && startDate.compareTo(referenceDate) >= 0) { + pastYear -= 100; + } + + ret.setFutureValue(new Pair<>(DateUtil.safeCreateFromMinValue(futureYear, 1, 1), + DateUtil.safeCreateFromMinValue(futureYear + totalLastYear, 1, 1))); + + ret.setPastValue(new Pair<>(DateUtil.safeCreateFromMinValue(pastYear, 1, 1), + DateUtil.safeCreateFromMinValue(pastYear + totalLastYear, 1, 1))); + + ret.setSuccess(true); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + private DateContext getYearContext(String startDateStr, String endDateStr, String text) { + boolean isEndDatePureYear = false; + boolean isDateRelative = false; + int contextYear = Constants.InvalidYear; + + Optional yearMatchForEndDate = Arrays.stream(RegExpUtility.getMatches(this.config.getYearRegex(), endDateStr)).findFirst(); + + if (yearMatchForEndDate.isPresent() && yearMatchForEndDate.get().length == endDateStr.length()) { + isEndDatePureYear = true; + } + + Optional relativeMatchForStartDate = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeRegex(), startDateStr)).findFirst(); + Optional relativeMatchForEndDate = Arrays.stream(RegExpUtility.getMatches(this.config.getRelativeRegex(), endDateStr)).findFirst(); + isDateRelative = relativeMatchForStartDate.isPresent() || relativeMatchForEndDate.isPresent(); + + if (!isEndDatePureYear && !isDateRelative) { + for (Match match : RegExpUtility.getMatches(config.getYearRegex(), text)) { + int year = config.getDateExtractor().getYearFromText(match); + + if (year != Constants.InvalidYear) { + if (contextYear == Constants.InvalidYear) { + contextYear = year; + } else { + // This indicates that the text has two different year value, no common context year + if (contextYear != year) { + contextYear = Constants.InvalidYear; + break; + } + } + } + } + } + + DateContext dateContext = new DateContext(); + dateContext.setYear(contextYear); + return dateContext; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java new file mode 100644 index 000000000..5eb012151 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeAltParser.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtendedModelResult; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Map; +import org.javatuples.Pair; + +public class BaseDateTimeAltParser implements IDateTimeParser { + + private static final String parserName = Constants.SYS_DATETIME_DATETIMEALT; + private final IDateTimeAltParserConfiguration config; + + public BaseDateTimeAltParser(IDateTimeAltParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeResolutionResult value = null; + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = parseDateTimeAndTimeAlt(er, reference); + + if (innerResult.getSuccess()) { + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : value.getTimex()); + + return ret; + } + + // merge the entity with its related contexts and then parse the combine text + private DateTimeResolutionResult parseDateTimeAndTimeAlt(ExtractResult er, LocalDateTime referenceTime) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // Original type of the extracted entity + String subType = ((Map)(er.getData())).get(Constants.SubType).toString(); + ExtractResult dateTimeEr = new ExtractResult(); + + // e.g. {next week Mon} or {Tue}, formmer--"next week Mon" doesn't contain "context" key + boolean hasContext = false; + ExtractResult contextEr = null; + if (((Map)er.getData()).containsKey(Constants.Context)) { + contextEr = (ExtractResult)((Map)er.getData()).get(Constants.Context); + if (contextEr.getType().equals(Constants.ContextType_RelativeSuffix)) { + dateTimeEr.setText(String.format("%s %s", er.getText(), contextEr.getText())); + } else { + dateTimeEr.setText(String.format("%s %s", contextEr.getText(), er.getText())); + } + + hasContext = true; + } else { + dateTimeEr.setText(er.getText()); + } + + dateTimeEr.setData(er.getData()); + DateTimeParseResult dateTimePr = null; + + if (subType.equals(Constants.SYS_DATETIME_DATE)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATE); + dateTimePr = this.config.getDateParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_TIME)) { + if (!hasContext) { + dateTimeEr.setType(Constants.SYS_DATETIME_TIME); + dateTimePr = this.config.getTimeParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.SYS_DATETIME_DATE) || contextEr.getType().equals(Constants.ContextType_RelativePrefix)) { + // For cases: + // Monday 9 am or 11 am + // next 9 am or 11 am + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIME); + dateTimePr = this.config.getDateTimeParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.ContextType_AmPm)) { + // For cases: in the afternoon 3 o'clock or 5 o'clock + dateTimeEr.setType(Constants.SYS_DATETIME_TIME); + dateTimePr = this.config.getTimeParser().parse(dateTimeEr, referenceTime); + } + } else if (subType.equals(Constants.SYS_DATETIME_DATETIME)) { + // "next week Mon 9 am or Tue 1 pm" + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIME); + dateTimePr = this.config.getDateTimeParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + if (!hasContext) { + dateTimeEr.setType(Constants.SYS_DATETIME_TIMEPERIOD); + dateTimePr = this.config.getTimePeriodParser().parse(dateTimeEr, referenceTime); + } else if (contextEr.getType().equals(Constants.SYS_DATETIME_DATE) || contextEr.getType().equals(Constants.ContextType_RelativePrefix)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIMEPERIOD); + dateTimePr = this.config.getDateTimePeriodParser().parse(dateTimeEr, referenceTime); + } + } else if (subType.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATETIMEPERIOD); + dateTimePr = this.config.getDateTimePeriodParser().parse(dateTimeEr, referenceTime); + } else if (subType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + dateTimeEr.setType(Constants.SYS_DATETIME_DATEPERIOD); + dateTimePr = this.config.getDatePeriodParser().parse(dateTimeEr, referenceTime); + } + + if (dateTimePr != null && dateTimePr.getValue() != null) { + ret.setFutureValue(((DateTimeResolutionResult)dateTimePr.getValue()).getFutureValue()); + ret.setPastValue(((DateTimeResolutionResult)dateTimePr.getValue()).getPastValue()); + ret.setTimex(dateTimePr.getTimexStr()); + + // Create resolution + getResolution(er, dateTimePr, ret); + + ret.setSuccess(true); + } + + return ret; + } + + private void getResolution(ExtractResult er, DateTimeParseResult pr, DateTimeResolutionResult ret) { + String parentText = ((Map)er.getData()).get(ExtendedModelResult.ParentTextKey).toString(); + String type = pr.getType(); + + boolean isPeriod = false; + boolean isSinglePoint = false; + String singlePointResolution = ""; + String pastStartPointResolution = ""; + String pastEndPointResolution = ""; + String futureStartPointResolution = ""; + String futureEndPointResolution = ""; + String singlePointType = ""; + String startPointType = ""; + String endPointType = ""; + + if (type.equals(Constants.SYS_DATETIME_DATEPERIOD) || type.equalsIgnoreCase(Constants.SYS_DATETIME_TIMEPERIOD) || + type.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + isPeriod = true; + switch (type) { + case Constants.SYS_DATETIME_DATEPERIOD: + startPointType = TimeTypeConstants.START_DATE; + endPointType = TimeTypeConstants.END_DATE; + pastStartPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatDate(((Pair)ret.getFutureValue()).getValue1()); + break; + + case Constants.SYS_DATETIME_DATETIMEPERIOD: + startPointType = TimeTypeConstants.START_DATETIME; + endPointType = TimeTypeConstants.END_DATETIME; + + if (ret.getPastValue() instanceof Pair) { + pastStartPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatDateTime(((Pair)ret.getFutureValue()).getValue1()); + } else if (ret.getPastValue() instanceof LocalDateTime) { + pastStartPointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getPastValue()); + futureStartPointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getFutureValue()); + } + + break; + + case Constants.SYS_DATETIME_TIMEPERIOD: + startPointType = TimeTypeConstants.START_TIME; + endPointType = TimeTypeConstants.END_TIME; + pastStartPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getPastValue()).getValue0()); + pastEndPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getPastValue()).getValue1()); + futureStartPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getFutureValue()).getValue0()); + futureEndPointResolution = DateTimeFormatUtil.formatTime(((Pair)ret.getFutureValue()).getValue1()); + break; + default: + break; + } + } else { + isSinglePoint = true; + switch (type) { + case Constants.SYS_DATETIME_DATE: + singlePointType = TimeTypeConstants.DATE; + singlePointResolution = DateTimeFormatUtil.formatDate((LocalDateTime)ret.getFutureValue()); + break; + + case Constants.SYS_DATETIME_DATETIME: + singlePointType = TimeTypeConstants.DATETIME; + singlePointResolution = DateTimeFormatUtil.formatDateTime((LocalDateTime)ret.getFutureValue()); + break; + + case Constants.SYS_DATETIME_TIME: + singlePointType = TimeTypeConstants.TIME; + singlePointResolution = DateTimeFormatUtil.formatTime((LocalDateTime)ret.getFutureValue()); + break; + default: + break; + } + } + + if (isPeriod) { + ret.setFutureResolution(ImmutableMap.builder() + .put(startPointType, futureStartPointResolution) + .put(endPointType, futureEndPointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + + ret.setPastResolution(ImmutableMap.builder() + .put(startPointType, pastStartPointResolution) + .put(endPointType, pastEndPointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + } else if (isSinglePoint) { + ret.setFutureResolution(ImmutableMap.builder() + .put(singlePointType, singlePointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + + ret.setPastResolution(ImmutableMap.builder() + .put(singlePointType, singlePointResolution) + .put(ExtendedModelResult.ParentTextKey, parentText) + .build()); + } + + if (((DateTimeResolutionResult)pr.getValue()).getMod() != null) { + ret.setMod(((DateTimeResolutionResult)pr.getValue()).getMod()); + } + + if (((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution()); + } + } + + @Override + public List filterResults(String query, List candidateResults) { + + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java new file mode 100644 index 000000000..835a21d7c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimeParser.java @@ -0,0 +1,398 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.AgoLaterUtil; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +public class BaseDateTimeParser implements IDateTimeParser { + + private final IDateTimeParserConfiguration config; + + public BaseDateTimeParser(IDateTimeParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATETIME; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = this.mergeDateAndTime(er.getText(), referenceDate); + + if (!innerResult.getSuccess()) { + innerResult = this.parseBasicRegex(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseTimeOfToday(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseSpecialTimeOfDate(er.getText(), referenceDate); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parserDurationWithAgoAndLater(er.getText(), referenceDate); + } + + if (innerResult.getSuccess()) { + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.DATETIME, + DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.DATETIME, + DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), value, "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + // Merge a Date entity and a Time entity + private DateTimeResolutionResult mergeDateAndTime(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + List ersDate = config.getDateExtractor().extract(text, reference); + if (ersDate.isEmpty()) { + ersDate = config.getDateExtractor().extract(config.getTokenBeforeDate() + text, reference); + if (ersDate.size() == 1) { + int newStart = ersDate.get(0).getStart() - config.getTokenBeforeDate().length(); + ersDate.get(0).setStart(newStart); + ersDate.set(0, ersDate.get(0)); + } else { + return result; + } + } else { + // This is to understand if there is an ambiguous token in the text. For some + // languages (e.g. spanish), + // the same word could mean different things (e.g a time in the day or an + // specific day). + if (config.containsAmbiguousToken(text, ersDate.get(0).getText())) { + return result; + } + } + + List ersTime = config.getTimeExtractor().extract(text, reference); + if (ersTime.isEmpty()) { + // Here we filter out "morning, afternoon, night..." time entities + ersTime = config.getTimeExtractor().extract(config.getTokenBeforeTime() + text, reference); + if (ersTime.size() == 1) { + int newStart = ersTime.get(0).getStart() - config.getTokenBeforeTime().length(); + ersTime.get(0).setStart(newStart); + ersTime.set(0, ersTime.get(0)); + } else if (ersTime.isEmpty()) { + // check whether there is a number being used as a time point + boolean hasTimeNumber = false; + List numErs = config.getIntegerExtractor().extract(text); + if (!numErs.isEmpty() && ersDate.size() == 1) { + for (ExtractResult num : numErs) { + int middleBegin = ersDate.get(0).getStart() + ersDate.get(0).getLength(); + int middleEnd = num.getStart(); + if (middleBegin > middleEnd) { + continue; + } + + String middleStr = text.substring(middleBegin, middleEnd).trim().toLowerCase(); + Optional match = Arrays + .stream(RegExpUtility.getMatches(config.getDateNumberConnectorRegex(), middleStr)) + .findFirst(); + if (StringUtility.isNullOrEmpty(middleStr) || match.isPresent()) { + num.setType(Constants.SYS_DATETIME_TIME); + ersTime.add(num); + hasTimeNumber = true; + } + } + } + + if (!hasTimeNumber) { + return result; + } + } + } + + // Handle cases like "Oct. 5 in the afternoon at 7:00"; + // in this case "5 in the afternoon" will be extracted as a Time entity + int correctTimeIdx = 0; + while (correctTimeIdx < ersTime.size() && ersTime.get(correctTimeIdx).isOverlap(ersDate.get(0))) { + correctTimeIdx++; + } + + if (correctTimeIdx >= ersTime.size()) { + return result; + } + + DateTimeParseResult prDate = config.getDateParser().parse(ersDate.get(0), reference); + DateTimeParseResult prTime = config.getTimeParser().parse(ersTime.get(correctTimeIdx), reference); + + if (prDate.getValue() == null || prTime.getValue() == null) { + return result; + } + + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)prDate.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)prDate.getValue()).getPastValue(); + LocalDateTime time = (LocalDateTime)((DateTimeResolutionResult)prTime.getValue()).getPastValue(); + + int hour = time.getHour(); + int min = time.getMinute(); + int sec = time.getSecond(); + + // Handle morning, afternoon + if (RegExpUtility.getMatches(config.getPMTimeRegex(), text).length != 0 && withinAfternoonHours(hour)) { + hour += Constants.HalfDayHourCount; + } else if (RegExpUtility.getMatches(config.getAMTimeRegex(), text).length != 0 && + withinMorningHoursAndNoon(hour, min, sec)) { + hour -= Constants.HalfDayHourCount; + } + + String timeStr = prTime.getTimexStr(); + if (timeStr.endsWith(Constants.Comment_AmPm)) { + timeStr = timeStr.substring(0, timeStr.length() - 4); + } + + timeStr = String.format("T%02d%s", hour, timeStr.substring(3)); + result.setTimex(prDate.getTimexStr() + timeStr); + DateTimeResolutionResult val = (DateTimeResolutionResult)prTime.getValue(); + if (hour <= Constants.HalfDayHourCount && RegExpUtility.getMatches(config.getPMTimeRegex(), text).length == 0 && + RegExpUtility.getMatches(config.getAMTimeRegex(), text).length == 0 && + !StringUtility.isNullOrEmpty(val.getComment())) { + result.setComment(Constants.Comment_AmPm); + } + + result.setFutureValue(DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), + futureDate.getDayOfMonth(), hour, min, sec)); + result.setPastValue(DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), + pastDate.getDayOfMonth(), hour, min, sec)); + + result.setSuccess(true); + + // Change the value of time object + prTime.setTimexStr(timeStr); + if (!StringUtility.isNullOrEmpty(result.getComment())) { + DateTimeResolutionResult newValue = (DateTimeResolutionResult)prTime.getValue(); + newValue.setComment(result.getComment().equals(Constants.Comment_AmPm) ? Constants.Comment_AmPm : ""); + prTime.setValue(newValue); + prTime.setTimexStr(timeStr); + } + + // Add the date and time object in case we want to split them + List entities = new ArrayList<>(); + entities.add(prDate); + entities.add(prTime); + result.setSubDateTimeEntities(entities); + + result.setTimeZoneResolution(((DateTimeResolutionResult)prTime.getValue()).getTimeZoneResolution()); + + return result; + } + + private DateTimeResolutionResult parseBasicRegex(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + // Handle "now" + if (RegexExtension.isExactMatch(config.getNowRegex(), trimmedText, true)) { + ResultTimex timexResult = config.getMatchedNowTimex(trimmedText); + result.setTimex(timexResult.getTimex()); + result.setFutureValue(reference); + result.setPastValue(reference); + result.setSuccess(true); + } + + return result; + } + + private DateTimeResolutionResult parseTimeOfToday(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + int hour = 0; + int minute = 0; + int second = 0; + String timeStr; + + ConditionalMatch wholeMatch = RegexExtension.matchExact(config.getSimpleTimeOfTodayAfterRegex(), trimmedText, true); + if (!wholeMatch.getSuccess()) { + wholeMatch = RegexExtension.matchExact(config.getSimpleTimeOfTodayBeforeRegex(), trimmedText, true); + } + + if (wholeMatch.getSuccess()) { + String hourStr = wholeMatch.getMatch().get().getGroup(Constants.HourGroupName).value; + if (StringUtility.isNullOrEmpty(hourStr)) { + hourStr = wholeMatch.getMatch().get().getGroup("hournum").value.toLowerCase(); + hour = config.getNumbers().get(hourStr); + } else { + hour = Integer.parseInt(hourStr); + } + + timeStr = String.format("T%02d", hour); + } else { + List ers = config.getTimeExtractor().extract(trimmedText, reference); + if (ers.size() != 1) { + ers = config.getTimeExtractor().extract(config.getTokenBeforeTime() + trimmedText, reference); + if (ers.size() == 1) { + int newStart = ers.get(0).getStart() - config.getTokenBeforeTime().length(); + ers.get(0).setStart(newStart); + ers.set(0, ers.get(0)); + } else { + return result; + } + } + + DateTimeParseResult pr = config.getTimeParser().parse(ers.get(0), reference); + if (pr.getValue() == null) { + return result; + } + + LocalDateTime time = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + hour = time.getHour(); + minute = time.getMinute(); + second = time.getSecond(); + timeStr = pr.getTimexStr(); + } + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSpecificTimeOfDayRegex(), trimmedText)) + .findFirst(); + + if (match.isPresent()) { + String matchStr = match.get().value.toLowerCase(); + + // Handle "last", "next" + int swift = config.getSwiftDay(matchStr); + LocalDateTime date = reference.plusDays(swift); + + // Handle "morning", "afternoon" + hour = config.getHour(matchStr, hour); + + // In this situation, timeStr cannot end up with "ampm", because we always have + // a "morning" or "night" + if (timeStr.endsWith(Constants.Comment_AmPm)) { + timeStr = timeStr.substring(0, timeStr.length() - 4); + } + + timeStr = String.format("T%02d%s", hour, timeStr.substring(3)); + + result.setTimex(DateTimeFormatUtil.formatDate(date) + timeStr); + LocalDateTime dateResult = DateUtil.safeCreateFromMinValue(date.getYear(), date.getMonthValue(), + date.getDayOfMonth(), hour, minute, second); + + result.setFutureValue(dateResult); + result.setPastValue(dateResult); + result.setSuccess(true); + } + + return result; + } + + private DateTimeResolutionResult parseSpecialTimeOfDate(String text, LocalDateTime reference) { + DateTimeResolutionResult result = parseUnspecificTimeOfDate(text, reference); + + if (result.getSuccess()) { + return result; + } + + List ers = config.getDateExtractor().extract(text, reference); + if (ers.size() != 1) { + return result; + } + + String beforeStr = text.substring(0, ers.get(0).getStart()); + if (RegExpUtility.getMatches(config.getSpecificEndOfRegex(), beforeStr).length != 0) { + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), reference); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + result = resolveEndOfDay(pr.getTimexStr(), futureDate, pastDate); + } + + return result; + } + + private DateTimeResolutionResult parseUnspecificTimeOfDate(String text, LocalDateTime reference) { + // Handle 'eod', 'end of day' + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional eod = Arrays.stream(RegExpUtility.getMatches(config.getUnspecificEndOfRegex(), text)).findFirst(); + + if (eod.isPresent()) { + result = resolveEndOfDay(DateTimeFormatUtil.formatDate(reference), reference, reference); + } + + return result; + } + + private DateTimeResolutionResult resolveEndOfDay(String timexPrefix, LocalDateTime futureDate, LocalDateTime pastDate) { + String timex = String.format("%sT23:59:59", timexPrefix); + LocalDateTime futureValue = LocalDateTime.of(futureDate.toLocalDate(), LocalTime.MIDNIGHT).plusDays(1).minusSeconds(1); + LocalDateTime pastValue = LocalDateTime.of(pastDate.toLocalDate(), LocalTime.MIDNIGHT).plusDays(1).minusSeconds(1); + + DateTimeResolutionResult result = new DateTimeResolutionResult(); + result.setTimex(timex); + result.setFutureValue(futureValue); + result.setPastValue(pastValue); + result.setSuccess(true); + + return result; + } + + private boolean withinAfternoonHours(int hour) { + return hour < Constants.HalfDayHourCount; + } + + private boolean withinMorningHoursAndNoon(int hour, int min, int sec) { + return (hour > Constants.HalfDayHourCount || (hour == Constants.HalfDayHourCount && (min > 0 || sec > 0))); + } + + private DateTimeResolutionResult parserDurationWithAgoAndLater(String text, LocalDateTime reference) { + return AgoLaterUtil.parseDurationWithAgoAndLater(text, reference, config.getDurationExtractor(), + config.getDurationParser(), config.getUnitMap(), config.getUnitRegex(), + config.getUtilityConfiguration(), config::getSwiftDay); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java new file mode 100644 index 000000000..011ceb157 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDateTimePeriodParser.java @@ -0,0 +1,1036 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RangeTimexComponents; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class BaseDateTimePeriodParser implements IDateTimeParser { + + protected final IDateTimePeriodParserConfiguration config; + + public BaseDateTimePeriodParser(IDateTimePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult = internalParse(er.getText(), referenceDate); + + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (HashMap)er.getData(); + + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + if (timezonePr.getValue() != null) { + innerResult.setTimeZoneResolution(((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution()); + } + } + + if (innerResult.getSuccess()) { + if (!isBeforeOrAfterMod(innerResult.getMod())) { + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, + DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getFutureValue()).getValue0())) + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getFutureValue()).getValue1())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getPastValue()).getValue0())) + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime(((Pair)innerResult.getPastValue()).getValue1())) + .build(); + innerResult.setPastResolution(pastResolution); + + } else { + if (innerResult.getMod().equals(Constants.AFTER_MOD)) { + // Cases like "1/1/2015 after 2:00" there is no EndTime + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.START_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + } else { + // Cases like "1/1/2015 before 5:00 in the afternoon" there is no StartTime + Map futureResolution = ImmutableMap.builder() + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getFutureValue())) + .build(); + innerResult.setFutureResolution(futureResolution); + + Map pastResolution = ImmutableMap.builder() + .put(TimeTypeConstants.END_DATETIME, DateTimeFormatUtil.formatDateTime((LocalDateTime)innerResult.getPastValue())) + .build(); + innerResult.setPastResolution(pastResolution); + } + } + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + private DateTimeResolutionResult internalParse(String text, LocalDateTime reference) { + DateTimeResolutionResult innerResult = this.mergeDateAndTimePeriods(text, reference); + + if (!innerResult.getSuccess()) { + innerResult = this.mergeTwoTimePoints(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseSpecificTimeOfDay(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDuration(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseRelativeUnit(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = this.parseDateWithPeriodPrefix(text, reference); + } + + if (!innerResult.getSuccess()) { + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + innerResult = this.parseDateWithTimePeriodSuffix(text, reference); + } + + return innerResult; + } + + private boolean isBeforeOrAfterMod(String mod) { + return !StringUtility.isNullOrEmpty(mod) && + (mod == Constants.BEFORE_MOD || mod == Constants.AFTER_MOD); + } + + private DateTimeResolutionResult mergeDateAndTimePeriods(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + String trimmedText = text.trim().toLowerCase(); + + List ers = config.getTimePeriodExtractor().extract(trimmedText, referenceTime); + + if (ers.size() == 0) { + return parsePureNumberCases(text, referenceTime); + } else if (ers.size() == 1) { + ParseResult timePeriodParseResult = config.getTimePeriodParser().parse(ers.get(0)); + DateTimeResolutionResult timePeriodResolutionResult = (DateTimeResolutionResult)timePeriodParseResult.getValue(); + + if (timePeriodResolutionResult == null) { + return parsePureNumberCases(text, referenceTime); + } + + String timePeriodTimex = timePeriodResolutionResult.getTimex(); + + + // If it is a range type timex + if (TimexUtility.isRangeTimex(timePeriodTimex)) { + List dateResult = config.getDateExtractor().extract(trimmedText.replace(ers.get(0).getText(), ""), referenceTime); + String dateText = trimmedText.replace(ers.get(0).getText(), "").replace(config.getTokenBeforeDate(), "").trim(); + + // If only one Date is extracted and the Date text equals to the rest part of source text + if (dateResult.size() == 1 && dateText.equals(dateResult.get(0).getText())) { + String dateTimex; + LocalDateTime futureTime; + LocalDateTime pastTime; + + DateTimeParseResult pr = config.getDateParser().parse(dateResult.get(0), referenceTime); + + if (pr.getValue() != null) { + futureTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + pastTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + dateTimex = pr.getTimexStr(); + } else { + return parsePureNumberCases(text, referenceTime); + } + + RangeTimexComponents rangeTimexComponents = TimexUtility.getRangeTimexComponents(timePeriodTimex); + if (rangeTimexComponents.isValid) { + String beginTimex = TimexUtility.combineDateAndTimeTimex(dateTimex, rangeTimexComponents.beginTimex); + String endTimex = TimexUtility.combineDateAndTimeTimex(dateTimex, rangeTimexComponents.endTimex); + ret.setTimex(TimexUtility.generateDateTimePeriodTimex(beginTimex, endTimex, rangeTimexComponents.durationTimex)); + + Pair timePeriodFutureValue = (Pair)timePeriodResolutionResult.getFutureValue(); + LocalDateTime beginTime = timePeriodFutureValue.getValue0(); + LocalDateTime endTime = timePeriodFutureValue.getValue1(); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(futureTime.getYear(), futureTime.getMonthValue(), futureTime.getDayOfMonth(), + beginTime.getHour(), beginTime.getMinute(), beginTime.getSecond()), + DateUtil.safeCreateFromMinValue(futureTime.getYear(), futureTime.getMonthValue(), futureTime.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()) + )); + + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(pastTime.getYear(), pastTime.getMonthValue(), pastTime.getDayOfMonth(), + beginTime.getHour(), beginTime.getMinute(), beginTime.getSecond()), + DateUtil.safeCreateFromMinValue(pastTime.getYear(), pastTime.getMonthValue(), pastTime.getDayOfMonth(), + endTime.getHour(), endTime.getMinute(), endTime.getSecond()) + )); + + + if (!StringUtility.isNullOrEmpty(timePeriodResolutionResult.getComment()) && + timePeriodResolutionResult.getComment().equals(Constants.Comment_AmPm)) { + // AmPm comment is used for later SetParserResult to judge whether this parse comments should have two parsing results + // Cases like "from 10:30 to 11 on 1/1/2015" should have AmPm comment, as it can be parsed to "10:30am to 11am" and also be parsed to "10:30pm to 11pm" + // Cases like "from 10:30 to 3 on 1/1/2015" should not have AmPm comment + if (beginTime.getHour() < Constants.HalfDayHourCount && endTime.getHour() < Constants.HalfDayHourCount) { + ret.setComment(Constants.Comment_AmPm); + } + } + + ret.setSuccess(true); + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr); + subDateTimeEntities.add(timePeriodParseResult); + ret.setSubDateTimeEntities(subDateTimeEntities); + + return ret; + } + } + + return parsePureNumberCases(text, referenceTime); + } + } + + return ret; + } + + // Handle cases like "Monday 7-9", where "7-9" can't be extracted by the TimePeriodExtractor + private DateTimeResolutionResult parsePureNumberCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPureNumberFromToRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPureNumberBetweenAndRegex(), trimmedText)).findFirst(); + } + + if (match.isPresent() && (match.get().index == 0 || match.get().index + match.get().length == trimmedText.length())) { + ParseTimePeriodResult parseTimePeriodResult = parseTimePeriod(match.get()); + int beginHour = parseTimePeriodResult.beginHour; + int endHour = parseTimePeriodResult.endHour; + ret.setComment(parseTimePeriodResult.comments); + + String dateStr = ""; + + // Parse following date + List ers = config.getDateExtractor().extract(trimmedText.replace(match.get().value, ""), referenceTime); + LocalDateTime futureDate; + LocalDateTime pastDate; + + if (ers.size() > 0) { + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), referenceTime); + if (pr.getValue() != null) { + DateTimeResolutionResult prValue = (DateTimeResolutionResult)pr.getValue(); + futureDate = (LocalDateTime)prValue.getFutureValue(); + pastDate = (LocalDateTime)prValue.getPastValue(); + + dateStr = pr.getTimexStr(); + + } else { + return ret; + } + } else { + return ret; + } + + int pastHours = endHour - beginHour; + String beginTimex = TimexUtility.combineDateAndTimeTimex(dateStr, DateTimeFormatUtil.shortTime(beginHour)); + String endTimex = TimexUtility.combineDateAndTimeTimex(dateStr, DateTimeFormatUtil.shortTime(endHour)); + String durationTimex = TimexUtility.generateDurationTimex(endHour - beginHour, Constants.TimexHour, true); + + ret.setTimex(TimexUtility.generateDateTimePeriodTimex(beginTimex, endTimex, durationTimex)); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + endHour, 0, 0) + )); + + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + endHour, 0, 0) + )); + + ret.setSuccess(true); + } + + return ret; + } + + private ParseTimePeriodResult parseTimePeriod(Match match) { + + ParseTimePeriodResult result = new ParseTimePeriodResult(); + + // This "from .. to .." pattern is valid if followed by a Date OR "pm" + boolean hasAm = false; + boolean hasPm = false; + String comments = ""; + + // Get hours + MatchGroup hourGroup = match.getGroup(Constants.HourGroupName); + String hourStr = hourGroup.captures[0].value; + + if (this.config.getNumbers().containsKey(hourStr)) { + result.beginHour = this.config.getNumbers().get(hourStr); + } else { + result.beginHour = Integer.parseInt(hourStr); + } + + hourStr = hourGroup.captures[1].value; + + if (this.config.getNumbers().containsKey(hourStr)) { + result.endHour = this.config.getNumbers().get(hourStr); + } else { + result.endHour = Integer.parseInt(hourStr); + } + + // Parse "pm" + String pmStr = match.getGroup(Constants.PmGroupName).value; + String amStr = match.getGroup(Constants.AmGroupName).value; + String descStr = match.getGroup(Constants.DescGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr) || !StringUtility.isNullOrEmpty(descStr) && descStr.startsWith("a")) { + if (result.beginHour >= Constants.HalfDayHourCount) { + result.beginHour -= Constants.HalfDayHourCount; + } + + if (result.endHour >= Constants.HalfDayHourCount) { + result.endHour -= Constants.HalfDayHourCount; + } + + hasAm = true; + } else if (!StringUtility.isNullOrEmpty(pmStr) || !StringUtility.isNullOrEmpty(descStr) && descStr.startsWith("p")) { + if (result.beginHour < Constants.HalfDayHourCount) { + result.beginHour += Constants.HalfDayHourCount; + } + + if (result.endHour < Constants.HalfDayHourCount) { + result.endHour += Constants.HalfDayHourCount; + } + + hasPm = true; + } + + if (!hasAm && !hasPm && result.beginHour <= Constants.HalfDayHourCount && result.endHour <= Constants.HalfDayHourCount) { + if (result.beginHour > result.endHour) { + if (result.beginHour == Constants.HalfDayHourCount) { + result.beginHour = 0; + } else { + result.endHour += Constants.HalfDayHourCount; + } + } + + result.comments = Constants.Comment_AmPm; + } + + return result; + } + + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + DateTimeParseResult pr1; + DateTimeParseResult pr2; + boolean bothHaveDates = false; + boolean beginHasDate = false; + boolean endHasDate = false; + + List timeExtractResults = config.getTimeExtractor().extract(text, referenceDate); + List dateTimeExtractResults = config.getDateTimeExtractor().extract(text, referenceDate); + + if (dateTimeExtractResults.size() == 2) { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(1), referenceDate); + bothHaveDates = true; + } else if (dateTimeExtractResults.size() == 1 && timeExtractResults.size() == 2) { + if (!dateTimeExtractResults.get(0).isOverlap(timeExtractResults.get(0))) { + pr1 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + endHasDate = true; + } else { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getTimeParser().parse(timeExtractResults.get(1), referenceDate); + beginHasDate = true; + } + } else if (dateTimeExtractResults.size() == 1 && timeExtractResults.size() == 1) { + if (timeExtractResults.get(0).getStart() < dateTimeExtractResults.get(0).getStart()) { + pr1 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + pr2 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + endHasDate = true; + } else if (timeExtractResults.get(0).getStart() >= dateTimeExtractResults.get(0).getStart() + dateTimeExtractResults.get(0).getLength()) { + pr1 = config.getDateTimeParser().parse(dateTimeExtractResults.get(0), referenceDate); + pr2 = config.getTimeParser().parse(timeExtractResults.get(0), referenceDate); + beginHasDate = true; + } else { + // If the only TimeExtractResult is part of DateTimeExtractResult, then it should not be handled in this method + return result; + } + } else if (timeExtractResults.size() == 2) { + // If both ends are Time. then this is a TimePeriod, not a DateTimePeriod + return result; + } else { + return result; + } + + if (pr1.getValue() == null || pr2.getValue() == null) { + return result; + } + + LocalDateTime futureBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime futureEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + LocalDateTime pastBegin = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue(); + LocalDateTime pastEnd = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue(); + + if (bothHaveDates) { + if (futureBegin.isAfter(futureEnd)) { + futureBegin = pastBegin; + } + + if (pastEnd.isBefore(pastBegin)) { + pastEnd = futureEnd; + } + } + + if (bothHaveDates) { + result.setTimex(String.format("(%s,%s,PT%dH)", pr1.getTimexStr(), pr2.getTimexStr(), Math.round(ChronoUnit.SECONDS.between(futureBegin, futureEnd) / 3600f))); + // Do nothing + } else if (beginHasDate) { + futureEnd = DateUtil.safeCreateFromMinValue(futureBegin.toLocalDate(), futureEnd.toLocalTime()); + pastEnd = DateUtil.safeCreateFromMinValue(pastBegin.toLocalDate(), pastEnd.toLocalTime()); + + String dateStr = pr1.getTimexStr().split("T")[0]; + result.setTimex(String.format("(%s,%s,PT%dH)", pr1.getTimexStr(), dateStr + pr2.getTimexStr(), ChronoUnit.HOURS.between(futureBegin, futureEnd))); + } else if (endHasDate) { + futureBegin = DateUtil.safeCreateFromMinValue(futureEnd.getYear(), futureEnd.getMonthValue(), futureEnd.getDayOfMonth(), + futureBegin.getHour(), futureBegin.getMinute(), futureBegin.getSecond()); + + pastBegin = DateUtil.safeCreateFromMinValue(pastEnd.getYear(), pastEnd.getMonthValue(), pastEnd.getDayOfMonth(), + pastBegin.getHour(), pastBegin.getMinute(), pastBegin.getSecond()); + + + String dateStr = pr2.getTimexStr().split("T")[0]; + result.setTimex(String.format("(%s,%s,PT%dH)", dateStr + pr1.getTimexStr(), pr2.getTimexStr(), ChronoUnit.HOURS.between(futureBegin, futureEnd))); + } + + DateTimeResolutionResult pr1Value = (DateTimeResolutionResult)pr1.getValue(); + DateTimeResolutionResult pr2Value = (DateTimeResolutionResult)pr2.getValue(); + + String ampmStr1 = pr1Value.getComment(); + String ampmStr2 = pr2Value.getComment(); + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && + !StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm)) { + result.setComment(Constants.Comment_AmPm); + } + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + if (pr1Value.getTimeZoneResolution() != null) { + result.setTimeZoneResolution(pr1Value.getTimeZoneResolution()); + } + + if (pr2Value.getTimeZoneResolution() != null) { + result.setTimeZoneResolution(pr2Value.getTimeZoneResolution()); + } + } + + result.setFutureValue(new Pair(futureBegin, futureEnd)); + result.setPastValue(new Pair(pastBegin, pastEnd)); + + result.setSuccess(true); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + result.setSubDateTimeEntities(subDateTimeEntities); + + return result; + } + + // Parse specific TimeOfDay like "this night", "early morning", "late evening" + protected DateTimeResolutionResult parseSpecificTimeOfDay(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(); + String timeText = trimmedText; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), trimmedText)).findFirst(); + + // Extract early/late prefix from text if any + boolean hasEarly = false; + boolean hasLate = false; + if (match.isPresent()) { + timeText = match.get().getGroup("timeOfDay").value; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("early").value)) { + hasEarly = true; + result.setComment(Constants.Comment_Early); + result.setMod(Constants.EARLY_MOD); + } + + if (!hasEarly && !StringUtility.isNullOrEmpty(match.get().getGroup("late").value)) { + hasLate = true; + result.setComment(Constants.Comment_Late); + result.setMod(Constants.LATE_MOD); + } + } else { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), trimmedText)).findFirst(); + } + + if (match.isPresent()) { + timeText = match.get().value; + } + } + + // Handle time of day + + String timeStr = null; + int beginHour = -1; + int endHour = -1; + int endMin = -1; + + // Late/early only works with time of day + // Only standard time of day (morinng, afternoon, evening and night) will not directly return + MatchedTimeRangeResult matchedTimeRange = config.getMatchedTimeRange(timeText, timeStr, beginHour, endHour, endMin); + timeStr = matchedTimeRange.getTimeStr(); + beginHour = matchedTimeRange.getBeginHour(); + endHour = matchedTimeRange.getEndHour(); + endMin = matchedTimeRange.getEndMin(); + + if (!matchedTimeRange.getMatched()) { + return result; + } + + // Modify time period if "early" or "late" exists + // Since 'time of day' is defined as four hour periods, + // the first 2 hours represent early, the later 2 hours represent late + if (hasEarly) { + endHour = beginHour + 2; + // Handling speical case: night ends with 23:59 + if (endMin == 59) { + endMin = 0; + } + } else if (hasLate) { + beginHour = beginHour + 2; + } + + if (RegexExtension.isExactMatch(config.getSpecificTimeOfDayRegex(), trimmedText, true)) { + int swift = config.getSwiftPrefix(trimmedText); + + LocalDateTime date = referenceDate.plusDays(swift); + int day = date.getDayOfMonth(); + int month = date.getMonthValue(); + int year = date.getYear(); + + result.setTimex(DateTimeFormatUtil.formatDate(date) + timeStr); + + Pair resultValue = new Pair( + DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, endMin, endMin) + ); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + return result; + } + + // Handle Date followed by morning, afternoon and morning, afternoon followed by Date + match = Arrays.stream(RegExpUtility.getMatches(config.getPeriodTimeOfDayWithDateRegex(), trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAmDescRegex(), trimmedText)).findFirst(); + + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getPmDescRegex(), trimmedText)).findFirst(); + } + } + + if (match.isPresent()) { + String beforeStr = trimmedText.substring(0, match.get().index).trim(); + String afterStr = trimmedText.substring(match.get().index + match.get().length).trim(); + + // Eliminate time period, if any + List timePeriodErs = config.getTimePeriodExtractor().extract(beforeStr); + if (timePeriodErs.size() > 0) { + beforeStr = beforeStr.substring(0, timePeriodErs.get(0).getStart()) + beforeStr.substring(timePeriodErs.get(0).getStart() + timePeriodErs.get(0).getLength()) + .trim(); + } else { + timePeriodErs = config.getTimePeriodExtractor().extract(afterStr); + if (timePeriodErs.size() > 0) { + afterStr = afterStr.substring(0, timePeriodErs.get(0).getStart()) + afterStr.substring(timePeriodErs.get(0).getStart() + timePeriodErs.get(0).getLength()) + .trim(); + } + } + + List ers = config.getDateExtractor().extract(beforeStr + " " + afterStr, referenceDate); + + if (ers.size() == 0 || ers.get(0).getLength() < beforeStr.length()) { + boolean valid = false; + + if (ers.size() > 0 && ers.get(0).getStart() == 0) { + String midStr = beforeStr.substring(ers.get(0).getStart() + ers.get(0).getLength()); + if (StringUtility.isNullOrWhiteSpace(midStr.replace(",", " "))) { + valid = true; + } + } + + if (!valid) { + ers = config.getDateExtractor().extract(afterStr, referenceDate); + + if (ers.size() == 0 || ers.get(0).getLength() != beforeStr.length()) { + if (ers.size() > 0 && ers.get(0).getStart() + ers.get(0).getLength() == afterStr.length()) { + String midStr = afterStr.substring(0, ers.get(0).getStart()); + if (StringUtility.isNullOrWhiteSpace(midStr.replace(",", " "))) { + valid = true; + } + } + } else { + valid = true; + } + } + + if (!valid) { + return result; + } + } + + boolean hasSpecificTimePeriod = false; + if (timePeriodErs.size() > 0) { + DateTimeParseResult timePr = config.getTimePeriodParser().parse(timePeriodErs.get(0), referenceDate); + if (timePr != null) { + Pair periodFuture = (Pair)((DateTimeResolutionResult)timePr.getValue()).getFutureValue(); + Pair periodPast = (Pair)((DateTimeResolutionResult)timePr.getValue()).getPastValue(); + + if (periodFuture == periodPast) { + beginHour = periodFuture.getValue0().getHour(); + endHour = periodFuture.getValue1().getHour(); + } else { + if (periodFuture.getValue0().getHour() >= beginHour || periodFuture.getValue1().getHour() <= endHour) { + beginHour = periodFuture.getValue0().getHour(); + endHour = periodFuture.getValue1().getHour(); + } else { + beginHour = periodPast.getValue0().getHour(); + endHour = periodPast.getValue1().getHour(); + } + } + + hasSpecificTimePeriod = true; + } + } + + DateTimeParseResult pr = config.getDateParser().parse(ers.get(0), referenceDate); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + if (!hasSpecificTimePeriod) { + result.setTimex(pr.getTimexStr() + timeStr); + } else { + result.setTimex(String.format("(%sT%d,%sT%d,PT%dH)", pr.getTimexStr(), beginHour, pr.getTimexStr(), endHour, endHour - beginHour)); + } + + Pair futureResult = new Pair( + DateUtil.safeCreateFromMinValue( + futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue( + futureDate.getYear(), futureDate.getMonthValue(), futureDate.getDayOfMonth(), + endHour, endMin, endMin) + ); + + Pair pastResult = new Pair( + DateUtil.safeCreateFromMinValue( + pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + beginHour, 0, 0), + DateUtil.safeCreateFromMinValue( + pastDate.getYear(), pastDate.getMonthValue(), pastDate.getDayOfMonth(), + endHour, endMin, endMin) + ); + + result.setFutureValue(futureResult); + result.setPastValue(pastResult); + + result.setSuccess(true); + + return result; + } + + return result; + } + + // TODO: this can be abstracted with the similar method in BaseDatePeriodParser + // Parse "in 20 minutes" + private DateTimeResolutionResult parseDuration(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + // For the rest of datetime, it will be handled in next function + if (RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), text).length > 0) { + return result; + } + + List ers = config.getDurationExtractor().extract(text, referenceTime); + + if (ers.size() == 1) { + ParseResult pr = config.getDurationParser().parse(ers.get(0)); + + String beforeStr = text.substring(0, pr.getStart()).trim().toLowerCase(); + String afterStr = text.substring(pr.getStart() + pr.getLength()).trim().toLowerCase(); + + List numbersInSuffix = config.getCardinalExtractor().extract(beforeStr); + List numbersInDuration = config.getCardinalExtractor().extract(ers.get(0).getText()); + + // Handle cases like "2 upcoming days", "5 previous years" + if (!numbersInSuffix.isEmpty() && numbersInDuration.isEmpty()) { + ExtractResult numberEr = numbersInSuffix.get(0); + String numberText = numberEr.getText(); + String durationText = ers.get(0).getText(); + String combinedText = String.format("%s %s", numberText, durationText); + List combinedDurationEr = config.getDurationExtractor().extract(combinedText, referenceTime); + + if (!combinedDurationEr.isEmpty()) { + pr = config.getDurationParser().parse(combinedDurationEr.get(0)); + int startIndex = numberEr.getStart() + numberEr.getLength(); + beforeStr = beforeStr.substring(startIndex).trim(); + } + } + + if (pr.getValue() != null) { + int swiftSeconds = 0; + String mod = ""; + DateTimeResolutionResult durationResult = (DateTimeResolutionResult)pr.getValue(); + + if (durationResult.getPastValue() instanceof Double && durationResult.getFutureValue() instanceof Double) { + swiftSeconds = Math.round(((Double)durationResult.getPastValue()).floatValue()); + } + + LocalDateTime beginTime = referenceTime; + LocalDateTime endTime = referenceTime; + + if (RegexExtension.isExactMatch(config.getPastRegex(), beforeStr, true)) { + mod = Constants.BEFORE_MOD; + beginTime = referenceTime.minusSeconds(swiftSeconds); + } + + // Handle the "within (the) (next) xx seconds/minutes/hours" case + // Should also handle the multiple duration case like P1DT8H + // Set the beginTime equal to reference time for now + if (RegexExtension.isExactMatch(config.getWithinNextPrefixRegex(), beforeStr, true)) { + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), beforeStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getPastRegex(), afterStr, true)) { + mod = Constants.BEFORE_MOD; + beginTime = referenceTime.minusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureRegex(), afterStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + if (RegexExtension.isExactMatch(config.getFutureSuffixRegex(), afterStr, true)) { + mod = Constants.AFTER_MOD; + endTime = beginTime.plusSeconds(swiftSeconds); + } + + result.setTimex(String.format("(%sT%s,%sT%s,%s)", + DateTimeFormatUtil.luisDate(beginTime), + DateTimeFormatUtil.luisTime(beginTime), + DateTimeFormatUtil.luisDate(endTime), + DateTimeFormatUtil.luisTime(endTime), + durationResult.getTimex() + )); + + Pair resultValue = new Pair(beginTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + if (!StringUtility.isNullOrEmpty(mod)) { + ((DateTimeResolutionResult)pr.getValue()).setMod(mod); + } + + List subDateTimeEntities = new ArrayList(); + subDateTimeEntities.add(pr); + result.setSubDateTimeEntities(subDateTimeEntities); + + return result; + } + } + + return result; + } + + // Parse "last minute", "next hour" + private DateTimeResolutionResult parseRelativeUnit(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getRelativeTimeUnitRegex(), text)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getRestOfDateTimeRegex(), text)).findFirst(); + } + + if (match.isPresent()) { + String srcUnit = match.get().getGroup("unit").value; + + String unitStr = config.getUnitMap().get(srcUnit); + + int swiftValue = 1; + Optional prefixMatch = Arrays.stream(RegExpUtility.getMatches(config.getPastRegex(), text)).findFirst(); + if (prefixMatch.isPresent()) { + swiftValue = -1; + } + + LocalDateTime beginTime = referenceDate; + LocalDateTime endTime = referenceDate; + String ptTimex = ""; + + if (config.getUnitMap().containsKey(srcUnit)) { + switch (unitStr) { + case "D": + endTime = DateUtil.safeCreateFromMinValue(beginTime.getYear(), beginTime.getMonthValue(), beginTime.getDayOfMonth()); + endTime = endTime.plusDays(1).minusSeconds(1); + ptTimex = String.format("PT%dS", ChronoUnit.SECONDS.between(beginTime, endTime)); + break; + case "H": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusHours(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusHours(swiftValue) : endTime; + ptTimex = "PT1H"; + break; + case "M": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusMinutes(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusMinutes(swiftValue) : endTime; + ptTimex = "PT1M"; + break; + case "S": + beginTime = swiftValue > 0 ? beginTime : referenceDate.plusSeconds(swiftValue); + endTime = swiftValue > 0 ? referenceDate.plusSeconds(swiftValue) : endTime; + ptTimex = "PT1S"; + break; + default: + return result; + } + + result.setTimex(String.format("(%sT%s,%sT%s,%s)", + DateTimeFormatUtil.luisDate(beginTime), + DateTimeFormatUtil.luisTime(beginTime), + DateTimeFormatUtil.luisDate(endTime), + DateTimeFormatUtil.luisTime(endTime), + ptTimex + )); + + Pair resultValue = new Pair(beginTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseDateWithPeriodPrefix(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + List dateResult = config.getDateExtractor().extract(text); + if (dateResult.size() > 0) { + String beforeStr = StringUtility.trimEnd(text.substring(0, dateResult.get(dateResult.size() - 1).getStart())); + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getPrefixDayRegex(), beforeStr)).findFirst(); + if (match.isPresent()) { + DateTimeParseResult pr = config.getDateParser().parse(dateResult.get(dateResult.size() - 1), referenceDate); + if (pr.getValue() != null) { + LocalDateTime startTime = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + startTime = LocalDateTime.of(startTime.getYear(), startTime.getMonthValue(), startTime.getDayOfMonth(), 0, 0, 0); + LocalDateTime endTime = startTime; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("EarlyPrefix").value)) { + endTime = endTime.plusHours(Constants.HalfDayHourCount); + result.setMod(Constants.EARLY_MOD); + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup("MidPrefix").value)) { + startTime = startTime.plusHours(Constants.HalfDayHourCount - Constants.HalfMidDayDurationHourCount); + endTime = endTime.plusHours(Constants.HalfDayHourCount + Constants.HalfMidDayDurationHourCount); + result.setMod(Constants.MID_MOD); + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup("LatePrefix").value)) { + startTime = startTime.plusHours(Constants.HalfDayHourCount); + endTime = startTime.plusHours(Constants.HalfDayHourCount); + result.setMod(Constants.LATE_MOD); + } else { + return result; + } + + result.setTimex(pr.getTimexStr()); + + Pair resultValue = new Pair(startTime, endTime); + + result.setFutureValue(resultValue); + result.setPastValue(resultValue); + + result.setSuccess(true); + } + } + } + + return result; + } + + private DateTimeResolutionResult parseDateWithTimePeriodSuffix(String text, LocalDateTime referenceDate) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional dateEr = config.getDateExtractor().extract(text).stream().findFirst(); + Optional timeEr = config.getTimeExtractor().extract(text).stream().findFirst(); + + if (dateEr.isPresent() && timeEr.isPresent()) { + int dateStrEnd = dateEr.get().getStart() + dateEr.get().getLength(); + + if (dateStrEnd < timeEr.get().getStart()) { + String midStr = text.substring(dateStrEnd, timeEr.get().getStart()); + + if (isValidConnectorForDateAndTimePeriod(midStr)) { + DateTimeParseResult datePr = config.getDateParser().parse(dateEr.get(), referenceDate); + DateTimeParseResult timePr = config.getTimeParser().parse(timeEr.get(), referenceDate); + + if (datePr != null && timePr != null) { + DateTimeResolutionResult timeResolutionResult = (DateTimeResolutionResult)timePr.getValue(); + DateTimeResolutionResult dateResolutionResult = (DateTimeResolutionResult)datePr.getValue(); + LocalDateTime futureDateValue = (LocalDateTime)dateResolutionResult.getFutureValue(); + LocalDateTime pastDateValue = (LocalDateTime)dateResolutionResult.getPastValue(); + LocalDateTime futureTimeValue = (LocalDateTime)timeResolutionResult.getFutureValue(); + LocalDateTime pastTimeValue = (LocalDateTime)timeResolutionResult.getPastValue(); + + result.setComment(timeResolutionResult.getComment()); + result.setTimex(datePr.getTimexStr() + timePr.getTimexStr()); + + result.setFutureValue(DateUtil.safeCreateFromMinValue(futureDateValue.toLocalDate(), futureTimeValue.toLocalTime())); + result.setPastValue(DateUtil.safeCreateFromMinValue(pastDateValue.toLocalDate(), pastTimeValue.toLocalTime())); + + if (RegExpUtility.getMatches(config.getBeforeRegex(), midStr).length > 0) { + result.setMod(Constants.BEFORE_MOD); + } else { + result.setMod(Constants.AFTER_MOD); + } + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(datePr); + subDateTimeEntities.add(timePr); + + result.setSubDateTimeEntities(subDateTimeEntities); + + result.setSuccess(true); + } + } + } + } + + return result; + } + + // Cases like "today after 2:00pm", "1/1/2015 before 2:00 in the afternoon" + // Valid connector in English for Before include: "before", "no later than", "in advance of", "prior to", "earlier than", "sooner than", "by", "till", "until"... + // Valid connector in English for After include: "after", "later than" + private boolean isValidConnectorForDateAndTimePeriod(String text) { + List beforeAfterRegexes = new ArrayList<>(); + beforeAfterRegexes.add(config.getBeforeRegex()); + beforeAfterRegexes.add(config.getAfterRegex()); + text = text.trim(); + + for (Pattern regex : beforeAfterRegexes) { + ConditionalMatch match = RegexExtension.matchExact(regex, text, true); + if (match.getSuccess()) { + return true; + } + } + + return false; + } + + private class ParseTimePeriodResult { + String comments; + int beginHour; + int endHour; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java new file mode 100644 index 000000000..85bb606b4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseDurationParser.java @@ -0,0 +1,414 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseDurationParser implements IDateTimeParser { + + private final IDurationParserConfiguration config; + + public BaseDurationParser(IDurationParserConfiguration configuration) { + this.config = configuration; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DURATION; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult; + + innerResult = parseMergedDuration(er.getText(), reference); + + if (!innerResult.getSuccess()) { + innerResult = parseNumberWithUnit(er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseImplicitDuration(er.getText(), reference); + } + + if (innerResult.getSuccess()) { + innerResult.setFutureResolution(ImmutableMap.builder() + .put(TimeTypeConstants.DURATION, StringUtility.format((Double)innerResult.getFutureValue())) + .build()); + + innerResult.setPastResolution(ImmutableMap.builder() + .put(TimeTypeConstants.DURATION, StringUtility.format((Double)innerResult.getPastValue())) + .build()); + + if (er.getData() != null) { + if (er.getData().equals(Constants.MORE_THAN_MOD)) { + innerResult.setMod(Constants.MORE_THAN_MOD); + } else if (er.getData().equals(Constants.LESS_THAN_MOD)) { + innerResult.setMod(Constants.LESS_THAN_MOD); + } + } + + value = innerResult; + } + } + + DateTimeParseResult result = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex() + ); + + return result; + } + + private DateTimeResolutionResult parseMergedDuration(String text, LocalDateTime reference) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + IExtractor durationExtractor = config.getDurationExtractor(); + + // DurationExtractor without parameter will not extract merged duration + List ers = durationExtractor.extract(text); + + // only handle merged duration cases like "1 month 21 days" + if (ers.size() <= 1) { + result.setSuccess(false); + return result; + } + + int start = ers.get(0).getStart(); + if (start != 0) { + String beforeStr = text.substring(0, start - 1); + if (!StringUtility.isNullOrWhiteSpace(beforeStr)) { + return result; + } + } + + int end = ers.get(ers.size() - 1).getStart() + ers.get(ers.size() - 1).getLength(); + if (end != text.length()) { + String afterStr = text.substring(end); + if (!StringUtility.isNullOrWhiteSpace(afterStr)) { + return result; + } + } + + List prs = new ArrayList<>(); + Map timexMap = new HashMap<>(); + + // insert timex into a dictionary + for (ExtractResult er : ers) { + Pattern unitRegex = config.getDurationUnitRegex(); + Optional unitMatch = Arrays.stream(RegExpUtility.getMatches(unitRegex, er.getText())).findFirst(); + if (unitMatch.isPresent()) { + DateTimeParseResult pr = (DateTimeParseResult)parse(er); + if (pr.getValue() != null) { + timexMap.put(unitMatch.get().getGroup("unit").value, pr.getTimexStr()); + prs.add(pr); + } + } + } + + // sort the timex using the granularity of the duration, "P1M23D" for "1 month 23 days" and "23 days 1 month" + if (prs.size() == ers.size()) { + + result.setTimex(TimexUtility.generateCompoundDurationTimex(timexMap, config.getUnitValueMap())); + + double value = 0; + for (DateTimeParseResult pr : prs) { + value += Double.parseDouble(((DateTimeResolutionResult)pr.getValue()).getFutureValue().toString()); + } + + result.setFutureValue(value); + result.setPastValue(value); + } + + result.setSuccess(true); + return result; + } + + private DateTimeResolutionResult parseNumberWithUnit(String text, LocalDateTime reference) { + DateTimeResolutionResult result = parseNumberSpaceUnit(text); + if (!result.getSuccess()) { + result = parseNumberCombinedUnit(text); + } + + if (!result.getSuccess()) { + result = parseAnUnit(text); + } + + if (!result.getSuccess()) { + result = parseInexactNumberUnit(text); + } + + return result; + } + + // check {and} suffix after a {number} {unit} + private double parseNumberWithUnitAndSuffix(String text) { + double numVal = 0; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixAndRegex(), text)).findFirst(); + if (match.isPresent()) { + String numStr = match.get().getGroup("suffix_num").value.toLowerCase(); + + if (config.getDoubleNumbers().containsKey(numStr)) { + numVal = config.getDoubleNumbers().get(numStr); + } + } + + return numVal; + } + + private DateTimeResolutionResult parseNumberSpaceUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + // if there are spaces between nubmer and unit + List ers = config.getCardinalExtractor().extract(text); + if (ers.size() == 1) { + ExtractResult er = ers.get(0); + ParseResult pr = config.getNumberParser().parse(er); + + // followed unit: {num} (and a half hours) + String srcUnit = ""; + String noNum = text.substring(er.getStart() + er.getLength()).trim().toLowerCase(); + String suffixStr = text; + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getFollowedUnit(), noNum)).findFirst(); + if (match.isPresent()) { + srcUnit = match.get().getGroup("unit").value.toLowerCase(); + suffixStr = match.get().getGroup(Constants.SuffixGroupName).value.toLowerCase(); + } + + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup(Constants.BusinessDayGroupName).value)) { + int numVal = Math.round(Double.valueOf(pr.getValue().toString()).floatValue()); + + String timex = TimexUtility.generateDurationTimex(numVal, Constants.TimexBusinessDay, false); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit.split(" ")[1]); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + + if (config.getUnitMap().containsKey(srcUnit)) { + double numVal = Double.parseDouble(pr.getValue().toString()) + parseNumberWithUnitAndSuffix(suffixStr); + + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = TimexUtility.generateDurationTimex(numVal, unitStr, isLessThanDay(unitStr)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private DateTimeResolutionResult parseNumberCombinedUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + String suffixStr = text; + + // if there are NO spaces between number and unit + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getNumberCombinedWithUnit(), text)).findFirst(); + if (match.isPresent()) { + Double numVal = Double.parseDouble(match.get().getGroup("num").value) + parseNumberWithUnitAndSuffix(suffixStr); + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + if ((numVal > 1000) && (unitStr.equals("Y") || unitStr.equals("MON") || unitStr.equals("W"))) { + return result; + } + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseAnUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + String suffixStr = text; + + // if there are NO spaces between number and unit + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getAnUnitRegex(), text)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getHalfDateUnitRegex(), text)).findFirst(); + } + + if (match.isPresent()) { + double numVal = StringUtility.isNullOrEmpty(match.get().getGroup("half").value) ? 1 : 0.5; + numVal += parseNumberWithUnitAndSuffix(suffixStr); + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(TimexUtility.generateDurationTimex(numVal, unitStr, isLessThanDay(unitStr))); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + + } else if (!StringUtility.isNullOrEmpty(match.get().getGroup(Constants.BusinessDayGroupName).value)) { + String timex = TimexUtility.generateDurationTimex(numVal, Constants.TimexBusinessDay, false); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit.split(" ")[1]); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private DateTimeResolutionResult parseInexactNumberUnit(String text) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getInexactNumberUnitRegex(), text)).findFirst(); + if (match.isPresent()) { + double numVal; + + if (!StringUtility.isNullOrEmpty(match.get().getGroup("NumTwoTerm").value)) { + numVal = 2; + } else { + // set the inexact number "few", "some" to 3 for now + numVal = 3; + } + + String numStr = StringUtility.format(numVal); + + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = numVal * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + return result; + } + } + + return result; + } + + private DateTimeResolutionResult parseImplicitDuration(String text, LocalDateTime reference) { + // handle "all day" "all year" + DateTimeResolutionResult result = getResultFromRegex(config.getAllDateUnitRegex(), text, "1"); + + // handle "during/for the day/week/month/year" + if (config.getOptions().match(DateTimeOptions.CalendarMode) && !result.getSuccess()) { + result = getResultFromRegex(config.getDuringRegex(), text, "1"); + } + + // handle "half day", "half year" + if (!result.getSuccess()) { + result = getResultFromRegex(config.getHalfDateUnitRegex(), text, "0.5"); + } + + // handle single duration unit, it is filtered in the extraction that there is a relative word in advance + if (!result.getSuccess()) { + result = getResultFromRegex(config.getFollowedUnit(), text, "1"); + } + + return result; + } + + private DateTimeResolutionResult getResultFromRegex(Pattern pattern, String text, String numStr) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, text)).findFirst(); + if (match.isPresent()) { + String srcUnit = match.get().getGroup("unit").value.toLowerCase(); + if (config.getUnitMap().containsKey(srcUnit)) { + String unitStr = config.getUnitMap().get(srcUnit); + + String timex = String.format("P%s%s%c", isLessThanDay(unitStr) ? "T" : "", numStr, unitStr.charAt(0)); + double timeValue = Double.parseDouble(numStr) * config.getUnitValueMap().get(srcUnit); + + result.setTimex(timex); + result.setFutureValue(timeValue); + result.setPastValue(timeValue); + + result.setSuccess(true); + } + } + + return result; + } + + private boolean isLessThanDay(String unit) { + return unit.equals("S") || unit.equals("M") || unit.equals("H"); + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java new file mode 100644 index 000000000..568ba9905 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParser.java @@ -0,0 +1,204 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import static java.lang.Integer.parseInt; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.function.IntFunction; +import java.util.regex.Pattern; +import java.util.stream.StreamSupport; + +public class BaseHolidayParser implements IDateTimeParser { + + private final IHolidayParserConfiguration config; + + public BaseHolidayParser(IHolidayParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_DATE; + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceDate = reference; + Object value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult = parseHolidayRegexMatch(er.getText(), referenceDate); + + if (innerResult.getSuccess()) { + HashMap futureResolution = new HashMap<>(); + futureResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getFutureValue())); + innerResult.setFutureResolution(futureResolution); + + HashMap pastResolution = new HashMap<>(); + pastResolution.put(TimeTypeConstants.DATE, DateTimeFormatUtil.formatDate((LocalDateTime)innerResult.getPastValue())); + innerResult.setPastResolution(pastResolution); + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex() + ); + + return ret; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + private DateTimeResolutionResult parseHolidayRegexMatch(String text, LocalDateTime referenceDate) { + + for (Pattern pattern : this.config.getHolidayRegexList()) { + ConditionalMatch match = RegexExtension.matchExact(pattern, text, true); + if (match.getSuccess()) { + // LUIS value string will be set in Match2Date method + DateTimeResolutionResult ret = match2Date(match.getMatch().get(), referenceDate); + + return ret; + } + } + + return new DateTimeResolutionResult(); + } + + private DateTimeResolutionResult match2Date(Match match, LocalDateTime referenceDate) { + + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String holidayStr = this.config.sanitizeHolidayToken(match.getGroup("holiday").value.toLowerCase(Locale.ROOT)); + + // get year (if exist) + String yearStr = match.getGroup("year").value.toLowerCase(); + String orderStr = match.getGroup("order").value.toLowerCase(); + int year; + boolean hasYear = false; + + if (!StringUtility.isNullOrEmpty(yearStr)) { + year = parseInt(yearStr); + hasYear = true; + } else if (!StringUtility.isNullOrEmpty(orderStr)) { + int swift = this.config.getSwiftYear((orderStr)); + if (swift < -1) { + return ret; + } + + year = referenceDate.getYear() + swift; + hasYear = true; + } else { + year = referenceDate.getYear(); + } + + String holidayKey = ""; + for (ImmutableMap.Entry> holidayPair : this.config.getHolidayNames().entrySet()) { + if (StreamSupport.stream(holidayPair.getValue().spliterator(), false).anyMatch(name -> holidayStr.equals(name))) { + holidayKey = holidayPair.getKey(); + break; + } + } + + String timexStr = ""; + if (!StringUtility.isNullOrEmpty(holidayKey)) { + + LocalDateTime value = referenceDate; + IntFunction function = this.config.getHolidayFuncDictionary().get(holidayKey); + if (function != null) { + + value = function.apply(year); + + timexStr = this.config.getVariableHolidaysTimexDictionary().get(holidayKey); + if (StringUtility.isNullOrEmpty(timexStr)) { + timexStr = String.format("-%02d-%02d", value.getMonthValue(), value.getDayOfMonth()); + } + } + + if (function == null) { + return ret; + } + + if (value.equals(DateUtil.minValue())) { + ret.setTimex(""); + ret.setPastValue(DateUtil.minValue()); + ret.setFutureValue(DateUtil.minValue()); + ret.setSuccess(true); + return ret; + } + + if (hasYear) { + ret.setTimex(String.format("%04d", year) + timexStr); + ret.setFutureValue(DateUtil.safeCreateFromMinValue(year, value.getMonthValue(), value.getDayOfMonth())); + ret.setPastValue(DateUtil.safeCreateFromMinValue(year, value.getMonthValue(), value.getDayOfMonth())); + ret.setSuccess(true); + return ret; + } + + ret.setTimex("XXXX" + timexStr); + ret.setFutureValue(getFutureValue(value, referenceDate, holidayKey)); + ret.setPastValue(getPastValue(value, referenceDate, holidayKey)); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private LocalDateTime getFutureValue(LocalDateTime value, LocalDateTime referenceDate, String holiday) { + + if (value.isBefore(referenceDate)) { + IntFunction function = this.config.getHolidayFuncDictionary().get(holiday); + if (function != null) { + return function.apply(value.getYear() + 1); + } + } + + return value; + } + + private LocalDateTime getPastValue(LocalDateTime value, LocalDateTime referenceDate, String holiday) { + + if (value.isAfter(referenceDate) || value == referenceDate) { + IntFunction function = this.config.getHolidayFuncDictionary().get(holiday); + if (function != null) { + return function.apply(value.getYear() - 1); + } + } + + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java new file mode 100644 index 000000000..d8a7cfc1d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseHolidayParserConfiguration.java @@ -0,0 +1,163 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.YearMonth; +import java.util.HashMap; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +public abstract class BaseHolidayParserConfiguration extends BaseOptionsConfiguration implements IHolidayParserConfiguration { + + private ImmutableMap variableHolidaysTimexDictionary; + + public final ImmutableMap getVariableHolidaysTimexDictionary() { + return variableHolidaysTimexDictionary; + } + + protected final void setVariableHolidaysTimexDictionary(ImmutableMap value) { + variableHolidaysTimexDictionary = value; + } + + private ImmutableMap> holidayFuncDictionary; + + public final ImmutableMap> getHolidayFuncDictionary() { + return holidayFuncDictionary; + } + + protected final void setHolidayFuncDictionary(ImmutableMap> value) { + holidayFuncDictionary = value; + } + + private ImmutableMap> holidayNames; + + public final ImmutableMap> getHolidayNames() { + return holidayNames; + } + + protected final void setHolidayNames(ImmutableMap> value) { + holidayNames = value; + } + + private Iterable holidayRegexList; + + public final Iterable getHolidayRegexList() { + return holidayRegexList; + } + + protected final void setHolidayRegexList(Iterable value) { + holidayRegexList = value; + } + + protected BaseHolidayParserConfiguration() { + super(DateTimeOptions.None); + this.variableHolidaysTimexDictionary = BaseDateTime.VariableHolidaysTimexDictionary; + this.setHolidayFuncDictionary(ImmutableMap.copyOf(initHolidayFuncs())); + } + + protected HashMap> initHolidayFuncs() { + HashMap> holidays = new HashMap<>(); + holidays.put("labour", BaseHolidayParserConfiguration::labourDay); + holidays.put("fathers", BaseHolidayParserConfiguration::fathersDay); + holidays.put("mothers", BaseHolidayParserConfiguration::mothersDay); + holidays.put("canberra", BaseHolidayParserConfiguration::canberraDay); + holidays.put("columbus", BaseHolidayParserConfiguration::columbusDay); + holidays.put("memorial", BaseHolidayParserConfiguration::memorialDay); + holidays.put("thanksgiving", BaseHolidayParserConfiguration::thanksgivingDay); + holidays.put("thanksgivingday", BaseHolidayParserConfiguration::thanksgivingDay); + holidays.put("blackfriday", BaseHolidayParserConfiguration::blackFriday); + holidays.put("martinlutherking", BaseHolidayParserConfiguration::martinLutherKingDay); + holidays.put("washingtonsbirthday", BaseHolidayParserConfiguration::washingtonsBirthday); + + return holidays; + } + + public abstract int getSwiftYear(String text); + + public abstract String sanitizeHolidayToken(String holiday); + + // @TODO auto-generate from YAML + private static LocalDateTime canberraDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, getDay(year, 3, 0, DayOfWeek.MONDAY)); + } + + private static LocalDateTime martinLutherKingDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, getDay(year, 1, 2, DayOfWeek.MONDAY)); + } + + private static LocalDateTime washingtonsBirthday(int year) { + return DateUtil.safeCreateFromMinValue(year, 2, getDay(year, 2, 2, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime mothersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, getDay(year, 5, 1, DayOfWeek.SUNDAY)); + } + + protected static LocalDateTime fathersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, getDay(year, 6, 2, DayOfWeek.SUNDAY)); + } + + protected static LocalDateTime memorialDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, getLastDay(year, 5, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime labourDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, getDay(year, 9, 0, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime internationalWorkersDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 5, 1); + } + + protected static LocalDateTime columbusDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, getDay(year, 10, 1, DayOfWeek.MONDAY)); + } + + protected static LocalDateTime thanksgivingDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, getDay(year, 11, 3, DayOfWeek.THURSDAY)); + } + + protected static LocalDateTime blackFriday(int year) { + return DateUtil.safeCreateFromMinValue(year, 11, getDay(year, 11, 3, DayOfWeek.FRIDAY)); + } + + protected static int getDay(int year, int month, int week, DayOfWeek dayOfWeek) { + + YearMonth yearMonthObject = YearMonth.of(year, month); + int daysInMonth = yearMonthObject.lengthOfMonth(); + + int weekCount = 0; + for (int day = 1; day < daysInMonth + 1; day++) { + if (DateUtil.safeCreateFromMinValue(year, month, day).getDayOfWeek() == dayOfWeek) { + weekCount++; + if (weekCount == week + 1) { + return day; + } + } + } + + throw new Error("day out of bound."); + } + + protected static int getLastDay(int year, int month, DayOfWeek dayOfWeek) { + + YearMonth yearMonthObject = YearMonth.of(year, month); + int daysInMonth = yearMonthObject.lengthOfMonth(); + + int lastDay = 0; + for (int day = 1; day < daysInMonth + 1; day++) { + if (DateUtil.safeCreateFromMinValue(year, month, day).getDayOfWeek() == dayOfWeek) { + lastDay = day; + } + } + + return lastDay; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java new file mode 100644 index 000000000..7039f98b1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java @@ -0,0 +1,834 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.DateTimeResolutionKey; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.MatchingUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class BaseMergedDateTimeParser implements IDateTimeParser { + + private final String parserName = "datetimeV2"; + private final IMergedParserConfiguration config; + private static final String dateMinString = DateTimeFormatUtil.formatDate(DateUtil.minValue()); + private static final String dateTimeMinString = DateTimeFormatUtil.formatDateTime(DateUtil.minValue()); + //private static final Calendar Cal = DateTimeFormatInfo.InvariantInfo.Calendar; + + public BaseMergedDateTimeParser(IMergedParserConfiguration config) { + this.config = config; + } + + public String getDateMinString() { + return dateMinString; + } + + public String getDateTimeMinString() { + return dateTimeMinString; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + DateTimeParseResult pr = null; + + String originText = er.getText(); + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + String newText = MatchingUtil.preProcessTextRemoveSuperfluousWords(er.getText(), config.getSuperfluousWordMatcher()).getText(); + int newLength = er.getLength() + er.getText().length() - originText.length(); + er = new ExtractResult(er.getStart(), newLength, newText, er.getType(), er.getData(), er.getMetadata()); + } + + // Push, save the MOD string + boolean hasBefore = false; + boolean hasAfter = false; + boolean hasSince = false; + boolean hasAround = false; + boolean hasYearAfter = false; + + // "InclusiveModifier" means MOD should include the start/end time + // For example, cases like "on or later than", "earlier than or in" have inclusive modifier + boolean hasInclusiveModifier = false; + String modStr = ""; + + if (er.getMetadata() != null && er.getMetadata().getHasMod()) { + ConditionalMatch beforeMatch = RegexExtension.matchBegin(config.getBeforeRegex(), er.getText(), true); + ConditionalMatch afterMatch = RegexExtension.matchBegin(config.getAfterRegex(), er.getText(), true); + ConditionalMatch sinceMatch = RegexExtension.matchBegin(config.getSinceRegex(), er.getText(), true); + ConditionalMatch aroundMatch = RegexExtension.matchBegin(config.getAroundRegex(), er.getText(), true); + + if (beforeMatch.getSuccess()) { + hasBefore = true; + er.setStart(er.getStart() + beforeMatch.getMatch().get().length); + er.setLength(er.getLength() - beforeMatch.getMatch().get().length); + er.setText(er.getText().substring(beforeMatch.getMatch().get().length)); + modStr = beforeMatch.getMatch().get().value; + + if (!StringUtility.isNullOrEmpty(beforeMatch.getMatch().get().getGroup("include").value)) { + hasInclusiveModifier = true; + } + } else if (afterMatch.getSuccess()) { + hasAfter = true; + er.setStart(er.getStart() + afterMatch.getMatch().get().length); + er.setLength(er.getLength() - afterMatch.getMatch().get().length); + er.setText(er.getText().substring(afterMatch.getMatch().get().length)); + modStr = afterMatch.getMatch().get().value; + + if (!StringUtility.isNullOrEmpty(afterMatch.getMatch().get().getGroup("include").value)) { + hasInclusiveModifier = true; + } + } else if (sinceMatch.getSuccess()) { + hasSince = true; + er.setStart(er.getStart() + sinceMatch.getMatch().get().length); + er.setLength(er.getLength() - sinceMatch.getMatch().get().length); + er.setText(er.getText().substring(sinceMatch.getMatch().get().length)); + modStr = sinceMatch.getMatch().get().value; + } else if (aroundMatch.getSuccess()) { + hasAround = true; + er.setStart(er.getStart() + aroundMatch.getMatch().get().length); + er.setLength(er.getLength() - aroundMatch.getMatch().get().length); + er.setText(er.getText().substring(aroundMatch.getMatch().get().length)); + modStr = aroundMatch.getMatch().get().value; + } else if ((er.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) && + Arrays.stream(RegExpUtility.getMatches(config.getYearRegex(), er.getText())).findFirst().isPresent()) || + (er.getType().equals(Constants.SYS_DATETIME_DATE)) || (er.getType().equals(Constants.SYS_DATETIME_TIME))) { + // This has to be put at the end of the if, or cases like "before 2012" and "after 2012" would fall into this + // 2012 or after/above, 3 pm or later + ConditionalMatch match = RegexExtension.matchEnd(config.getSuffixAfterRegex(), er.getText(), true); + if (match.getSuccess()) { + hasYearAfter = true; + er.setLength(er.getLength() - match.getMatch().get().length); + er.setText(er.getLength() > 0 ? er.getText().substring(0, er.getLength()) : ""); + modStr = match.getMatch().get().value; + } + } + } + + if (er.getType().equals(Constants.SYS_DATETIME_DATE)) { + if (er.getMetadata() != null && er.getMetadata().getIsHoliday()) { + pr = config.getHolidayParser().parse(er, reference); + } else { + pr = this.config.getDateParser().parse(er, reference); + } + } else if (er.getType().equals(Constants.SYS_DATETIME_TIME)) { + pr = this.config.getTimeParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + pr = this.config.getDateTimeParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATEPERIOD)) { + pr = this.config.getDatePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + pr = this.config.getTimePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + pr = this.config.getDateTimePeriodParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DURATION)) { + pr = this.config.getDurationParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_SET)) { + pr = this.config.getGetParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_DATETIMEALT)) { + pr = this.config.getDateTimeAltParser().parse(er, reference); + } else if (er.getType().equals(Constants.SYS_DATETIME_TIMEZONE)) { + if (config.getOptions().match(DateTimeOptions.EnablePreview)) { + pr = this.config.getTimeZoneParser().parse(er, reference); + } + } else { + return null; + } + + if (pr == null) { + return null; + } + + // Pop, restore the MOD string + if (hasBefore && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + + if (!hasInclusiveModifier) { + val.setMod(combineMod(val.getMod(), Constants.BEFORE_MOD)); + } else { + val.setMod(combineMod(val.getMod(), Constants.UNTIL_MOD)); + } + + pr.setValue(val); + } + + if (hasAfter && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + + if (!hasInclusiveModifier) { + val.setMod(combineMod(val.getMod(), Constants.AFTER_MOD)); + } else { + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + } + + pr.setValue(val); + } + + if (hasSince && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + } + + if (hasAround && pr != null && pr.getValue() != null) { + + pr.setStart(pr.getStart() - modStr.length()); + pr.setText(modStr + pr.getText()); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.APPROX_MOD)); + pr.setValue(val); + } + + if (hasYearAfter && pr != null && pr.getValue() != null) { + + pr.setText(pr.getText() + modStr); + pr.setLength(pr.getLength() + modStr.length()); + + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + hasSince = true; + } + + // For cases like "3 pm or later on Monday" + if (pr != null && pr.getValue() != null && pr.getType().equals(Constants.SYS_DATETIME_DATETIME)) { + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getSuffixAfterRegex(), pr.getText())).findFirst(); + if (match.isPresent() && match.get().index != 0) { + DateTimeResolutionResult val = (DateTimeResolutionResult)pr.getValue(); + val.setMod(combineMod(val.getMod(), Constants.SINCE_MOD)); + pr.setValue(val); + hasSince = true; + } + } + + if (config.getOptions().match(DateTimeOptions.SplitDateAndTime) && pr != null && pr.getValue() != null && + ((DateTimeResolutionResult)pr.getValue()).getSubDateTimeEntities() != null) { + pr.setValue(dateTimeResolutionForSplit(pr)); + } else { + boolean hasModifier = hasBefore || hasAfter || hasSince; + if (pr.getValue() != null) { + ((DateTimeResolutionResult)pr.getValue()).setHasRangeChangingMod(hasModifier); + } + + pr = setParseResult(pr, hasModifier); + } + + // In this version, ExperimentalMode only cope with the "IncludePeriodEnd" case + if (this.config.getOptions().match(DateTimeOptions.ExperimentalMode)) { + if (pr.getMetadata() != null && pr.getMetadata().getIsPossiblyIncludePeriodEnd()) { + pr = setInclusivePeriodEnd(pr); + } + } + + if (this.config.getOptions().match(DateTimeOptions.EnablePreview)) { + int prLength = pr.getLength() + originText.length() - pr.getText().length(); + pr = new DateTimeParseResult(pr.getStart(), prLength, originText, pr.getType(), pr.getData(), pr.getValue(), pr.getResolutionStr(), pr.getTimexStr()); + } + + return pr; + } + + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + private boolean filterResultsPredicate(DateTimeParseResult pr, Match match) { + return !(match.index < pr.getStart() + pr.getLength() && pr.getStart() < match.index + match.length); + } + + public DateTimeParseResult setParseResult(DateTimeParseResult slot, boolean hasMod) { + SortedMap slotValue = dateTimeResolution(slot); + // Change the type at last for the after or before modes + String type = String.format("%s.%s", parserName, determineDateTimeType(slot.getType(), hasMod)); + + slot.setValue(slotValue); + slot.setType(type); + + return slot; + } + + public DateTimeParseResult setInclusivePeriodEnd(DateTimeParseResult slot) { + String currentType = parserName + "." + Constants.SYS_DATETIME_DATEPERIOD; + if (slot.getType().equals(currentType)) { + Stream timexStream = Arrays.asList(slot.getTimexStr().split(",|\\(|\\)")).stream(); + String[] timexComponents = timexStream.filter(str -> !str.isEmpty()).collect(Collectors.toList()).toArray(new String[0]); + + // Only handle DatePeriod like "(StartDate,EndDate,Duration)" + if (timexComponents.length == 3) { + TreeMap value = (TreeMap)slot.getValue(); + String altTimex = ""; + + if (value != null && value.containsKey(ResolutionKey.ValueSet)) { + if (value.get(ResolutionKey.ValueSet) instanceof List) { + List> valueSet = (List>)value.get(ResolutionKey.ValueSet); + if (!value.isEmpty()) { + + for (HashMap values : valueSet) { + // This is only a sanity check, as here we only handle DatePeriod like "(StartDate,EndDate,Duration)" + if (values.containsKey(DateTimeResolutionKey.START) && + values.containsKey(DateTimeResolutionKey.END) && + values.containsKey(DateTimeResolutionKey.Timex)) { + + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + LocalDateTime startDate = LocalDate.parse(values.get(DateTimeResolutionKey.START), formatter).atStartOfDay(); + LocalDateTime endDate = LocalDate.parse(values.get(DateTimeResolutionKey.END), formatter).atStartOfDay(); + String durationStr = timexComponents[2]; + DatePeriodTimexType datePeriodTimexType = TimexUtility.getDatePeriodTimexType(durationStr); + + endDate = TimexUtility.offsetDateObject(endDate, 1, datePeriodTimexType); + values.put(DateTimeResolutionKey.END, DateTimeFormatUtil.luisDate(endDate)); + values.put(DateTimeResolutionKey.Timex, generateEndInclusiveTimex(slot.getTimexStr(), datePeriodTimexType, startDate, endDate)); + + if (StringUtility.isNullOrEmpty(altTimex)) { + altTimex = values.get(DateTimeResolutionKey.Timex); + } + } + } + } + } + } + + slot.setValue(value); + slot.setTimexStr(altTimex); + } + } + return slot; + } + + public String generateEndInclusiveTimex(String originalTimex, DatePeriodTimexType datePeriodTimexType, LocalDateTime startDate, LocalDateTime endDate) { + String timexEndInclusive = TimexUtility.generateDatePeriodTimex(startDate, endDate, datePeriodTimexType); + + // Sometimes the original timex contains fuzzy part like "XXXX-05-31" + // The fuzzy part needs to stay the same in the new end-inclusive timex + if (originalTimex.contains(Character.toString(Constants.TimexFuzzy)) && originalTimex.length() == timexEndInclusive.length()) { + char[] timexCharSet = new char[timexEndInclusive.length()]; + + for (int i = 0; i < originalTimex.length(); i++) { + if (originalTimex.charAt(i) != Constants.TimexFuzzy) { + timexCharSet[i] = timexEndInclusive.charAt(i); + } else { + timexCharSet[i] = Constants.TimexFuzzy; + } + } + + timexEndInclusive = new String(timexCharSet); + } + + return timexEndInclusive; + } + + public String determineDateTimeType(String type, boolean hasMod) { + if (config.getOptions().match(DateTimeOptions.SplitDateAndTime)) { + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + return Constants.SYS_DATETIME_TIME; + } + } else { + if (hasMod) { + if (type.equals(Constants.SYS_DATETIME_DATE)) { + return Constants.SYS_DATETIME_DATEPERIOD; + } + + if (type.equals(Constants.SYS_DATETIME_TIME)) { + return Constants.SYS_DATETIME_TIMEPERIOD; + } + + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + } + } + + return type; + } + + public String determineSourceEntityType(String sourceType, String newType, boolean hasMod) { + if (!hasMod) { + return null; + } + + if (!newType.equals(sourceType)) { + return Constants.SYS_DATETIME_DATETIMEPOINT; + } + + if (newType.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + return Constants.SYS_DATETIME_DATETIMEPERIOD; + } + + return null; + } + + public List dateTimeResolutionForSplit(DateTimeParseResult slot) { + List results = new ArrayList<>(); + if (((DateTimeResolutionResult)slot.getValue()).getSubDateTimeEntities() != null) { + List subEntities = ((DateTimeResolutionResult)slot.getValue()).getSubDateTimeEntities(); + for (Object subEntity : subEntities) { + DateTimeParseResult result = (DateTimeParseResult)subEntity; + result.setStart(result.getStart() + slot.getStart()); + results.addAll(dateTimeResolutionForSplit(result)); + } + } else { + slot.setValue(dateTimeResolution(slot)); + slot.setType(String.format("%s.%s",parserName, determineDateTimeType(slot.getType(), false))); + + results.add(slot); + } + + return results; + } + + public SortedMap dateTimeResolution(DateTimeParseResult slot) { + if (slot == null) { + return null; + } + + List> resolutions = new ArrayList<>(); + Map res = new HashMap<>(); + + DateTimeResolutionResult val = (DateTimeResolutionResult)slot.getValue(); + if (val == null) { + return null; + } + + boolean islunar = val.getIsLunar() != null ? val.getIsLunar() : false; + String mod = val.getMod(); + + String list = null; + + // Resolve dates list for date periods + if (slot.getType().equals(Constants.SYS_DATETIME_DATEPERIOD) && val.getList() != null) { + list = String.join(",", val.getList().stream().map(o -> DateTimeFormatUtil.luisDate((LocalDateTime)o)).collect(Collectors.toList())); + } + + // With modifier, output Type might not be the same with type in resolution comments + // For example, if the resolution type is "date", with modifier the output type should be "daterange" + String typeOutput = determineDateTimeType(slot.getType(), !StringUtility.isNullOrEmpty(mod)); + String sourceEntity = determineSourceEntityType(slot.getType(), typeOutput, val.getHasRangeChangingMod()); + String comment = val.getComment(); + + String type = slot.getType(); + String timex = slot.getTimexStr(); + + // The following should be added to res first, since ResolveAmPm requires these fields. + addResolutionFields(res, DateTimeResolutionKey.Timex, timex); + addResolutionFields(res, Constants.Comment, comment); + addResolutionFields(res, DateTimeResolutionKey.Mod, mod); + addResolutionFields(res, ResolutionKey.Type, typeOutput); + addResolutionFields(res, DateTimeResolutionKey.IsLunar, islunar ? Boolean.toString(islunar) : ""); + + boolean hasTimeZone = false; + + // For standalone timezone entity recognition, we generate TimeZoneResolution for each entity we extracted. + // We also merge time entity with timezone entity and add the information in TimeZoneResolution to every DateTime resolutions. + if (val.getTimeZoneResolution() != null) { + if (slot.getType().equals(Constants.SYS_DATETIME_TIMEZONE)) { + // single timezone + Map resolutionField = new LinkedHashMap<>(); + resolutionField.put(ResolutionKey.Value, val.getTimeZoneResolution().getValue()); + resolutionField.put(Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + + addResolutionFields(res, Constants.ResolveTimeZone, resolutionField); + } else { + // timezone as clarification of datetime + hasTimeZone = true; + addResolutionFields(res, Constants.TimeZone, val.getTimeZoneResolution().getValue()); + addResolutionFields(res, Constants.TimeZoneText, val.getTimeZoneResolution().getTimeZoneText()); + addResolutionFields(res, Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + } + } + + LinkedHashMap pastResolutionStr = new LinkedHashMap<>(); + if (((DateTimeResolutionResult)slot.getValue()).getPastResolution() != null) { + pastResolutionStr.putAll(((DateTimeResolutionResult)slot.getValue()).getPastResolution()); + } + + Map futureResolutionStr = ((DateTimeResolutionResult)slot.getValue()).getFutureResolution(); + + if (typeOutput.equals(Constants.SYS_DATETIME_DATETIMEALT) && pastResolutionStr.size() > 0) { + typeOutput = determineResolutionDateTimeType(pastResolutionStr); + } + + Map resolutionPast = generateResolution(type, pastResolutionStr, mod); + Map resolutionFuture = generateResolution(type, futureResolutionStr, mod); + + // If past and future are same, keep only one + if (resolutionFuture.equals(resolutionPast)) { + if (resolutionPast.size() > 0) { + addResolutionFields(res, Constants.Resolve, resolutionPast); + } + } else { + if (resolutionPast.size() > 0) { + addResolutionFields(res, Constants.ResolveToPast, resolutionPast); + } + + if (resolutionFuture.size() > 0) { + addResolutionFields(res, Constants.ResolveToFuture, resolutionFuture); + } + } + + // If 'ampm', double our resolution accordingly + if (!StringUtility.isNullOrEmpty(comment) && comment.equals(Constants.Comment_AmPm)) { + if (res.containsKey(Constants.Resolve)) { + resolveAmPm(res, Constants.Resolve); + } else { + resolveAmPm(res, Constants.ResolveToPast); + resolveAmPm(res, Constants.ResolveToFuture); + } + } + + // If WeekOf and in CalendarMode, modify the past part of our resolution + if (config.getOptions().match(DateTimeOptions.CalendarMode) && + !StringUtility.isNullOrEmpty(comment) && comment.equals(Constants.Comment_WeekOf)) { + resolveWeekOf(res, Constants.ResolveToPast); + } + + if (comment != null && !comment.isEmpty() && TimexUtility.hasDoubleTimex(comment)) { + res = TimexUtility.processDoubleTimex(res, Constants.ResolveToFuture, Constants.ResolveToPast, timex); + } + + for (Map.Entry p : res.entrySet()) { + if (p.getValue() instanceof Map) { + Map value = new LinkedHashMap<>(); + + addResolutionFields(value, DateTimeResolutionKey.Timex, timex); + addResolutionFields(value, DateTimeResolutionKey.Mod, mod); + addResolutionFields(value, ResolutionKey.Type, typeOutput); + addResolutionFields(value, DateTimeResolutionKey.IsLunar, islunar ? Boolean.toString(islunar) : ""); + addResolutionFields(value, DateTimeResolutionKey.List, list); + addResolutionFields(value, DateTimeResolutionKey.SourceEntity, sourceEntity); + + if (hasTimeZone) { + addResolutionFields(value, Constants.TimeZone, val.getTimeZoneResolution().getValue()); + addResolutionFields(value, Constants.TimeZoneText, val.getTimeZoneResolution().getTimeZoneText()); + addResolutionFields(value, Constants.UtcOffsetMinsKey, val.getTimeZoneResolution().getUtcOffsetMins().toString()); + } + + for (Map.Entry q : ((Map)p.getValue()).entrySet()) { + value.put(q.getKey(), q.getValue()); + } + + resolutions.add(value); + } + } + + if (resolutionPast.size() == 0 && resolutionFuture.size() == 0 && val.getTimeZoneResolution() == null) { + Map notResolved = new LinkedHashMap<>(); + notResolved.put(DateTimeResolutionKey.Timex, timex); + notResolved.put(ResolutionKey.Type, typeOutput); + notResolved.put(ResolutionKey.Value, "not resolved"); + + resolutions.add(notResolved); + } + + SortedMap result = new TreeMap<>(); + result.put(ResolutionKey.ValueSet, resolutions); + + return result; + } + + private String combineMod(String originalMod, String newMod) { + String combinedMod = newMod; + if (originalMod != null && originalMod != "") { + combinedMod = newMod + "-" + originalMod; + } + return combinedMod; + } + + private String determineResolutionDateTimeType(LinkedHashMap pastResolutionStr) { + switch (pastResolutionStr.keySet().stream().findFirst().get()) { + case TimeTypeConstants.START_DATE: + return Constants.SYS_DATETIME_DATEPERIOD; + case TimeTypeConstants.START_DATETIME: + return Constants.SYS_DATETIME_DATETIMEPERIOD; + case TimeTypeConstants.START_TIME: + return Constants.SYS_DATETIME_TIMEPERIOD; + default: + return pastResolutionStr.keySet().stream().findFirst().get().toLowerCase(); + } + } + + private void addResolutionFields(Map dic, String key, Object value) { + if (value != null) { + dic.put(key, value); + } + } + + private void addResolutionFields(Map dic, String key, String value) { + if (!StringUtility.isNullOrEmpty(value)) { + dic.put(key, value); + } + } + + private void resolveAmPm(Map resolutionDic, String keyName) { + if (resolutionDic.containsKey(keyName)) { + Map resolution = (Map)resolutionDic.get(keyName); + + Map resolutionPm = new LinkedHashMap<>(); + + if (!resolutionDic.containsKey(DateTimeResolutionKey.Timex)) { + return; + } + + String timex = (String)resolutionDic.get(DateTimeResolutionKey.Timex); + timex = timex != null ? timex : ""; + + resolutionDic.remove(keyName); + resolutionDic.put(keyName + "Am", resolution); + + switch ((String)resolutionDic.get(ResolutionKey.Type)) { + case Constants.SYS_DATETIME_TIME: + resolutionPm.put(ResolutionKey.Value, DateTimeFormatUtil.toPm(resolution.get(ResolutionKey.Value))); + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.toPm(timex)); + break; + case Constants.SYS_DATETIME_DATETIME: + String[] splited = resolution.get(ResolutionKey.Value).split(" "); + resolutionPm.put(ResolutionKey.Value, splited[0] + " " + DateTimeFormatUtil.toPm(splited[1])); + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + case Constants.SYS_DATETIME_TIMEPERIOD: + if (resolution.containsKey(DateTimeResolutionKey.START)) { + resolutionPm.put(DateTimeResolutionKey.START, DateTimeFormatUtil.toPm(resolution.get(DateTimeResolutionKey.START))); + } + + if (resolution.containsKey(DateTimeResolutionKey.END)) { + resolutionPm.put(DateTimeResolutionKey.END, DateTimeFormatUtil.toPm(resolution.get(DateTimeResolutionKey.END))); + } + + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + case Constants.SYS_DATETIME_DATETIMEPERIOD: + if (resolution.containsKey(DateTimeResolutionKey.START)) { + LocalDateTime start = LocalDateTime.parse(resolution.get(DateTimeResolutionKey.START), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + start = start.getHour() == Constants.HalfDayHourCount ? start.minusHours(Constants.HalfDayHourCount) : start.plusHours(Constants.HalfDayHourCount); + + resolutionPm.put(DateTimeResolutionKey.START, DateTimeFormatUtil.formatDateTime(start)); + } + + if (resolution.containsKey(DateTimeResolutionKey.END)) { + LocalDateTime end = LocalDateTime.parse(resolution.get(DateTimeResolutionKey.END), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")); + end = end.getHour() == Constants.HalfDayHourCount ? end.minusHours(Constants.HalfDayHourCount) : end.plusHours(Constants.HalfDayHourCount); + + resolutionPm.put(DateTimeResolutionKey.END, DateTimeFormatUtil.formatDateTime(end)); + } + + resolutionPm.put(DateTimeResolutionKey.Timex, DateTimeFormatUtil.allStringToPm(timex)); + break; + default: + break; + } + resolutionDic.put(keyName + "Pm", resolutionPm); + } + } + + private void resolveWeekOf(Map resolutionDic, String keyName) { + if (resolutionDic.containsKey(keyName)) { + Map resolution = (Map)resolutionDic.get(keyName); + + LocalDateTime monday = DateUtil.tryParse(resolution.get(DateTimeResolutionKey.START)); + resolution.put(DateTimeResolutionKey.Timex, TimexUtility.generateWeekTimex(monday)); + + resolutionDic.put(keyName, resolution); + } + } + + private Map generateResolution(String type, Map resolutionDic, String mod) { + Map res = new LinkedHashMap<>(); + + if (type.equals(Constants.SYS_DATETIME_DATETIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_TIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.TIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATE)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATE, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DURATION)) { + if (resolutionDic.containsKey(TimeTypeConstants.DURATION)) { + res.put(ResolutionKey.Value, resolutionDic.get(TimeTypeConstants.DURATION)); + } + } else if (type.equals(Constants.SYS_DATETIME_TIMEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_TIME, TimeTypeConstants.END_TIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATE, TimeTypeConstants.END_DATE, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATETIMEPERIOD)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATETIME, TimeTypeConstants.END_DATETIME, mod, res); + } else if (type.equals(Constants.SYS_DATETIME_DATETIMEALT)) { + // for a period + if (resolutionDic.size() > 2) { + addAltPeriodToResolution(resolutionDic, mod, res); + } else { + // for a datetime point + addAltSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIMEALT, mod, res); + } + } + + return res; + } + + public void addAltPeriodToResolution(Map resolutionDic, String mod, Map res) { + if (resolutionDic.containsKey(TimeTypeConstants.START_DATETIME) && resolutionDic.containsKey(TimeTypeConstants.END_DATETIME)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATETIME, TimeTypeConstants.END_DATETIME, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.START_DATE) && resolutionDic.containsKey(TimeTypeConstants.END_DATE)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_DATE, TimeTypeConstants.END_DATE, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.START_TIME) && resolutionDic.containsKey(TimeTypeConstants.END_TIME)) { + addPeriodToResolution(resolutionDic, TimeTypeConstants.START_TIME, TimeTypeConstants.END_TIME, mod, res); + } + } + + public void addAltSingleDateTimeToResolution(Map resolutionDic, String type, String mod, Map res) { + if (resolutionDic.containsKey(TimeTypeConstants.DATE)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATE, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.DATETIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.DATETIME, mod, res); + } else if (resolutionDic.containsKey(TimeTypeConstants.TIME)) { + addSingleDateTimeToResolution(resolutionDic, TimeTypeConstants.TIME, mod, res); + } + } + + public void addSingleDateTimeToResolution(Map resolutionDic, String type, String mod, Map res) { + // If an "invalid" Date or DateTime is extracted, it should not have an assigned resolution. + // Only valid entities should pass this condition. + if (resolutionDic.containsKey(type) && !resolutionDic.get(type).startsWith(dateMinString)) { + if (!StringUtility.isNullOrEmpty(mod)) { + if (mod.equals(Constants.BEFORE_MOD)) { + res.put(DateTimeResolutionKey.END, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.AFTER_MOD)) { + res.put(DateTimeResolutionKey.START, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.SINCE_MOD)) { + res.put(DateTimeResolutionKey.START, resolutionDic.get(type)); + return; + } + + if (mod.equals(Constants.UNTIL_MOD)) { + res.put(DateTimeResolutionKey.END, resolutionDic.get(type)); + return; + } + } + + res.put(ResolutionKey.Value, resolutionDic.get(type)); + } + } + + public void addPeriodToResolution(Map resolutionDic, String startType, String endType, String mod, Map res) { + String start = ""; + String end = ""; + + if (resolutionDic.containsKey(startType)) { + if (resolutionDic.get(startType).startsWith(dateMinString)) { + return; + } + start = resolutionDic.get(startType); + } + + if (resolutionDic.containsKey(endType)) { + if (resolutionDic.get(endType).startsWith(dateMinString)) { + return; + } + end = resolutionDic.get(endType); + } + + if (!StringUtility.isNullOrEmpty(mod)) { + // For the 'before' mod + // 1. Cases like "Before December", the start of the period should be the end of the new period, not the start + // 2. Cases like "More than 3 days before today", the date point should be the end of the new period + if (mod.startsWith(Constants.BEFORE_MOD)) { + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end) && !mod.endsWith(Constants.LATE_MOD)) { + res.put(DateTimeResolutionKey.END, start); + } else { + res.put(DateTimeResolutionKey.END, end); + } + + return; + } + + // For the 'after' mod + // 1. Cases like "After January", the end of the period should be the start of the new period, not the end + // 2. Cases like "More than 3 days after today", the date point should be the start of the new period + if (mod.startsWith(Constants.AFTER_MOD)) { + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end) && !mod.endsWith(Constants.EARLY_MOD)) { + res.put(DateTimeResolutionKey.START, end); + } else { + res.put(DateTimeResolutionKey.START, start); + } + + return; + } + + // For the 'since' mod, the start of the period should be the start of the new period, not the end + if (mod.equals(Constants.SINCE_MOD)) { + res.put(DateTimeResolutionKey.START, start); + return; + } + + // For the 'until' mod, the end of the period should be the end of the new period, not the start + if (mod.equals(Constants.UNTIL_MOD)) { + res.put(DateTimeResolutionKey.END, end); + return; + } + } + + if (!StringUtility.isNullOrEmpty(start) && !StringUtility.isNullOrEmpty(end)) { + res.put(DateTimeResolutionKey.START, start); + res.put(DateTimeResolutionKey.END, end); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java new file mode 100644 index 000000000..220773230 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseSetParser.java @@ -0,0 +1,251 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Optional; +import java.util.regex.Matcher; + +public class BaseSetParser implements IDateTimeParser { + @Override + public String getParserName() { + return Constants.SYS_DATETIME_SET; + } + + private ISetParserConfiguration config; + + public BaseSetParser(ISetParserConfiguration configuration) { + this.config = configuration; + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeResolutionResult value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult = parseEachUnit(er.getText()); + if (!innerResult.getSuccess()) { + innerResult = parseEachDuration(er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parserTimeEveryday(er.getText(), reference); + } + + // NOTE: Please do not change the order of following function + // datetimeperiod>dateperiod>timeperiod>datetime>date>time + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateTimePeriodExtractor(), config.getDateTimePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDatePeriodExtractor(), config.getDatePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getTimePeriodExtractor(), config.getTimePeriodParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateTimeExtractor(), config.getDateTimeParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getDateExtractor(), config.getDateParser(), er.getText(), reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseEach(config.getTimeExtractor(), config.getTimeParser(), er.getText(), reference); + } + + if (innerResult.getSuccess()) { + HashMap futureMap = new HashMap<>(); + futureMap.put(TimeTypeConstants.SET, innerResult.getFutureValue().toString()); + innerResult.setFutureResolution(futureMap); + + HashMap pastMap = new HashMap<>(); + pastMap.put(TimeTypeConstants.SET, innerResult.getPastValue().toString()); + innerResult.setPastResolution(pastMap); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : value.getTimex() + ); + + return ret; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public List filterResults(String query, List candidateResults) { + throw new UnsupportedOperationException(); + } + + private DateTimeResolutionResult parseEachDuration(String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = this.config.getDurationExtractor().extract(text, refDate); + if (ers.size() != 1 || !StringUtility.isNullOrWhiteSpace(text.substring(ers.get(0).getStart() + ers.get(0).getLength()))) { + return ret; + } + + String beforeStr = text.substring(0, ers.get(0).getStart()); + Matcher regexMatch = this.config.getEachPrefixRegex().matcher(beforeStr); + if (regexMatch.find()) { + DateTimeParseResult pr = this.config.getDurationParser().parse(ers.get(0), LocalDateTime.now()); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + pr.getTimexStr()); + ret.setPastValue("Set: " + pr.getTimexStr()); + ret.setSuccess(true); + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseEachUnit(String text) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + // handle "daily", "weekly" + Optional matched = Arrays.stream(RegExpUtility.getMatches(this.config.getPeriodicRegex(), text)).findFirst(); + if (matched.isPresent()) { + + MatchedTimexResult result = this.config.getMatchedDailyTimex(text); + if (!result.getResult()) { + return ret; + } + + ret.setTimex(result.getTimex()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + // Handle "each month" + ConditionalMatch exactMatch = RegexExtension.matchExact(this.config.getEachUnitRegex(), text, true); + if (exactMatch.getSuccess()) { + + String sourceUnit = exactMatch.getMatch().get().getGroup("unit").value; + if (!StringUtility.isNullOrEmpty(sourceUnit) && this.config.getUnitMap().containsKey(sourceUnit)) { + + MatchedTimexResult result = this.config.getMatchedUnitTimex(sourceUnit); + if (!result.getResult()) { + return ret; + } + + // Handle "every other month" + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getEachUnitRegex(), text)).findFirst(); + + if (exactMatch.getMatch().get().getGroup("other").value != "") { + result.setTimex(result.getTimex().replace("1", "2")); + } + + ret.setTimex(result.getTimex()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + } + + return ret; + } + + private DateTimeResolutionResult parserTimeEveryday(String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = this.config.getTimeExtractor().extract(text, refDate); + if (ers.size() != 1) { + return ret; + } + + String afterStr = text.replace(ers.get(0).getText(), ""); + Matcher match = this.config.getEachDayRegex().matcher(afterStr); + if (match.find()) { + DateTimeParseResult pr = this.config.getTimeParser().parse(ers.get(0), LocalDateTime.now()); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + + private DateTimeResolutionResult parseEach(IDateTimeExtractor extractor, IDateTimeParser parser, String text, LocalDateTime refDate) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List ers = null; + + // remove key words of set type from text + boolean success = false; + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getSetEachRegex(), text)).findFirst(); + if (match.isPresent()) { + + StringBuilder sb = new StringBuilder(text); + String trimmedText = sb.delete(match.get().index, match.get().index + match.get().length).toString(); + ers = extractor.extract(trimmedText, refDate); + if (ers.size() == 1 && ers.get(0).getLength() == trimmedText.length()) { + + success = true; + } + } + + // remove suffix 's' and "on" if existed and re-try + match = Arrays.stream(RegExpUtility.getMatches(this.config.getSetWeekDayRegex(), text)).findFirst(); + if (match.isPresent()) { + + StringBuilder sb = new StringBuilder(text); + String trimmedText = sb.delete(match.get().index, match.get().index + match.get().length).toString(); + trimmedText = new StringBuilder(trimmedText).insert(match.get().index, match.get().getGroup("weekday").value).toString(); + ers = extractor.extract(trimmedText, refDate); + if (ers.size() == 1 && ers.get(0).getLength() == trimmedText.length()) { + + success = true; + } + } + + if (success) { + DateTimeParseResult pr = parser.parse(ers.get(0), refDate); + ret.setTimex(pr.getTimexStr()); + ret.setFutureValue("Set: " + ret.getTimex()); + ret.setPastValue("Set: " + ret.getTimex()); + ret.setSuccess(true); + + return ret; + } + + return ret; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java new file mode 100644 index 000000000..ce61d453f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeParser.java @@ -0,0 +1,358 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Pattern; + +public class BaseTimeParser implements IDateTimeParser { + + private final ITimeParserConfiguration config; + + public BaseTimeParser(ITimeParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_TIME; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + + LocalDateTime referenceTime = reference; + + Object value = null; + + if (er.getType().equals(getParserName())) { + DateTimeResolutionResult innerResult; + + // Resolve timezome + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (Map)er.getData(); + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + + innerResult = internalParse(er.getText().substring(0, er.getText().length() - timezoneEr.getLength()), referenceTime); + + if (timezonePr.getValue() != null) { + TimeZoneResolutionResult timeZoneResolution = ((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution(); + innerResult.setTimeZoneResolution(timeZoneResolution); + } + + } else { + innerResult = internalParse(er.getText(), referenceTime); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put(TimeTypeConstants.TIME, DateTimeFormatUtil.formatTime((LocalDateTime)innerResult.getFutureValue())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put(TimeTypeConstants.TIME, DateTimeFormatUtil.formatTime((LocalDateTime)innerResult.getPastValue())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + protected DateTimeResolutionResult internalParse(String text, LocalDateTime referenceTime) { + + DateTimeResolutionResult innerResult = parseBasicRegexMatch(text, referenceTime); + return innerResult; + } + + // parse basic patterns in TimeRegexList + private DateTimeResolutionResult parseBasicRegexMatch(String text, LocalDateTime referenceTime) { + + String trimmedText = text.trim().toLowerCase(); + int offset = 0; + Optional match = Arrays.stream(RegExpUtility.getMatches(config.getAtRegex(), trimmedText)).findFirst(); + if (!match.isPresent()) { + match = Arrays.stream(RegExpUtility.getMatches(config.getAtRegex(), config.getTimeTokenPrefix() + trimmedText)).findFirst(); + offset = config.getTimeTokenPrefix().length(); + } + + if (match.isPresent() && match.get().index == offset && match.get().length == trimmedText.length()) { + return match2Time(match.get(), referenceTime); + } + + // parse hour pattern, like "twenty one", "16" + // create a extract comments which content the pass-in text + Integer hour = null; + if (config.getNumbers().containsKey(text)) { + hour = config.getNumbers().get(text); + } else { + try { + hour = Integer.parseInt(text); + } catch (Exception ignored) { + hour = null; + } + } + + if (hour != null && hour >= 0 && hour <= 24) { + DateTimeResolutionResult result = new DateTimeResolutionResult(); + + if (hour == 24) { + hour = 0; + } + + if (hour <= Constants.HalfDayHourCount && hour != 0) { + result.setComment(Constants.Comment_AmPm); + } + + result.setTimex(String.format("T%02d", hour)); + LocalDateTime value = DateUtil.safeCreateFromMinValue(referenceTime.getYear(), referenceTime.getMonthValue(), referenceTime.getDayOfMonth(), hour, 0, 0); + result.setFutureValue(value); + result.setPastValue(value); + result.setSuccess(true); + return result; + } + + for (Pattern regex : config.getTimeRegexes()) { + ConditionalMatch exactMatch = RegexExtension.matchExact(regex, trimmedText, true); + + if (exactMatch.getSuccess()) { + return match2Time(exactMatch.getMatch().get(), referenceTime); + } + } + + return new DateTimeResolutionResult(); + } + + private DateTimeResolutionResult match2Time(Match match, LocalDateTime referenceTime) { + + DateTimeResolutionResult result = new DateTimeResolutionResult(); + boolean hasMin = false; + boolean hasSec = false; + boolean hasAm = false; + boolean hasPm = false; + boolean hasMid = false; + int hour = 0; + int minute = 0; + int second = 0; + int day = referenceTime.getDayOfMonth(); + int month = referenceTime.getMonthValue(); + int year = referenceTime.getYear(); + + String writtenTimeStr = match.getGroup("writtentime").value; + + if (!StringUtility.isNullOrEmpty(writtenTimeStr)) { + // get hour + String hourStr = match.getGroup("hournum").value.toLowerCase(); + hour = config.getNumbers().get(hourStr); + + // get minute + String minStr = match.getGroup("minnum").value.toLowerCase(); + String tensStr = match.getGroup("tens").value.toLowerCase(); + + if (!StringUtility.isNullOrEmpty(minStr)) { + minute = config.getNumbers().get(minStr); + if (!StringUtility.isNullOrEmpty(tensStr)) { + minute += config.getNumbers().get(tensStr); + } + hasMin = true; + } + } else if (!StringUtility.isNullOrEmpty(match.getGroup("mid").value)) { + hasMid = true; + if (!StringUtility.isNullOrEmpty(match.getGroup("midnight").value)) { + hour = 0; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midmorning").value)) { + hour = 10; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midafternoon").value)) { + hour = 14; + minute = 0; + second = 0; + } else if (!StringUtility.isNullOrEmpty(match.getGroup("midday").value)) { + hour = Constants.HalfDayHourCount; + minute = 0; + second = 0; + } + } else { + // get hour + String hourStr = match.getGroup(Constants.HourGroupName).value; + if (StringUtility.isNullOrEmpty(hourStr)) { + hourStr = match.getGroup("hournum").value.toLowerCase(); + if (!config.getNumbers().containsKey(hourStr)) { + return result; + } + + hour = config.getNumbers().get(hourStr); + } else { + if (!IntegerUtility.canParse(hourStr)) { + if (!config.getNumbers().containsKey(hourStr.toLowerCase())) { + return result; + } + + hour = config.getNumbers().get(hourStr.toLowerCase()); + } else { + hour = Integer.parseInt(hourStr); + } + } + + // get minute + String minStr = match.getGroup(Constants.MinuteGroupName).value.toLowerCase(); + if (StringUtility.isNullOrEmpty(minStr)) { + minStr = match.getGroup("minnum").value; + if (!StringUtility.isNullOrEmpty(minStr)) { + minute = config.getNumbers().get(minStr); + hasMin = true; + } + + String tensStr = match.getGroup("tens").value; + if (!StringUtility.isNullOrEmpty(tensStr)) { + minute += config.getNumbers().get(tensStr); + hasMin = true; + } + } else { + minute = Integer.parseInt(minStr); + hasMin = true; + } + + // get second + String secStr = match.getGroup(Constants.SecondGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(secStr)) { + second = Integer.parseInt(secStr); + hasSec = true; + } + } + + // Adjust by desc string + String descStr = match.getGroup(Constants.DescGroupName).value.toLowerCase(); + + // ampm is a special case in which at 6ampm = at 6 + if (isAmDesc(descStr, match)) { + if (hour >= Constants.HalfDayHourCount) { + hour -= Constants.HalfDayHourCount; + } + + if (!checkRegex(config.getUtilityConfiguration().getAmPmDescRegex(), descStr)) { + hasAm = true; + } + + } else if (isPmDesc(descStr, match)) { + if (hour < Constants.HalfDayHourCount) { + hour += Constants.HalfDayHourCount; + } + + hasPm = true; + } + + // adjust min by prefix + String timePrefix = match.getGroup(Constants.PrefixGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(timePrefix)) { + PrefixAdjustResult prefixResult = config.adjustByPrefix(timePrefix, hour, minute, hasMin); + hour = prefixResult.hour; + minute = prefixResult.minute; + hasMin = prefixResult.hasMin; + } + + // adjust hour by suffix + String timeSuffix = match.getGroup(Constants.SuffixGroupName).value.toLowerCase(); + if (!StringUtility.isNullOrEmpty(timeSuffix)) { + SuffixAdjustResult suffixResult = config.adjustBySuffix(timeSuffix, hour, minute, hasMin, hasAm, hasPm); + hour = suffixResult.hour; + minute = suffixResult.minute; + hasMin = suffixResult.hasMin; + hasAm = suffixResult.hasAm; + hasPm = suffixResult.hasPm; + } + + if (hour == 24) { + hour = 0; + } + + StringBuilder timex = new StringBuilder(String.format("T%02d", hour)); + + if (hasMin) { + timex.append(String.format(":%02d", minute)); + } + + if (hasSec) { + timex.append(String.format(":%02d", second)); + } + + result.setTimex(timex.toString()); + + if (hour <= Constants.HalfDayHourCount && !hasPm && !hasAm && !hasMid) { + result.setComment(Constants.Comment_AmPm); + } + + LocalDateTime resultTime = DateUtil.safeCreateFromMinValue(year, month, day, hour, minute, second); + result.setFutureValue(resultTime); + result.setPastValue(resultTime); + + result.setSuccess(true); + + return result; + } + + private boolean isAmDesc(String descStr, Match match) { + return checkRegex(config.getUtilityConfiguration().getAmDescRegex(), descStr) || + checkRegex(config.getUtilityConfiguration().getAmPmDescRegex(), descStr) || + !StringUtility.isNullOrEmpty(match.getGroup(Constants.ImplicitAmGroupName).value); + } + + private boolean isPmDesc(String descStr, Match match) { + return checkRegex(config.getUtilityConfiguration().getPmDescRegex(), descStr) || + !StringUtility.isNullOrEmpty(match.getGroup(Constants.ImplicitPmGroupName).value); + } + + private boolean checkRegex(Pattern regex, String input) { + Optional result = Arrays.stream(RegExpUtility.getMatches(regex, input)).findFirst(); + return result.isPresent(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java new file mode 100644 index 000000000..c0b3fffe6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimePeriodParser.java @@ -0,0 +1,697 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.TimeTypeConstants; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneUtility; +import com.microsoft.recognizers.text.utilities.Capture; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import org.javatuples.Pair; + +public class BaseTimePeriodParser implements IDateTimeParser { + + private final ITimePeriodParserConfiguration config; + + private static final String parserName = Constants.SYS_DATETIME_TIMEPERIOD; //"TimePeriod"; + + public BaseTimePeriodParser(ITimePeriodParserConfiguration config) { + this.config = config; + } + + @Override + public String getParserName() { + return parserName; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + Object value = null; + + if (er.getType().equals(getParserName())) { + + DateTimeResolutionResult innerResult; + + if (TimeZoneUtility.shouldResolveTimeZone(er, config.getOptions())) { + Map metadata = (HashMap)er.getData(); + + ExtractResult timezoneEr = (ExtractResult)metadata.get(Constants.SYS_DATETIME_TIMEZONE); + ParseResult timezonePr = config.getTimeZoneParser().parse(timezoneEr); + + innerResult = internalParse(er.getText().substring(0, er.getLength() - timezoneEr.getLength()), reference); + + if (timezonePr.getValue() != null) { + innerResult.setTimeZoneResolution(((DateTimeResolutionResult)timezonePr.getValue()).getTimeZoneResolution()); + } + } else { + innerResult = internalParse(er.getText(), reference); + } + + if (innerResult.getSuccess()) { + ImmutableMap.Builder futureResolution = ImmutableMap.builder(); + futureResolution.put( + TimeTypeConstants.START_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getFutureValue()).getValue0())); + futureResolution.put( + TimeTypeConstants.END_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getFutureValue()).getValue1())); + + innerResult.setFutureResolution(futureResolution.build()); + + ImmutableMap.Builder pastResolution = ImmutableMap.builder(); + pastResolution.put( + TimeTypeConstants.START_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getPastValue()).getValue0())); + pastResolution.put( + TimeTypeConstants.END_TIME, + DateTimeFormatUtil.formatTime(((Pair)innerResult.getPastValue()).getValue1())); + + innerResult.setPastResolution(pastResolution.build()); + + value = innerResult; + } + } + + DateTimeParseResult ret = new DateTimeParseResult( + er.getStart(), + er.getLength(), + er.getText(), + er.getType(), + er.getData(), + value, + "", + value == null ? "" : ((DateTimeResolutionResult)value).getTimex()); + + return ret; + } + + private DateTimeResolutionResult internalParse(String text, LocalDateTime reference) { + DateTimeResolutionResult innerResult = parseSimpleCases(text, reference); + + if (!innerResult.getSuccess()) { + innerResult = mergeTwoTimePoints(text, reference); + } + + if (!innerResult.getSuccess()) { + innerResult = parseTimeOfDay(text, reference); + } + + return innerResult; + } + + // Cases like "from 3 to 5am" or "between 3:30 and 5" are parsed here + private DateTimeResolutionResult parseSimpleCases(String text, LocalDateTime referenceTime) { + // Cases like "from 3 to 5pm" or "between 4 and 6am", time point is pure number without colon + DateTimeResolutionResult ret = parsePureNumCases(text, referenceTime); + + if (!ret.getSuccess()) { + // Cases like "from 3:30 to 5" or "netween 3:30am to 6pm", at least one of the time point contains colon + ret = parseSpecificTimeCases(text, referenceTime); + } + + return ret; + } + + // Cases like "from 3 to 5pm" or "between 4 and 6am", time point is pure number without colon + private DateTimeResolutionResult parsePureNumCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + int year = referenceTime.getYear(); + int month = referenceTime.getMonthValue(); + int day = referenceTime.getDayOfMonth(); + String trimmedText = text.trim().toLowerCase(); + + ConditionalMatch match = RegexExtension.matchBegin(this.config.getPureNumberFromToRegex(), trimmedText, true); + + if (!match.getSuccess()) { + match = RegexExtension.matchBegin(this.config.getPureNumberBetweenAndRegex(), trimmedText, true); + } + + if (match.getSuccess()) { + // this "from .. to .." pattern is valid if followed by a Date OR Constants.PmGroupName + boolean isValid = false; + + // get hours + MatchGroup hourGroup = match.getMatch().get().getGroup(Constants.HourGroupName); + String hourStr = hourGroup.captures[0].value; + int afterHourIndex = hourGroup.captures[0].index + hourGroup.captures[0].length; + + // hard to integrate this part into the regex + if (afterHourIndex == trimmedText.length() || !trimmedText.substring(afterHourIndex).trim().startsWith(":")) { + + int beginHour; + if (!this.config.getNumbers().containsKey(hourStr)) { + beginHour = Integer.parseInt(hourStr); + } else { + beginHour = this.config.getNumbers().get(hourStr); + } + + hourStr = hourGroup.captures[1].value; + afterHourIndex = hourGroup.captures[1].index + hourGroup.captures[1].length; + + if (afterHourIndex == trimmedText.length() || !trimmedText.substring(afterHourIndex).trim().startsWith(":")) { + int endHour; + if (!this.config.getNumbers().containsKey(hourStr)) { + endHour = Integer.parseInt(hourStr); + } else { + endHour = this.config.getNumbers().get(hourStr); + } + + // parse Constants.PmGroupName + String leftDesc = match.getMatch().get().getGroup("leftDesc").value; + String rightDesc = match.getMatch().get().getGroup("rightDesc").value; + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + String descStr = match.getMatch().get().getGroup(Constants.DescGroupName).value; + + // The "ampm" only occurs in time, we don't have to consider it here + if (StringUtility.isNullOrEmpty(leftDesc)) { + + boolean rightAmValid = !StringUtility.isNullOrEmpty(rightDesc) && + Arrays.stream(RegExpUtility.getMatches(config.getUtilityConfiguration().getAmDescRegex(), rightDesc.toLowerCase())).findFirst().isPresent(); + boolean rightPmValid = !StringUtility.isNullOrEmpty(rightDesc) && + Arrays.stream(RegExpUtility.getMatches(config.getUtilityConfiguration().getPmDescRegex(), rightDesc.toLowerCase())).findFirst().isPresent(); + + if (!StringUtility.isNullOrEmpty(amStr) || rightAmValid) { + if (endHour >= Constants.HalfDayHourCount) { + endHour -= Constants.HalfDayHourCount; + } + + if (beginHour >= Constants.HalfDayHourCount && beginHour - Constants.HalfDayHourCount < endHour) { + beginHour -= Constants.HalfDayHourCount; + } + + // Resolve case like "11 to 3am" + if (beginHour < Constants.HalfDayHourCount && beginHour > endHour) { + beginHour += Constants.HalfDayHourCount; + } + + isValid = true; + + } else if (!StringUtility.isNullOrEmpty(pmStr) || rightPmValid) { + + if (endHour < Constants.HalfDayHourCount) { + endHour += Constants.HalfDayHourCount; + } + + // Resolve case like "11 to 3pm" + if (beginHour + Constants.HalfDayHourCount < endHour) { + beginHour += Constants.HalfDayHourCount; + } + + isValid = true; + + } + } + + if (isValid) { + String beginStr = String.format("T%02d", beginHour); + String endStr = String.format("T%02d", endHour); + + if (endHour >= beginHour) { + ret.setTimex(String.format("(%s,%s,PT%sH)", beginStr, endStr, (endHour - beginHour))); + } else { + ret.setTimex(String.format("(%s,%s,PT%sH)", beginStr, endStr, (endHour - beginHour + 24))); + } + + // Try to get the timezone resolution + List timeErs = config.getTimeExtractor().extract(trimmedText); + for (ExtractResult er : timeErs) { + DateTimeParseResult pr = config.getTimeParser().parse(er, referenceTime); + if (((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr.getValue()).getTimeZoneResolution()); + break; + } + } + + ret.setFutureValue( + new Pair(DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, 0, 0))); + ret.setPastValue( + new Pair(DateUtil.safeCreateFromMinValue(year, month, day, beginHour, 0, 0), + DateUtil.safeCreateFromMinValue(year, month, day, endHour, 0, 0))); + + ret.setSuccess(true); + } + } + } + } + + return ret; + } + + // Cases like "from 3:30 to 5" or "between 3:30am to 6pm", at least one of the time point contains colon + private DateTimeResolutionResult parseSpecificTimeCases(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + int year = referenceTime.getYear(); + int month = referenceTime.getMonthValue(); + int day = referenceTime.getDayOfMonth(); + + // Handle cases like "from 4:30 to 5" + ConditionalMatch match = RegexExtension.matchExact(config.getSpecificTimeFromToRegex(), text, true); + + if (!match.getSuccess()) { + // Handle cases like "between 5:10 and 7" + match = RegexExtension.matchExact(config.getSpecificTimeBetweenAndRegex(), text, true); + } + + if (match.getSuccess()) { + // Cases like "half past seven" are not handled here + if (!match.getMatch().get().getGroup(Constants.PrefixGroupName).value.equals("")) { + return ret; + } + + // Cases like "4" is different with "4:00" as the Timex is different "T04H" vs "T04H00M" + // Uses this invalidFlag to differentiate + int beginHour; + int beginMinute = Constants.InvalidMinute; + int beginSecond = Constants.InvalidSecond; + int endHour; + int endMinute = Constants.InvalidMinute; + int endSecond = Constants.InvalidSecond; + + // Get time1 and time2 + MatchGroup hourGroup = match.getMatch().get().getGroup(Constants.HourGroupName); + + String hourStr = hourGroup.captures[0].value; + + if (config.getNumbers().containsKey(hourStr)) { + beginHour = config.getNumbers().get(hourStr); + } else { + beginHour = Integer.parseInt(hourStr); + } + + + hourStr = hourGroup.captures[1].value; + + if (config.getNumbers().containsKey(hourStr)) { + endHour = config.getNumbers().get(hourStr); + } else { + endHour = Integer.parseInt(hourStr); + } + + int time1StartIndex = match.getMatch().get().getGroup("time1").index; + int time1EndIndex = time1StartIndex + match.getMatch().get().getGroup("time1").length; + int time2StartIndex = match.getMatch().get().getGroup("time2").index; + int time2EndIndex = time2StartIndex + match.getMatch().get().getGroup("time2").length; + + // Get beginMinute (if exists) and endMinute (if exists) + for (int i = 0; i < match.getMatch().get().getGroup(Constants.MinuteGroupName).captures.length; i++) { + Capture minuteCapture = match.getMatch().get().getGroup(Constants.MinuteGroupName).captures[i]; + if (minuteCapture.index >= time1StartIndex && (minuteCapture.index + minuteCapture.length) <= time1EndIndex) { + beginMinute = Integer.parseInt(minuteCapture.value); + } else if (minuteCapture.index >= time2StartIndex && (minuteCapture.index + minuteCapture.length) <= time2EndIndex) { + endMinute = Integer.parseInt(minuteCapture.value); + } + } + + // Get beginSecond (if exists) and endSecond (if exists) + for (int i = 0; i < match.getMatch().get().getGroup(Constants.SecondGroupName).captures.length; i++) { + Capture secondCapture = match.getMatch().get().getGroup(Constants.SecondGroupName).captures[i]; + if (secondCapture.index >= time1StartIndex && (secondCapture.index + secondCapture.length) <= time1EndIndex) { + beginSecond = Integer.parseInt(secondCapture.value); + } else if (secondCapture.index >= time2StartIndex && (secondCapture.index + secondCapture.length) <= time2EndIndex) { + endSecond = Integer.parseInt(secondCapture.value); + } + } + + // Desc here means descriptions like "am / pm / o'clock" + // Get leftDesc (if exists) and rightDesc (if exists) + String leftDesc = match.getMatch().get().getGroup("leftDesc").value; + String rightDesc = match.getMatch().get().getGroup("rightDesc").value; + + for (int i = 0; i < match.getMatch().get().getGroup(Constants.DescGroupName).captures.length; i++) { + Capture descCapture = match.getMatch().get().getGroup(Constants.DescGroupName).captures[i]; + if (descCapture.index >= time1StartIndex && (descCapture.index + descCapture.length) <= time1EndIndex && StringUtility.isNullOrEmpty(leftDesc)) { + leftDesc = descCapture.value; + } else if (descCapture.index >= time2StartIndex && (descCapture.index + descCapture.length) <= time2EndIndex && StringUtility.isNullOrEmpty(rightDesc)) { + rightDesc = descCapture.value; + } + } + + LocalDateTime beginDateTime = DateUtil.safeCreateFromMinValue( + year, + month, + day, + beginHour, + beginMinute >= 0 ? beginMinute : 0, + beginSecond >= 0 ? beginSecond : 0); + + LocalDateTime endDateTime = DateUtil.safeCreateFromMinValue( + year, + month, + day, + endHour, + endMinute >= 0 ? endMinute : 0, + endSecond >= 0 ? endSecond : 0); + + boolean hasLeftAm = !StringUtility.isNullOrEmpty(leftDesc) && leftDesc.toLowerCase().startsWith("a"); + boolean hasLeftPm = !StringUtility.isNullOrEmpty(leftDesc) && leftDesc.toLowerCase().startsWith("p"); + boolean hasRightAm = !StringUtility.isNullOrEmpty(rightDesc) && rightDesc.toLowerCase().startsWith("a"); + boolean hasRightPm = !StringUtility.isNullOrEmpty(rightDesc) && rightDesc.toLowerCase().startsWith("p"); + boolean hasLeft = hasLeftAm || hasLeftPm; + boolean hasRight = hasRightAm || hasRightPm; + + // Both timepoint has description like 'am' or 'pm' + if (hasLeft && hasRight) { + if (hasLeftAm) { + if (beginHour >= Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + } else if (hasLeftPm) { + if (beginHour < Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + } + + if (hasRightAm) { + if (endHour > Constants.HalfDayHourCount) { + endDateTime = endDateTime.minusHours(Constants.HalfDayHourCount); + } + } else if (hasRightPm) { + if (endHour < Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } else if (hasLeft || hasRight) { // one of the timepoint has description like 'am' or 'pm' + if (hasLeftAm) { + if (beginHour >= Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + + if (endHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } else if (hasLeftPm) { + if (beginHour < Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + + if (endHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + Duration span = Duration.between(endDateTime, beginDateTime).abs(); + if (span.toHours() >= Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(24); + } else { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } + } + + if (hasRightAm) { + if (endHour >= Constants.HalfDayHourCount) { + endDateTime = endDateTime.minusHours(Constants.HalfDayHourCount); + } + + if (beginHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } + } + } else if (hasRightPm) { + if (endHour < Constants.HalfDayHourCount) { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + + if (beginHour < Constants.HalfDayHourCount) { + if (endDateTime.isBefore(beginDateTime)) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } else { + Duration span = Duration.between(beginDateTime, endDateTime); + if (span.toHours() > Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.plusHours(Constants.HalfDayHourCount); + } + } + } + } + } else if (beginHour <= Constants.HalfDayHourCount && endHour <= Constants.HalfDayHourCount) { + // No 'am' or 'pm' indicator + if (beginHour > endHour) { + if (beginHour == Constants.HalfDayHourCount) { + beginDateTime = beginDateTime.minusHours(Constants.HalfDayHourCount); + } else { + endDateTime = endDateTime.plusHours(Constants.HalfDayHourCount); + } + } + ret.setComment(Constants.Comment_AmPm); + } + + if (endDateTime.isBefore(beginDateTime)) { + endDateTime = endDateTime.plusHours(24); + } + + String beginStr = DateTimeFormatUtil.shortTime(beginDateTime.getHour(), beginMinute, beginSecond); + String endStr = DateTimeFormatUtil.shortTime(endDateTime.getHour(), endMinute, endSecond); + + ret.setSuccess(true); + + ret.setTimex(String.format("(%s,%s,%s)", beginStr, endStr, DateTimeFormatUtil.luisTimeSpan(Duration.between(beginDateTime, endDateTime)))); + + ret.setFutureValue(new Pair(beginDateTime, endDateTime)); + ret.setPastValue(new Pair(beginDateTime, endDateTime)); + + List subDateTimeEntities = new ArrayList<>(); + + // In SplitDateAndTime mode, time points will be get from these SubDateTimeEntities + // Cases like "from 4 to 5pm", "4" should not be treated as SubDateTimeEntity + if (hasLeft || beginMinute != Constants.InvalidMinute || beginSecond != Constants.InvalidSecond) { + ExtractResult er = new ExtractResult( + time1StartIndex, + time1EndIndex - time1StartIndex, + text.substring(time1StartIndex, time1EndIndex), + Constants.SYS_DATETIME_TIME); + + DateTimeParseResult pr = this.config.getTimeParser().parse(er, referenceTime); + subDateTimeEntities.add(pr); + } + + // Cases like "from 4am to 5", "5" should not be treated as SubDateTimeEntity + if (hasRight || endMinute != Constants.InvalidMinute || endSecond != Constants.InvalidSecond) { + ExtractResult er = new ExtractResult( + + time2StartIndex, + time2EndIndex - time2StartIndex, + text.substring(time2StartIndex, time2EndIndex), + Constants.SYS_DATETIME_TIME + ); + + DateTimeParseResult pr = this.config.getTimeParser().parse(er, referenceTime); + subDateTimeEntities.add(pr); + } + ret.setSubDateTimeEntities(subDateTimeEntities); + ret.setSuccess(true); + } + + return ret; + } + + private DateTimeResolutionResult mergeTwoTimePoints(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + DateTimeParseResult pr1 = null; + DateTimeParseResult pr2 = null; + boolean validTimeNumber = false; + + List ers = this.config.getTimeExtractor().extract(text, referenceTime); + if (ers.size() != 2) { + if (ers.size() == 1) { + List numErs = this.config.getIntegerExtractor().extract(text); + int erStart = ers.get(0).getStart() != null ? ers.get(0).getStart() : 0; + int erLength = ers.get(0).getLength() != null ? ers.get(0).getLength() : 0; + + for (ExtractResult num : numErs) { + int numStart = num.getStart() != null ? num.getStart() : 0; + int numLength = num.getLength() != null ? num.getLength() : 0; + int midStrBegin = 0; + int midStrEnd = 0; + // ending number + if (numStart > erStart + erLength) { + midStrBegin = erStart + erLength; + midStrEnd = numStart - midStrBegin; + } else if (numStart + numLength < erStart) { + midStrBegin = numStart + numLength; + midStrEnd = erStart - midStrBegin; + } + + // check if the middle string between the time point and the valid number is a connect string. + String middleStr = text.substring(midStrBegin, midStrBegin + midStrEnd); + Optional tillMatch = Arrays.stream(RegExpUtility.getMatches(this.config.getTillRegex(), middleStr)).findFirst(); + if (tillMatch.isPresent()) { + num.setData(null); + num.setType(Constants.SYS_DATETIME_TIME); + ers.add(num); + validTimeNumber = true; + break; + } + } + + ers.sort(Comparator.comparingInt(x -> x.getStart())); + } + + if (!validTimeNumber) { + return ret; + } + } + + pr1 = this.config.getTimeParser().parse(ers.get(0), referenceTime); + pr2 = this.config.getTimeParser().parse(ers.get(1), referenceTime); + + if (pr1.getValue() == null || pr2.getValue() == null) { + return ret; + } + + String ampmStr1 = ((DateTimeResolutionResult)pr1.getValue()).getComment(); + String ampmStr2 = ((DateTimeResolutionResult)pr2.getValue()).getComment(); + + LocalDateTime beginTime = (LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue(); + LocalDateTime endTime = (LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue(); + + if (!StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm) && + (endTime.compareTo(beginTime) < 1) && endTime.plusHours(Constants.HalfDayHourCount).isAfter(beginTime)) { + endTime = endTime.plusHours(Constants.HalfDayHourCount); + ((DateTimeResolutionResult)pr2.getValue()).setFutureValue(endTime); + pr2.setTimexStr(String.format("T%s", endTime.getHour())); + if (endTime.getMinute() > 0) { + pr2.setTimexStr(String.format("%s:%s", pr2.getTimexStr(), endTime.getMinute())); + } + } + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && endTime.isAfter(beginTime.plusHours(Constants.HalfDayHourCount))) { + beginTime = beginTime.plusHours(Constants.HalfDayHourCount); + ((DateTimeResolutionResult)pr1.getValue()).setFutureValue(beginTime); + pr1.setTimexStr(String.format("T%s", beginTime.getHour())); + if (beginTime.getMinute() > 0) { + pr1.setTimexStr(String.format("%s:%s", pr1.getTimexStr(), beginTime.getMinute())); + } + } + + if (endTime.isBefore(beginTime)) { + endTime = endTime.plusDays(1); + } + + long minutes = (Duration.between(beginTime, endTime).toMinutes() % 60); + long hours = (Duration.between(beginTime, endTime).toHours() % 24); + ret.setTimex(String.format("(%s,%s,PT", pr1.getTimexStr(), pr2.getTimexStr())); + + if (hours > 0) { + ret.setTimex(String.format("%s%sH", ret.getTimex(), hours)); + } + if (minutes > 0) { + ret.setTimex(String.format("%s%sM", ret.getTimex(), minutes)); + } + ret.setTimex(ret.getTimex() + ")"); + + ret.setFutureValue(new Pair(beginTime, endTime)); + ret.setPastValue(new Pair(beginTime, endTime)); + ret.setSuccess(true); + + if (!StringUtility.isNullOrEmpty(ampmStr1) && ampmStr1.endsWith(Constants.Comment_AmPm) && + !StringUtility.isNullOrEmpty(ampmStr2) && ampmStr2.endsWith(Constants.Comment_AmPm)) { + ret.setComment(Constants.Comment_AmPm); + } + + if (((DateTimeResolutionResult)pr1.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr1.getValue()).getTimeZoneResolution()); + } else if (((DateTimeResolutionResult)pr2.getValue()).getTimeZoneResolution() != null) { + ret.setTimeZoneResolution(((DateTimeResolutionResult)pr2.getValue()).getTimeZoneResolution()); + } + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(pr1); + subDateTimeEntities.add(pr2); + ret.setSubDateTimeEntities(subDateTimeEntities); + + return ret; + } + + private DateTimeResolutionResult parseTimeOfDay(String text, LocalDateTime referenceTime) { + int day = referenceTime.getDayOfMonth(); + int month = referenceTime.getMonthValue(); + int year = referenceTime.getYear(); + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + + // extract early/late prefix from text + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config.getTimeOfDayRegex(), text)).findFirst(); + boolean hasEarly = false; + boolean hasLate = false; + if (match.isPresent()) { + if (!StringUtility.isNullOrEmpty(match.get().getGroup("early").value)) { + String early = match.get().getGroup("early").value; + text = text.replace(early, ""); + hasEarly = true; + ret.setComment(Constants.Comment_Early); + ret.setMod(Constants.EARLY_MOD); + } + + if (!hasEarly && !StringUtility.isNullOrEmpty(match.get().getGroup("late").value)) { + String late = match.get().getGroup("late").value; + text = text.replace(late, ""); + hasLate = true; + ret.setComment(Constants.Comment_Late); + ret.setMod(Constants.LATE_MOD); + } + } + MatchedTimeRangeResult timexResult = this.config.getMatchedTimexRange(text, "", 0, 0, 0); + if (!timexResult.getMatched()) { + return new DateTimeResolutionResult(); + } + + // modify time period if "early" or "late" is existed + if (hasEarly) { + timexResult.setEndHour(timexResult.getBeginHour() + 2); + // handling case: night end with 23:59 + if (timexResult.getEndMin() == 59) { + timexResult.setEndMin(0); + } + } else if (hasLate) { + timexResult.setBeginHour(timexResult.getBeginHour() + 2); + } + + ret.setTimex(timexResult.getTimeStr()); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getEndHour(), timexResult.getEndMin(), timexResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue(LocalDateTime.MIN, year, month, day, timexResult.getEndHour(), timexResult.getEndMin(), timexResult.getEndMin()))); + + ret.setSuccess(true); + + return ret; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java new file mode 100644 index 000000000..79bd6a34d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseTimeZoneParser.java @@ -0,0 +1,168 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.resources.EnglishTimeZone; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimeZoneResolutionResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BaseTimeZoneParser implements IDateTimeParser { + private final Pattern directUtcRegex; + + public BaseTimeZoneParser() { + directUtcRegex = RegExpUtility.getSafeRegExp(EnglishTimeZone.DirectUtcRegex); + } + + @Override + public String getParserName() { + return Constants.SYS_DATETIME_TIME; + } + + @Override + public List filterResults(String query, List candidateResults) { + return candidateResults; + } + + public String normalizeText(String text) { + text = text.replaceAll("\\s+", " "); + text = text.replaceAll("time$|timezone$", ""); + return text.trim(); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + return this.parse(extractResult, LocalDateTime.now()); + } + + @Override + public DateTimeParseResult parse(ExtractResult er, LocalDateTime reference) { + DateTimeParseResult result; + result = new DateTimeParseResult(er); + + String text = er.getText().toLowerCase(); + String normalizedText = normalizeText(text); + Matcher match = directUtcRegex.matcher(text); + String matched = match.find() ? match.group(2) : ""; + int offsetInMinutes = matched != null ? computeMinutes(matched) : Constants.InvalidOffsetValue; + + if (offsetInMinutes != Constants.InvalidOffsetValue) { + DateTimeResolutionResult value = getDateTimeResolutionResult(offsetInMinutes, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, offsetInMinutes); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else if (checkAbbrToMin(normalizedText)) { + int utcMinuteShift = EnglishTimeZone.AbbrToMinMapping.getOrDefault(normalizedText, 0); + + DateTimeResolutionResult value = getDateTimeResolutionResult(utcMinuteShift, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, utcMinuteShift); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else if (checkFullToMin(normalizedText)) { + int utcMinuteShift = EnglishTimeZone.FullToMinMapping.getOrDefault(normalizedText, 0); + + DateTimeResolutionResult value = getDateTimeResolutionResult(utcMinuteShift, text); + String resolutionStr = String.format("%s: %d", Constants.UtcOffsetMinsKey, utcMinuteShift); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } else { + // TODO: Temporary solution for city timezone and ambiguous data + DateTimeResolutionResult value = new DateTimeResolutionResult(); + value.setSuccess(true); + value.setTimeZoneResolution(new TimeZoneResolutionResult("UTC+XX:XX", Constants.InvalidOffsetValue, text)); + String resolutionStr = String.format("%s: %s", Constants.UtcOffsetMinsKey, "XX:XX"); + + result.setValue(value); + result.setResolutionStr(resolutionStr); + } + + return result; + } + + private boolean checkAbbrToMin(String text) { + if (EnglishTimeZone.AbbrToMinMapping.containsKey(text)) { + return EnglishTimeZone.AbbrToMinMapping.get(text) != Constants.InvalidOffsetValue; + } + return false; + } + + private boolean checkFullToMin(String text) { + if (EnglishTimeZone.FullToMinMapping.containsKey(text)) { + return EnglishTimeZone.FullToMinMapping.get(text) != Constants.InvalidOffsetValue; + } + return false; + } + + private DateTimeResolutionResult getDateTimeResolutionResult(int offsetInMinutes, String text) { + DateTimeResolutionResult value = new DateTimeResolutionResult(); + value.setSuccess(true); + value.setTimeZoneResolution(new TimeZoneResolutionResult(convertOffsetInMinsToOffsetString(offsetInMinutes), offsetInMinutes, text)); + return value; + } + + private String convertOffsetInMinsToOffsetString(int offsetInMinutes) { + return String.format("UTC%s%s", offsetInMinutes >= 0 ? "+" : "-", convertMinsToRegularFormat(Math.abs(offsetInMinutes))); + } + + private String convertMinsToRegularFormat(int offsetMins) { + Duration duration = Duration.ofMinutes(offsetMins); + return String.format("%02d:%02d", duration.toHours() % 24, duration.toMinutes() % 60); + } + + // Compute UTC offset in minutes from matched timezone offset in text. e.g. "-4:30" -> -270; "+8"-> 480. + public int computeMinutes(String utcOffset) { + if (utcOffset.length() == 0) { + return Constants.InvalidOffsetValue; + } + + utcOffset = utcOffset.trim(); + int sign = Constants.PositiveSign; // later than utc, default value + if (utcOffset.startsWith("+") || utcOffset.startsWith("-") || utcOffset.startsWith("±")) { + if (utcOffset.startsWith("-")) { + sign = Constants.NegativeSign; // earlier than utc 0 + } + + utcOffset = utcOffset.substring(1).trim(); + } + + int hours = 0; + final int minutes; + if (utcOffset.contains(":")) { + String[] tokens = utcOffset.split(":"); + hours = Integer.parseInt(tokens[0]); + minutes = Integer.parseInt(tokens[1]); + } else { + minutes = 0; + try { + hours = Integer.parseInt(utcOffset); + } catch (Exception e) { + hours = 0; + } + } + + if (hours > Constants.HalfDayHourCount) { + return Constants.InvalidOffsetValue; + } + + if (Arrays.stream(new int[]{0, 15, 30, 45, 60}).anyMatch(x -> x == minutes)) { + return Constants.InvalidOffsetValue; + } + + int offsetInMinutes = hours * 60 + minutes; + offsetInMinutes *= sign; + + return offsetInMinutes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java new file mode 100644 index 000000000..62b6a9f17 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/DateTimeParseResult.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; +import com.microsoft.recognizers.text.ParseResult; + +public class DateTimeParseResult extends ParseResult { + //TimexStr is only used in extractors related with date and time + //It will output the TIMEX representation of a time string. + private String timexStr; + + public DateTimeParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, String timexStr) { + super(start, length, text, type, data, value, resolutionStr); + this.timexStr = timexStr; + } + + public DateTimeParseResult(ExtractResult er) { + this(er.getStart(), er.getLength(), er.getText(), er.getType(), er.getData(), null, null, null); + } + + public DateTimeParseResult(ParseResult pr) { + this(pr.getStart(), pr.getLength(), pr.getText(), pr.getType(), pr.getData(), pr.getValue(), pr.getResolutionStr(), null); + } + + public DateTimeParseResult(Integer start, Integer length, String text, String type, Object data, Object value, String resolutionStr, String timexStr, Metadata metadata) { + super(start, length, text, type, data, value, resolutionStr, metadata); + this.timexStr = timexStr; + } + + public String getTimexStr() { + return timexStr; + } + + public void setTimexStr(String timexStr) { + this.timexStr = timexStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java new file mode 100644 index 000000000..e88ae76da --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/IDateTimeParser.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.datetime.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; + +import java.time.LocalDateTime; +import java.util.List; + +public interface IDateTimeParser extends IParser { + String getParserName(); + + DateTimeParseResult parse(ExtractResult er, LocalDateTime reference); + + List filterResults(String query, List candidateResults); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java new file mode 100644 index 000000000..c72112364 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/BaseDateParserConfiguration.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; + +public abstract class BaseDateParserConfiguration extends BaseOptionsConfiguration implements ICommonDateTimeParserConfiguration { + protected BaseDateParserConfiguration(DateTimeOptions options) { + super(options); + } + + @Override + public ImmutableMap getDayOfMonth() { + return BaseDateTime.DayOfMonthDictionary; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..2696ac12c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ICommonDateTimeParserConfiguration.java @@ -0,0 +1,80 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ICommonDateTimeParserConfiguration extends IOptionsConfiguration { + IExtractor getCardinalExtractor(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + IDateExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getDatePeriodParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDateTimePeriodParser(); + + IDateTimeParser getDateTimeAltParser(); + + IDateTimeParser getTimeZoneParser(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getNumbers(); + + ImmutableMap getUnitValueMap(); + + ImmutableMap getSeasonMap(); + + ImmutableMap getSpecialYearPrefixesMap(); + + ImmutableMap getUnitMap(); + + ImmutableMap getCardinalMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getDoubleNumbers(); + + ImmutableMap getWrittenDecades(); + + ImmutableMap getSpecialDecadeCases(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java new file mode 100644 index 000000000..0f288f7a6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateParserConfiguration.java @@ -0,0 +1,100 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.List; +import java.util.regex.Pattern; + +public interface IDateParserConfiguration extends IOptionsConfiguration { + String getDateTokenPrefix(); + + IExtractor getIntegerExtractor(); + + IExtractor getOrdinalExtractor(); + + IExtractor getCardinalExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateExtractor getDateExtractor(); + + IDateTimeParser getDurationParser(); + + Iterable getDateRegexes(); + + Pattern getOnRegex(); + + Pattern getSpecialDayRegex(); + + Pattern getSpecialDayWithNumRegex(); + + Pattern getNextRegex(); + + Pattern getThisRegex(); + + Pattern getLastRegex(); + + Pattern getUnitRegex(); + + Pattern getWeekDayRegex(); + + Pattern getMonthRegex(); + + Pattern getWeekDayOfMonthRegex(); + + Pattern getForTheRegex(); + + Pattern getWeekDayAndDayOfMonthRegex(); + + Pattern getRelativeMonthRegex(); + + Pattern getStrictRelativeRegex(); + + Pattern getYearSuffix(); + + Pattern getRelativeWeekDayRegex(); + + Pattern getRelativeDayRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getPastPrefixRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getDayOfWeek(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getCardinalMap(); + + List getSameDayTerms(); + + List getPlusOneDayTerms(); + + List getMinusOneDayTerms(); + + List getPlusTwoDayTerms(); + + List getMinusTwoDayTerms(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + Integer getSwiftMonthOrYear(String text); + + Boolean isCardinalLast(String text); + + String normalize(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java new file mode 100644 index 000000000..b32018ec7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDatePeriodParserConfiguration.java @@ -0,0 +1,155 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +import java.util.regex.Pattern; + +public interface IDatePeriodParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IDateExtractor getDateExtractor(); + + IExtractor getCardinalExtractor(); + + IExtractor getOrdinalExtractor(); + + IExtractor getIntegerExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getDateParser(); + + Pattern getMonthFrontBetweenRegex(); + + Pattern getBetweenRegex(); + + Pattern getMonthFrontSimpleCasesRegex(); + + Pattern getSimpleCasesRegex(); + + Pattern getOneWordPeriodRegex(); + + Pattern getMonthWithYear(); + + Pattern getMonthNumWithYear(); + + Pattern getYearRegex(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getWeekOfMonthRegex(); + + Pattern getWeekOfYearRegex(); + + Pattern getQuarterRegex(); + + Pattern getQuarterRegexYearFront(); + + Pattern getAllHalfYearRegex(); + + Pattern getSeasonRegex(); + + Pattern getWhichWeekRegex(); + + Pattern getWeekOfRegex(); + + Pattern getMonthOfRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getNextPrefixRegex(); + + Pattern getPastPrefixRegex(); + + Pattern getThisPrefixRegex(); + + Pattern getRestOfDateRegex(); + + Pattern getLaterEarlyPeriodRegex(); + + Pattern getWeekWithWeekDayRangeRegex(); + + Pattern getYearPlusNumberRegex(); + + Pattern getDecadeWithCenturyRegex(); + + Pattern getYearPeriodRegex(); + + Pattern getComplexDatePeriodRegex(); + + Pattern getRelativeDecadeRegex(); + + Pattern getReferenceDatePeriodRegex(); + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getLessThanRegex(); + + Pattern getMoreThanRegex(); + + Pattern getCenturySuffixRegex(); + + Pattern getRelativeRegex(); + + Pattern getUnspecificEndOfRangeRegex(); + + Pattern getNowRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getCardinalMap(); + + ImmutableMap getDayOfMonth(); + + ImmutableMap getMonthOfYear(); + + ImmutableMap getSeasonMap(); + + ImmutableMap getSpecialYearPrefixesMap(); + + ImmutableMap getWrittenDecades(); + + ImmutableMap getNumbers(); + + ImmutableMap getSpecialDecadeCases(); + + boolean isFuture(String text); + + boolean isYearToDate(String text); + + boolean isMonthToDate(String text); + + boolean isWeekOnly(String text); + + boolean isWeekend(String text); + + boolean isMonthOnly(String text); + + boolean isYearOnly(String text); + + int getSwiftYear(String text); + + int getSwiftDayOrMonth(String text); + + boolean isLastCardinal(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..6b1139546 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeAltParserConfiguration.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +public interface IDateTimeAltParserConfiguration { + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimePeriodParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDatePeriodParser(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java new file mode 100644 index 000000000..30303ee29 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimeParserConfiguration.java @@ -0,0 +1,70 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface IDateTimeParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + String getTokenBeforeTime(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IExtractor getCardinalExtractor(); + + IExtractor getIntegerExtractor(); + + IParser getNumberParser(); + + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + Pattern getNowRegex(); + + Pattern getAMTimeRegex(); + + Pattern getPMTimeRegex(); + + Pattern getSimpleTimeOfTodayAfterRegex(); + + Pattern getSimpleTimeOfTodayBeforeRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getSpecificEndOfRegex(); + + Pattern getUnspecificEndOfRegex(); + + Pattern getUnitRegex(); + + Pattern getDateNumberConnectorRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + boolean containsAmbiguousToken(String text, String matchedText); + + ResultTimex getMatchedNowTimex(String text); + + int getSwiftDay(String text); + + int getHour(String text, int hour); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..d74d1da68 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDateTimePeriodParserConfiguration.java @@ -0,0 +1,84 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; + +import java.util.regex.Pattern; + +public interface IDateTimePeriodParserConfiguration extends IOptionsConfiguration { + String getTokenBeforeDate(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeExtractor getDurationExtractor(); + + IExtractor getCardinalExtractor(); + + IParser getNumberParser(); + + IDateTimeParser getDateParser(); + + IDateTimeParser getTimeParser(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeParser getDurationParser(); + + IDateTimeParser getTimeZoneParser(); + + Pattern getPureNumberFromToRegex(); + + Pattern getPureNumberBetweenAndRegex(); + + Pattern getSpecificTimeOfDayRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getPastRegex(); + + Pattern getFutureRegex(); + + Pattern getFutureSuffixRegex(); + + Pattern getNumberCombinedWithUnitRegex(); + + Pattern getUnitRegex(); + + Pattern getPeriodTimeOfDayWithDateRegex(); + + Pattern getRelativeTimeUnitRegex(); + + Pattern getRestOfDateTimeRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getPrefixDayRegex(); + + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getNumbers(); + + MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin); + + int getSwiftPrefix(String text); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java new file mode 100644 index 000000000..686ba08b2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IDurationParserConfiguration.java @@ -0,0 +1,44 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.util.regex.Pattern; + +public interface IDurationParserConfiguration extends IOptionsConfiguration { + IExtractor getCardinalExtractor(); + + IExtractor getDurationExtractor(); + + IParser getNumberParser(); + + Pattern getNumberCombinedWithUnit(); + + Pattern getAnUnitRegex(); + + Pattern getDuringRegex(); + + Pattern getAllDateUnitRegex(); + + Pattern getHalfDateUnitRegex(); + + Pattern getSuffixAndRegex(); + + Pattern getFollowedUnit(); + + Pattern getConjunctionRegex(); + + Pattern getInexactNumberRegex(); + + Pattern getInexactNumberUnitRegex(); + + Pattern getDurationUnitRegex(); + + ImmutableMap getUnitMap(); + + ImmutableMap getUnitValueMap(); + + ImmutableMap getDoubleNumbers(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java new file mode 100644 index 000000000..b93317514 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IHolidayParserConfiguration.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.regex.Pattern; + +public interface IHolidayParserConfiguration extends IOptionsConfiguration { + ImmutableMap getVariableHolidaysTimexDictionary(); + + ImmutableMap> getHolidayFuncDictionary(); + + ImmutableMap> getHolidayNames(); + + Iterable getHolidayRegexList(); + + int getSwiftYear(String text); + + String sanitizeHolidayToken(String holiday); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java new file mode 100644 index 000000000..8fd654b80 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/IMergedParserConfiguration.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public interface IMergedParserConfiguration extends ICommonDateTimeParserConfiguration { + Pattern getBeforeRegex(); + + Pattern getAfterRegex(); + + Pattern getSinceRegex(); + + Pattern getAroundRegex(); + + Pattern getSuffixAfterRegex(); + + Pattern getYearRegex(); + + IDateTimeParser getGetParser(); + + IDateTimeParser getHolidayParser(); + + IDateTimeParser getTimeZoneParser(); + + StringMatcher getSuperfluousWordMatcher(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java new file mode 100644 index 000000000..881b20158 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ISetParserConfiguration.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; + +import java.util.regex.Pattern; + +public interface ISetParserConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getDurationExtractor(); + + IDateTimeParser getDurationParser(); + + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getTimeParser(); + + IDateTimeExtractor getDateExtractor(); + + IDateTimeParser getDateParser(); + + IDateTimeExtractor getDateTimeExtractor(); + + IDateTimeParser getDateTimeParser(); + + IDateTimeExtractor getDatePeriodExtractor(); + + IDateTimeParser getDatePeriodParser(); + + IDateTimeExtractor getTimePeriodExtractor(); + + IDateTimeParser getTimePeriodParser(); + + IDateTimeExtractor getDateTimePeriodExtractor(); + + IDateTimeParser getDateTimePeriodParser(); + + ImmutableMap getUnitMap(); + + Pattern getEachPrefixRegex(); + + Pattern getPeriodicRegex(); + + Pattern getEachUnitRegex(); + + Pattern getEachDayRegex(); + + Pattern getSetWeekDayRegex(); + + Pattern getSetEachRegex(); + + MatchedTimexResult getMatchedDailyTimex(String text); + + MatchedTimexResult getMatchedUnitTimex(String text); +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java new file mode 100644 index 000000000..1f0854154 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimeParserConfiguration.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ITimeParserConfiguration extends IOptionsConfiguration { + String getTimeTokenPrefix(); + + Pattern getAtRegex(); + + Iterable getTimeRegexes(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + IDateTimeParser getTimeZoneParser(); + + PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin); + + SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java new file mode 100644 index 000000000..2b8bd57ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/ITimePeriodParserConfiguration.java @@ -0,0 +1,40 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; + +import java.util.regex.Pattern; + +public interface ITimePeriodParserConfiguration extends IOptionsConfiguration { + IDateTimeExtractor getTimeExtractor(); + + IDateTimeParser getTimeParser(); + + IExtractor getIntegerExtractor(); + + IDateTimeParser getTimeZoneParser(); + + Pattern getPureNumberFromToRegex(); + + Pattern getPureNumberBetweenAndRegex(); + + Pattern getSpecificTimeFromToRegex(); + + Pattern getSpecificTimeBetweenAndRegex(); + + Pattern getTimeOfDayRegex(); + + Pattern getGeneralEndingRegex(); + + Pattern getTillRegex(); + + ImmutableMap getNumbers(); + + IDateTimeUtilityConfiguration getUtilityConfiguration(); + + MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java new file mode 100644 index 000000000..59f105384 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/MatchedTimeRangeResult.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class MatchedTimeRangeResult { + private boolean matched; + private String timeStr; + private int beginHour; + private int endHour; + private int endMin; + + public MatchedTimeRangeResult(boolean matched, String timeStr, int beginHour, int endHour, int endMin) { + this.matched = matched; + this.timeStr = timeStr; + this.beginHour = beginHour; + this.endHour = endHour; + this.endMin = endMin; + } + + public boolean getMatched() { + return matched; + } + + public String getTimeStr() { + return timeStr; + } + + public int getBeginHour() { + return beginHour; + } + + public int getEndHour() { + return endHour; + } + + public int getEndMin() { + return endMin; + } + + public void setMatched(boolean matched) { + this.matched = matched; + } + + public void setTimeStr(String timeStr) { + this.timeStr = timeStr; + } + + public void setBeginHour(int beginHour) { + this.beginHour = beginHour; + } + + public void setEndHour(int endHour) { + this.endHour = endHour; + } + + public void setEndMin(int endMin) { + this.endMin = endMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java new file mode 100644 index 000000000..7887e1c54 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/PrefixAdjustResult.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class PrefixAdjustResult { + public final int hour; + public final int minute; + public final boolean hasMin; + + public PrefixAdjustResult(int hour, int minute, boolean hasMin) { + this.hour = hour; + this.minute = minute; + this.hasMin = hasMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java new file mode 100644 index 000000000..86a6a1e34 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/config/SuffixAdjustResult.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.datetime.parsers.config; + +public class SuffixAdjustResult { + public final int hour; + public final int minute; + public final boolean hasMin; + public final boolean hasAm; + public final boolean hasPm; + + public SuffixAdjustResult(int hour, int minute, boolean hasMin, boolean hasAm, boolean hasPm) { + this.hour = hour; + this.minute = minute; + this.hasMin = hasMin; + this.hasAm = hasAm; + this.hasPm = hasPm; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java new file mode 100644 index 000000000..3add7bfae --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/BaseDateTime.java @@ -0,0 +1,111 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class BaseDateTime { + + public static final String HourRegex = "(?2[0-4]|[0-1]?\\d)(h)?"; + + public static final String TwoDigitHourRegex = "(?[0-1]\\d|2[0-4])(h)?"; + + public static final String MinuteRegex = "(?[0-5]?\\d)(?!\\d)"; + + public static final String TwoDigitMinuteRegex = "(?[0-5]\\d)(?!\\d)"; + + public static final String DeltaMinuteRegex = "(?[0-5]?\\d)"; + + public static final String SecondRegex = "(?[0-5]?\\d)"; + + public static final String FourDigitYearRegex = "\\b(?((1\\d|20)\\d{2})|2100)(?!\\.0\\b)\\b"; + + public static final String HyphenDateRegex = "((?[0-9]{4})-?(?1[0-2]|0[1-9])-?(?3[01]|0[1-9]|[12][0-9]))|((?1[0-2]|0[1-9])-?(?3[01]|0[1-9]|[12][0-9])-?(?[0-9]{4}))|((?3[01]|0[1-9]|[12][0-9])-?(?1[0-2]|0[1-9])-?(?[0-9]{4}))"; + + public static final String IllegalYearRegex = "([-])({FourDigitYearRegex})([-])" + .replace("{FourDigitYearRegex}", FourDigitYearRegex); + + public static final String RangeConnectorSymbolRegex = "(--|-|—|——|~|–)"; + + public static final String BaseAmDescRegex = "(am\\b|a\\s*\\.\\s*m\\s*\\.|a[\\.]?\\s*m\\b)"; + + public static final String BasePmDescRegex = "(pm\\b|p\\s*\\.\\s*m\\s*\\.|p[\\.]?\\s*m\\b)"; + + public static final String BaseAmPmDescRegex = "(ampm)"; + + public static final String EqualRegex = "(?)="; + + public static final int MinYearNum = 1500; + + public static final int MaxYearNum = 2100; + + public static final int MaxTwoDigitYearFutureNum = 30; + + public static final int MinTwoDigitYearPastNum = 40; + + public static final ImmutableMap DayOfMonthDictionary = ImmutableMap.builder() + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("13", 13) + .put("14", 14) + .put("15", 15) + .put("16", 16) + .put("17", 17) + .put("18", 18) + .put("19", 19) + .put("20", 20) + .put("21", 21) + .put("22", 22) + .put("23", 23) + .put("24", 24) + .put("25", 25) + .put("26", 26) + .put("27", 27) + .put("28", 28) + .put("29", 29) + .put("30", 30) + .put("31", 31) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("fathers", "-06-WXX-7-3") + .put("mothers", "-05-WXX-7-2") + .put("thanksgiving", "-11-WXX-4-4") + .put("martinlutherking", "-01-WXX-1-3") + .put("washingtonsbirthday", "-02-WXX-1-3") + .put("canberra", "-03-WXX-1-1") + .put("labour", "-09-WXX-1-1") + .put("columbus", "-10-WXX-1-2") + .put("memorial", "-05-WXX-1-4") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java new file mode 100644 index 000000000..23ebb3069 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/ChineseDateTime.java @@ -0,0 +1,1016 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseDateTime { + + public static final String LangMarker = "Chi"; + + public static final String MonthRegex = "(?正月|一月|二月|三月|四月|五月|六月|七月|八月|九月|十月|十一月|十二月|01月|02月|03月|04月|05月|06月|07月|08月|09月|10月|11月|12月|1月|2月|3月|4月|5月|6月|7月|8月|9月|大年(?!龄|纪|级))"; + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|1|2|3|4|5|6|7|8|9)"; + + public static final String OneToNineIntegerRegex = "[一二三四五六七八九壹贰叁肆伍陆柒捌玖]"; + + public static final String DateDayRegexInChinese = "(?(([12][0-9]|3[01]|[1-9]|[三叁][十拾][一壹]?|[二贰貳]?[十拾]({OneToNineIntegerRegex})?|{OneToNineIntegerRegex})[日号]|初一|三十))" + .replace("{OneToNineIntegerRegex}", OneToNineIntegerRegex); + + public static final String DayRegexNumInChinese = "(?[12][0-9]|3[01]|[1-9]|[三叁][十拾][一壹]?|[二贰貳]?[十拾]({OneToNineIntegerRegex})?|{OneToNineIntegerRegex}|廿|卅)" + .replace("{OneToNineIntegerRegex}", OneToNineIntegerRegex); + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)"; + + public static final String TwoNumYear = "50"; + + public static final String YearNumRegex = "(?((1[5-9]|20)\\d{2})|2100)"; + + public static final String SimpleYearRegex = "(?(\\d{2,4}))"; + + public static final String ZeroToNineIntegerRegexChs = "[一二三四五六七八九零壹贰叁肆伍陆柒捌玖〇两千俩倆仨]"; + + public static final String DynastyStartYear = "元"; + + public static final String RegionTitleRegex = "(贞观|开元|神龙|洪武|建文|永乐|景泰|天顺|成化|嘉靖|万历|崇祯|顺治|康熙|雍正|乾隆|嘉庆|道光|咸丰|同治|光绪|宣统|民国)"; + + public static final String DynastyYearRegex = "(?{RegionTitleRegex})(?({DynastyStartYear}|\\d{1,3}|[十拾]?({ZeroToNineIntegerRegexChs}[十百拾佰]?){0,3}))" + .replace("{RegionTitleRegex}", RegionTitleRegex) + .replace("{DynastyStartYear}", DynastyStartYear) + .replace("{ZeroToNineIntegerRegexChs}", ZeroToNineIntegerRegexChs); + + public static final String DateYearInChineseRegex = "(?({ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}{ZeroToNineIntegerRegexChs}|{DynastyYearRegex}))" + .replace("{ZeroToNineIntegerRegexChs}", ZeroToNineIntegerRegexChs) + .replace("{DynastyYearRegex}", DynastyYearRegex); + + public static final String WeekDayRegex = "(?周日|周天|周一|周二|周三|周四|周五|周六|星期一|星期二|星期三|星期四|星期五|星期六|星期日|星期天|礼拜一|礼拜二|礼拜三|礼拜四|礼拜五|礼拜六|礼拜日|礼拜天|禮拜一|禮拜二|禮拜三|禮拜四|禮拜五|禮拜六|禮拜日|禮拜天|週日|週天|週一|週二|週三|週四|週五|週六)"; + + public static final String LunarRegex = "(农历|初一|正月|大年(?!龄|纪|级))"; + + public static final String DateThisRegex = "(这个|这一个|这|这一|本){WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateLastRegex = "(上一个|上个|上一|上|最后一个|最后)(的)?{WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateNextRegex = "(下一个|下个|下一|下)(的)?{WeekDayRegex}" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "(最近|前天|后天|昨天|明天|今天|今日|明日|昨日|大后天|大前天|後天|大後天)"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String WeekDayOfMonthRegex = "((({MonthRegex}|{MonthNumRegex})的\\s*)(?第一个|第二个|第三个|第四个|第五个|最后一个)\\s*{WeekDayRegex})" + .replace("{MonthRegex}", MonthRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String ThisPrefixRegex = "这个|这一个|这|这一|本|今"; + + public static final String LastPrefixRegex = "上个|上一个|上|上一|去"; + + public static final String NextPrefixRegex = "下个|下一个|下|下一|明"; + + public static final String RelativeRegex = "(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex}))" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{LastPrefixRegex}", LastPrefixRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex); + + public static final String SpecialDate = "(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex})年)?(?({ThisPrefixRegex}|{LastPrefixRegex}|{NextPrefixRegex})月)?{DateDayRegexInChinese}" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{LastPrefixRegex}", LastPrefixRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese); + + public static final String DateUnitRegex = "(?年|个月|周|日|天)"; + + public static final String BeforeRegex = "以前|之前|前"; + + public static final String AfterRegex = "以后|以後|之后|之後|后|後"; + + public static final String DateRegexList1 = "({LunarRegex}(\\s*))?((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?{MonthRegex}(\\s*){DateDayRegexInChinese}((\\s*|,|,){WeekDayRegex})?" + .replace("{LunarRegex}", LunarRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateRegexList2 = "((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?({LunarRegex}(\\s*))?{MonthRegex}(\\s*){DateDayRegexInChinese}((\\s*|,|,){WeekDayRegex})?" + .replace("{MonthRegex}", MonthRegex) + .replace("{DateDayRegexInChinese}", DateDayRegexInChinese) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{LunarRegex}", LunarRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex); + + public static final String DateRegexList3 = "((({SimpleYearRegex}|{DateYearInChineseRegex})年)(\\s*))?({LunarRegex}(\\s*))?{MonthRegex}(\\s*)({DayRegexNumInChinese}|{DayRegex})((\\s*|,|,){WeekDayRegex})?" + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegexNumInChinese}", DayRegexNumInChinese) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{LunarRegex}", LunarRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateRegexList4 = "{MonthNumRegex}\\s*/\\s*{DayRegex}" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateRegexList5 = "{DayRegex}\\s*/\\s*{MonthNumRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String DateRegexList6 = "{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}\\s*[/\\\\\\-]\\s*{SimpleYearRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex); + + public static final String DateRegexList7 = "{DayRegex}\\s*[/\\\\\\-\\.]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\.]\\s*{SimpleYearRegex}" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex); + + public static final String DateRegexList8 = "{SimpleYearRegex}\\s*[/\\\\\\-\\. ]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\. ]\\s*{DayRegex}" + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DatePeriodTillRegex = "(?到|至|--|-|—|——|~|–)"; + + public static final String DatePeriodTillSuffixRequiredRegex = "(?与|和)"; + + public static final String DatePeriodDayRegexInChinese = "(?初一|三十|一日|十一日|二十一日|三十一日|二日|三日|四日|五日|六日|七日|八日|九日|十二日|十三日|十四日|十五日|十六日|十七日|十八日|十九日|二十二日|二十三日|二十四日|二十五日|二十六日|二十七日|二十八日|二十九日|一日|十一日|十日|二十一日|二十日|三十一日|三十日|二日|三日|四日|五日|六日|七日|八日|九日|十二日|十三日|十四日|十五日|十六日|十七日|十八日|十九日|二十二日|二十三日|二十四日|二十五日|二十六日|二十七日|二十八日|二十九日|十日|二十日|三十日|10日|11日|12日|13日|14日|15日|16日|17日|18日|19日|1日|20日|21日|22日|23日|24日|25日|26日|27日|28日|29日|2日|30日|31日|3日|4日|5日|6日|7日|8日|9日|一号|十一号|二十一号|三十一号|二号|三号|四号|五号|六号|七号|八号|九号|十二号|十三号|十四号|十五号|十六号|十七号|十八号|十九号|二十二号|二十三号|二十四号|二十五号|二十六号|二十七号|二十八号|二十九号|一号|十一号|十号|二十一号|二十号|三十一号|三十号|二号|三号|四号|五号|六号|七号|八号|九号|十二号|十三号|十四号|十五号|十六号|十七号|十八号|十九号|二十二号|二十三号|二十四号|二十五号|二十六号|二十七号|二十八号|二十九号|十号|二十号|三十号|10号|11号|12号|13号|14号|15号|16号|17号|18号|19号|1号|20号|21号|22号|23号|24号|25号|26号|27号|28号|29号|2号|30号|31号|3号|4号|5号|6号|7号|8号|9号|一|十一|二十一|三十一|二|三|四|五|六|七|八|九|十二|十三|十四|十五|十六|十七|十八|十九|二十二|二十三|二十四|二十五|二十六|二十七|二十八|二十九|一|十一|十|二十一|二十|三十一|三十|二|三|四|五|六|七|八|九|十二|十三|十四|十五|十六|十七|十八|十九|二十二|二十三|二十四|二十五|二十六|二十七|二十八|二十九|十|二十|三十|廿|卅)"; + + public static final String DatePeriodThisRegex = "这个|这一个|这|这一|本"; + + public static final String DatePeriodLastRegex = "上个|上一个|上|上一"; + + public static final String DatePeriodNextRegex = "下个|下一个|下|下一"; + + public static final String RelativeMonthRegex = "(?({DatePeriodThisRegex}|{DatePeriodLastRegex}|{DatePeriodNextRegex})\\s*月)" + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex); + + public static final String HalfYearRegex = "((?(上|前)半年)|(?(下|后)半年))"; + + public static final String YearRegex = "(({YearNumRegex})(\\s*年)?|({SimpleYearRegex})\\s*年){HalfYearRegex}?" + .replace("{YearNumRegex}", YearNumRegex) + .replace("{SimpleYearRegex}", SimpleYearRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String StrictYearRegex = "({YearRegex}(?=[\\u4E00-\\u9FFF]|\\s|$|\\W))" + .replace("{YearRegex}", YearRegex); + + public static final String YearRegexInNumber = "(?(\\d{4}))"; + + public static final String DatePeriodYearInChineseRegex = "{DateYearInChineseRegex}年{HalfYearRegex}?" + .replace("{DateYearInChineseRegex}", DateYearInChineseRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String MonthSuffixRegex = "(?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String SimpleCasesRegex = "((从)\\s*)?(({YearRegex}|{DatePeriodYearInChineseRegex})\\s*)?{MonthSuffixRegex}({DatePeriodDayRegexInChinese}|{DayRegex})\\s*{DatePeriodTillRegex}\\s*({DatePeriodDayRegexInChinese}|{DayRegex})((\\s+|\\s*,\\s*){YearRegex})?" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DatePeriodDayRegexInChinese}", DatePeriodDayRegexInChinese) + .replace("{DayRegex}", DayRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex); + + public static final String YearAndMonth = "({DatePeriodYearInChineseRegex}|{YearRegex})\\s*{MonthRegex}" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String PureNumYearAndMonth = "({YearRegexInNumber}\\s*[-\\.\\/]\\s*{MonthNumRegex})|({MonthNumRegex}\\s*\\/\\s*{YearRegexInNumber})" + .replace("{YearRegexInNumber}", YearRegexInNumber) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String OneWordPeriodRegex = "(((?(明|今|去)年)\\s*)?{MonthRegex}|({DatePeriodThisRegex}|{DatePeriodLastRegex}|{DatePeriodNextRegex})(?半)?\\s*(周末|周|月|年)|周末|(今|明|去|前|后)年(\\s*{HalfYearRegex})?)" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex) + .replace("{HalfYearRegex}", HalfYearRegex); + + public static final String WeekOfMonthRegex = "(?{MonthSuffixRegex}的(?第一|第二|第三|第四|第五|最后一)\\s*周\\s*)" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String UnitRegex = "(?年|(个)?月|周|日|天)"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String NumberCombinedWithUnit = "(?\\d+(\\.\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DateRangePrepositions = "((从|在|自)\\s*)?"; + + public static final String YearToYear = "({DateRangePrepositions})({DatePeriodYearInChineseRegex}|{YearRegex})\\s*({DatePeriodTillRegex}|后|後|之后|之後)\\s*({DatePeriodYearInChineseRegex}|{YearRegex})(\\s*((之间|之内|期间|中间|间)|前|之前))?" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String YearToYearSuffixRequired = "({DateRangePrepositions})({DatePeriodYearInChineseRegex}|{YearRegex})\\s*({DatePeriodTillSuffixRequiredRegex})\\s*({DatePeriodYearInChineseRegex}|{YearRegex})\\s*(之间|之内|期间|中间|间)" + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodTillSuffixRequiredRegex}", DatePeriodTillSuffixRequiredRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String MonthToMonth = "({DateRangePrepositions})({MonthRegex}){DatePeriodTillRegex}({MonthRegex})" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodTillRegex}", DatePeriodTillRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String MonthToMonthSuffixRequired = "({DateRangePrepositions})({MonthRegex}){DatePeriodTillSuffixRequiredRegex}({MonthRegex})\\s*(之间|之内|期间|中间|间)" + .replace("{MonthRegex}", MonthRegex) + .replace("{DatePeriodTillSuffixRequiredRegex}", DatePeriodTillSuffixRequiredRegex) + .replace("{DateRangePrepositions}", DateRangePrepositions); + + public static final String PastRegex = "(?(之前|前|上|近|过去))"; + + public static final String FutureRegex = "(?(之后|之後|后|後|(?春|夏|秋|冬)(天|季)?"; + + public static final String SeasonWithYear = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?{SeasonRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex) + .replace("{SeasonRegex}", SeasonRegex); + + public static final String QuarterRegex = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(第(?1|2|3|4|一|二|三|四)季度)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String CenturyRegex = "(?\\d|1\\d|2\\d)世纪"; + + public static final String CenturyRegexInChinese = "(?一|二|三|四|五|六|七|八|九|十|十一|十二|十三|十四|十五|十六|十七|十八|十九|二十|二十一|二十二)世纪"; + + public static final String RelativeCenturyRegex = "(?({DatePeriodLastRegex}|{DatePeriodThisRegex}|{DatePeriodNextRegex}))世纪" + .replace("{DatePeriodLastRegex}", DatePeriodLastRegex) + .replace("{DatePeriodThisRegex}", DatePeriodThisRegex) + .replace("{DatePeriodNextRegex}", DatePeriodNextRegex); + + public static final String DecadeRegexInChinese = "(?十|一十|二十|三十|四十|五十|六十|七十|八十|九十)"; + + public static final String DecadeRegex = "(?({CenturyRegex}|{CenturyRegexInChinese}|{RelativeCenturyRegex}))?(?(\\d0|{DecadeRegexInChinese}))年代" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{CenturyRegexInChinese}", CenturyRegexInChinese) + .replace("{RelativeCenturyRegex}", RelativeCenturyRegex) + .replace("{DecadeRegexInChinese}", DecadeRegexInChinese); + + public static final String PrepositionRegex = "(?^的|在$)"; + + public static final String NowRegex = "(?现在|马上|立刻|刚刚才|刚刚|刚才|这会儿|当下|此刻)"; + + public static final String NightRegex = "(?早|晚)"; + + public static final String TimeOfTodayRegex = "(今晚|今早|今晨|明晚|明早|明晨|昨晚)(的|在)?"; + + public static final String DateTimePeriodTillRegex = "(?到|直到|--|-|—|——)"; + + public static final String DateTimePeriodPrepositionRegex = "(?^\\s*的|在\\s*$)"; + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String HourNumRegex = "(?[零〇一二两三四五六七八九]|二十[一二三四]?|十[一二三四五六七八九]?)"; + + public static final String ZhijianRegex = "^\\s*(之间|之内|期间|中间|间)"; + + public static final String DateTimePeriodThisRegex = "这个|这一个|这|这一"; + + public static final String DateTimePeriodLastRegex = "上个|上一个|上|上一"; + + public static final String DateTimePeriodNextRegex = "下个|下一个|下|下一"; + + public static final String AmPmDescRegex = "(?(am|a\\.m\\.|a m|a\\. m\\.|a\\.m|a\\. m|a m|pm|p\\.m\\.|p m|p\\. m\\.|p\\.m|p\\. m|p m))"; + + public static final String TimeOfDayRegex = "(?凌晨|清晨|早上|早间|早|上午|中午|下午|午后|晚上|夜里|夜晚|半夜|夜间|深夜|傍晚|晚)"; + + public static final String SpecificTimeOfDayRegex = "((({DateTimePeriodThisRegex}|{DateTimePeriodNextRegex}|{DateTimePeriodLastRegex})\\s+{TimeOfDayRegex})|(今晚|今早|今晨|明晚|明早|明晨|昨晚))" + .replace("{DateTimePeriodThisRegex}", DateTimePeriodThisRegex) + .replace("{DateTimePeriodNextRegex}", DateTimePeriodNextRegex) + .replace("{DateTimePeriodLastRegex}", DateTimePeriodLastRegex) + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String DateTimePeriodUnitRegex = "(个)?(?(小时|钟头|分钟|秒钟|时|分|秒))"; + + public static final String DateTimePeriodFollowedUnit = "^\\s*{DateTimePeriodUnitRegex}" + .replace("{DateTimePeriodUnitRegex}", DateTimePeriodUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){DateTimePeriodUnitRegex}" + .replace("{DateTimePeriodUnitRegex}", DateTimePeriodUnitRegex); + + public static final String DurationYearRegex = "((\\d{3,4})|0\\d|两千)\\s*年"; + + public static final String DurationHalfSuffixRegex = "半"; + + public static final ImmutableMap DurationSuffixList = ImmutableMap.builder() + .put("M", "分钟") + .put("S", "秒钟|秒") + .put("H", "个小时|小时|个钟头|钟头|时") + .put("D", "天") + .put("W", "星期|个星期|周") + .put("Mon", "个月") + .put("Y", "年") + .build(); + + public static final List DurationAmbiguousUnits = Arrays.asList("分钟", "秒钟", "秒", "个小时", "小时", "天", "星期", "个星期", "周", "个月", "年", "时"); + + public static final String DurationUnitRegex = "(?{DateUnitRegex}|分钟?|秒钟?|个?小时|时|个?钟头|天|个?星期|周|个?月|年)" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String DurationConnectorRegex = "^\\s*(?[多又余零]?)\\s*$"; + + public static final String LunarHolidayRegex = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?除夕|春节|中秋节|中秋|元宵节|端午节|端午|重阳节)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String HolidayRegexList1 = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?新年|五一|劳动节|元旦节|元旦|愚人节|平安夜|圣诞节|植树节|国庆节|情人节|教师节|儿童节|妇女节|青年节|建军节|女生节|光棍节|双十一|清明节|清明)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String HolidayRegexList2 = "(({YearRegex}|{DatePeriodYearInChineseRegex}|(?明年|今年|去年))(的)?)?(?母亲节|父亲节|感恩节|万圣节)" + .replace("{YearRegex}", YearRegex) + .replace("{DatePeriodYearInChineseRegex}", DatePeriodYearInChineseRegex); + + public static final String SetUnitRegex = "(?年|月|周|星期|日|天|小时|时|分钟|分|秒钟|秒)"; + + public static final String SetEachUnitRegex = "(?(每个|每一|每)\\s*{SetUnitRegex})" + .replace("{SetUnitRegex}", SetUnitRegex); + + public static final String SetEachPrefixRegex = "(?(每)\\s*$)"; + + public static final String SetLastRegex = "(?last|this|next)"; + + public static final String SetEachDayRegex = "(每|每一)(天|日)\\s*$"; + + public static final String TimeHourNumRegex = "(00|01|02|03|04|05|06|07|08|09|0|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeMinuteNumRegex = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeSecondNumRegex = "(00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String TimeHourChsRegex = "([零〇一二两三四五六七八九]|二十[一二三四]?|十[一二三四五六七八九]?)"; + + public static final String TimeMinuteChsRegex = "([二三四五]?十[一二三四五六七八九]?|六十|[零〇一二三四五六七八九])"; + + public static final String TimeSecondChsRegex = "{TimeMinuteChsRegex}" + .replace("{TimeMinuteChsRegex}", TimeMinuteChsRegex); + + public static final String TimeClockDescRegex = "(点\\s*整|点\\s*钟|点|时)"; + + public static final String TimeMinuteDescRegex = "(分钟|分|)"; + + public static final String TimeSecondDescRegex = "(秒钟|秒)"; + + public static final String TimeBanHourPrefixRegex = "(第)"; + + public static final String TimeHourRegex = "(?{TimeHourChsRegex}|{TimeHourNumRegex}){TimeClockDescRegex}" + .replace("{TimeBanHourPrefixRegex}", TimeBanHourPrefixRegex) + .replace("{TimeHourChsRegex}", TimeHourChsRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{TimeClockDescRegex}", TimeClockDescRegex); + + public static final String TimeMinuteRegex = "(?{TimeMinuteChsRegex}|{TimeMinuteNumRegex}){TimeMinuteDescRegex}" + .replace("{TimeMinuteChsRegex}", TimeMinuteChsRegex) + .replace("{TimeMinuteNumRegex}", TimeMinuteNumRegex) + .replace("{TimeMinuteDescRegex}", TimeMinuteDescRegex); + + public static final String TimeSecondRegex = "(?{TimeSecondChsRegex}|{TimeSecondNumRegex}){TimeSecondDescRegex}" + .replace("{TimeSecondChsRegex}", TimeSecondChsRegex) + .replace("{TimeSecondNumRegex}", TimeSecondNumRegex) + .replace("{TimeSecondDescRegex}", TimeSecondDescRegex); + + public static final String TimeHalfRegex = "(?过半|半)"; + + public static final String TimeQuarterRegex = "(?[一两二三四1-4])\\s*(刻钟|刻)"; + + public static final String TimeChineseTimeRegex = "{TimeHourRegex}({TimeQuarterRegex}|{TimeHalfRegex}|((过|又)?{TimeMinuteRegex})({TimeSecondRegex})?)?" + .replace("{TimeHourRegex}", TimeHourRegex) + .replace("{TimeQuarterRegex}", TimeQuarterRegex) + .replace("{TimeHalfRegex}", TimeHalfRegex) + .replace("{TimeMinuteRegex}", TimeMinuteRegex) + .replace("{TimeSecondRegex}", TimeSecondRegex); + + public static final String TimeDigitTimeRegex = "(?{TimeHourNumRegex}):(?{TimeMinuteNumRegex})(:(?{TimeSecondNumRegex}))?" + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{TimeMinuteNumRegex}", TimeMinuteNumRegex) + .replace("{TimeSecondNumRegex}", TimeSecondNumRegex); + + public static final String TimeDayDescRegex = "(?凌晨|清晨|早上|早间|早|上午|中午|下午|午后|晚上|夜里|夜晚|半夜|午夜|夜间|深夜|傍晚|晚)"; + + public static final String TimeApproximateDescPreffixRegex = "(大[约概]|差不多|可能|也许|约|不超过|不多[于过]|最[多长少]|少于|[超短长多]过|几乎要|将近|差点|快要|接近|至少|起码|超出|不到)"; + + public static final String TimeApproximateDescSuffixRegex = "(左右)"; + + public static final String TimeRegexes1 = "{TimeApproximateDescPreffixRegex}?{TimeDayDescRegex}?{TimeChineseTimeRegex}{TimeApproximateDescSuffixRegex}?" + .replace("{TimeApproximateDescPreffixRegex}", TimeApproximateDescPreffixRegex) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex) + .replace("{TimeApproximateDescSuffixRegex}", TimeApproximateDescSuffixRegex); + + public static final String TimeRegexes2 = "{TimeApproximateDescPreffixRegex}?{TimeDayDescRegex}?{TimeDigitTimeRegex}{TimeApproximateDescSuffixRegex}?(\\s*{AmPmDescRegex}?)" + .replace("{TimeApproximateDescPreffixRegex}", TimeApproximateDescPreffixRegex) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex) + .replace("{TimeApproximateDescSuffixRegex}", TimeApproximateDescSuffixRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex); + + public static final String TimeRegexes3 = "差{TimeMinuteRegex}{TimeChineseTimeRegex}" + .replace("{TimeMinuteRegex}", TimeMinuteRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodTimePeriodConnectWords = "(起|至|到|–|-|—|~|~)"; + + public static final String TimePeriodLeftChsTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeChineseTimeRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodRightChsTimeRegex = "{TimePeriodTimePeriodConnectWords}(?{TimeDayDescRegex}?{TimeChineseTimeRegex})(之间)?" + .replace("{TimePeriodTimePeriodConnectWords}", TimePeriodTimePeriodConnectWords) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeChineseTimeRegex}", TimeChineseTimeRegex); + + public static final String TimePeriodLeftDigitTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeDigitTimeRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex); + + public static final String TimePeriodRightDigitTimeRegex = "{TimePeriodTimePeriodConnectWords}(?{TimeDayDescRegex}?{TimeDigitTimeRegex})(之间)?" + .replace("{TimePeriodTimePeriodConnectWords}", TimePeriodTimePeriodConnectWords) + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeDigitTimeRegex}", TimeDigitTimeRegex); + + public static final String TimePeriodShortLeftChsTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeHourChsRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeHourChsRegex}", TimeHourChsRegex); + + public static final String TimePeriodShortLeftDigitTimeRegex = "(从)?(?{TimeDayDescRegex}?({TimeHourNumRegex}))" + .replace("{TimeDayDescRegex}", TimeDayDescRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex); + + public static final String TimePeriodRegexes1 = "({TimePeriodLeftDigitTimeRegex}{TimePeriodRightDigitTimeRegex}|{TimePeriodLeftChsTimeRegex}{TimePeriodRightChsTimeRegex})" + .replace("{TimePeriodLeftDigitTimeRegex}", TimePeriodLeftDigitTimeRegex) + .replace("{TimePeriodRightDigitTimeRegex}", TimePeriodRightDigitTimeRegex) + .replace("{TimePeriodLeftChsTimeRegex}", TimePeriodLeftChsTimeRegex) + .replace("{TimePeriodRightChsTimeRegex}", TimePeriodRightChsTimeRegex); + + public static final String TimePeriodRegexes2 = "({TimePeriodShortLeftDigitTimeRegex}{TimePeriodRightDigitTimeRegex}|{TimePeriodShortLeftChsTimeRegex}{TimePeriodRightChsTimeRegex})" + .replace("{TimePeriodShortLeftDigitTimeRegex}", TimePeriodShortLeftDigitTimeRegex) + .replace("{TimePeriodRightDigitTimeRegex}", TimePeriodRightDigitTimeRegex) + .replace("{TimePeriodShortLeftChsTimeRegex}", TimePeriodShortLeftChsTimeRegex) + .replace("{TimePeriodRightChsTimeRegex}", TimePeriodRightChsTimeRegex); + + public static final String FromToRegex = "(从|自).+([至到]).+"; + + public static final String AmbiguousRangeModifierPrefix = "(从|自)"; + + public static final String ParserConfigurationBefore = "((?和|或|及)?(之前|以前)|前)"; + + public static final String ParserConfigurationAfter = "((?和|或|及)?(之后|之後|以后|以後)|后|後)"; + + public static final String ParserConfigurationUntil = "(直到|直至|截至|截止(到)?)"; + + public static final String ParserConfigurationSincePrefix = "(自从|自|自打|打|从)"; + + public static final String ParserConfigurationSinceSuffix = "(以来|开始|起)"; + + public static final String ParserConfigurationLastWeekDayToken = "最后一个"; + + public static final String ParserConfigurationNextMonthToken = "下一个"; + + public static final String ParserConfigurationLastMonthToken = "上一个"; + + public static final String ParserConfigurationDatePrefix = " "; + + public static final ImmutableMap ParserConfigurationUnitMap = ImmutableMap.builder() + .put("年", "Y") + .put("月", "MON") + .put("个月", "MON") + .put("日", "D") + .put("周", "W") + .put("天", "D") + .put("小时", "H") + .put("个小时", "H") + .put("时", "H") + .put("分钟", "M") + .put("分", "M") + .put("秒钟", "S") + .put("秒", "S") + .put("星期", "W") + .put("个星期", "W") + .build(); + + public static final ImmutableMap ParserConfigurationUnitValueMap = ImmutableMap.builder() + .put("years", 31536000L) + .put("year", 31536000L) + .put("months", 2592000L) + .put("month", 2592000L) + .put("weeks", 604800L) + .put("week", 604800L) + .put("days", 86400L) + .put("day", 86400L) + .put("hours", 3600L) + .put("hour", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("seconds", 1L) + .put("second", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final List MonthTerms = Arrays.asList("月"); + + public static final List WeekendTerms = Arrays.asList("周末"); + + public static final List WeekTerms = Arrays.asList("周", "星期"); + + public static final List YearTerms = Arrays.asList("年"); + + public static final List ThisYearTerms = Arrays.asList("今年"); + + public static final List LastYearTerms = Arrays.asList("去年"); + + public static final List NextYearTerms = Arrays.asList("明年"); + + public static final List YearAfterNextTerms = Arrays.asList("后年"); + + public static final List YearBeforeLastTerms = Arrays.asList("前年"); + + public static final ImmutableMap ParserConfigurationSeasonMap = ImmutableMap.builder() + .put("春", "SP") + .put("夏", "SU") + .put("秋", "FA") + .put("冬", "WI") + .build(); + + public static final ImmutableMap ParserConfigurationSeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap ParserConfigurationCardinalMap = ImmutableMap.builder() + .put("一", 1) + .put("二", 2) + .put("三", 3) + .put("四", 4) + .put("五", 5) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("第一个", 1) + .put("第二个", 2) + .put("第三个", 3) + .put("第四个", 4) + .put("第五个", 5) + .put("第一", 1) + .put("第二", 2) + .put("第三", 3) + .put("第四", 4) + .put("第五", 5) + .build(); + + public static final ImmutableMap ParserConfigurationDayOfMonth = ImmutableMap.builder() + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("13", 13) + .put("14", 14) + .put("15", 15) + .put("16", 16) + .put("17", 17) + .put("18", 18) + .put("19", 19) + .put("20", 20) + .put("21", 21) + .put("22", 22) + .put("23", 23) + .put("24", 24) + .put("25", 25) + .put("26", 26) + .put("27", 27) + .put("28", 28) + .put("29", 29) + .put("30", 30) + .put("31", 31) + .put("1日", 1) + .put("2日", 2) + .put("3日", 3) + .put("4日", 4) + .put("5日", 5) + .put("6日", 6) + .put("7日", 7) + .put("8日", 8) + .put("9日", 9) + .put("10日", 10) + .put("11日", 11) + .put("12日", 12) + .put("13日", 13) + .put("14日", 14) + .put("15日", 15) + .put("16日", 16) + .put("17日", 17) + .put("18日", 18) + .put("19日", 19) + .put("20日", 20) + .put("21日", 21) + .put("22日", 22) + .put("23日", 23) + .put("24日", 24) + .put("25日", 25) + .put("26日", 26) + .put("27日", 27) + .put("28日", 28) + .put("29日", 29) + .put("30日", 30) + .put("31日", 31) + .put("一日", 1) + .put("十一日", 11) + .put("二十日", 20) + .put("十日", 10) + .put("二十一日", 21) + .put("三十一日", 31) + .put("二日", 2) + .put("三日", 3) + .put("四日", 4) + .put("五日", 5) + .put("六日", 6) + .put("七日", 7) + .put("八日", 8) + .put("九日", 9) + .put("十二日", 12) + .put("十三日", 13) + .put("十四日", 14) + .put("十五日", 15) + .put("十六日", 16) + .put("十七日", 17) + .put("十八日", 18) + .put("十九日", 19) + .put("二十二日", 22) + .put("二十三日", 23) + .put("二十四日", 24) + .put("二十五日", 25) + .put("二十六日", 26) + .put("二十七日", 27) + .put("二十八日", 28) + .put("二十九日", 29) + .put("三十日", 30) + .put("1号", 1) + .put("2号", 2) + .put("3号", 3) + .put("4号", 4) + .put("5号", 5) + .put("6号", 6) + .put("7号", 7) + .put("8号", 8) + .put("9号", 9) + .put("10号", 10) + .put("11号", 11) + .put("12号", 12) + .put("13号", 13) + .put("14号", 14) + .put("15号", 15) + .put("16号", 16) + .put("17号", 17) + .put("18号", 18) + .put("19号", 19) + .put("20号", 20) + .put("21号", 21) + .put("22号", 22) + .put("23号", 23) + .put("24号", 24) + .put("25号", 25) + .put("26号", 26) + .put("27号", 27) + .put("28号", 28) + .put("29号", 29) + .put("30号", 30) + .put("31号", 31) + .put("一号", 1) + .put("十一号", 11) + .put("二十号", 20) + .put("十号", 10) + .put("二十一号", 21) + .put("三十一号", 31) + .put("二号", 2) + .put("三号", 3) + .put("四号", 4) + .put("五号", 5) + .put("六号", 6) + .put("七号", 7) + .put("八号", 8) + .put("九号", 9) + .put("十二号", 12) + .put("十三号", 13) + .put("十四号", 14) + .put("十五号", 15) + .put("十六号", 16) + .put("十七号", 17) + .put("十八号", 18) + .put("十九号", 19) + .put("二十二号", 22) + .put("二十三号", 23) + .put("二十四号", 24) + .put("二十五号", 25) + .put("二十六号", 26) + .put("二十七号", 27) + .put("二十八号", 28) + .put("二十九号", 29) + .put("三十号", 30) + .put("初一", 32) + .put("三十", 30) + .put("一", 1) + .put("十一", 11) + .put("二十", 20) + .put("十", 10) + .put("二十一", 21) + .put("三十一", 31) + .put("二", 2) + .put("三", 3) + .put("四", 4) + .put("五", 5) + .put("六", 6) + .put("七", 7) + .put("八", 8) + .put("九", 9) + .put("十二", 12) + .put("十三", 13) + .put("十四", 14) + .put("十五", 15) + .put("十六", 16) + .put("十七", 17) + .put("十八", 18) + .put("十九", 19) + .put("二十二", 22) + .put("二十三", 23) + .put("二十四", 24) + .put("二十五", 25) + .put("二十六", 26) + .put("二十七", 27) + .put("二十八", 28) + .put("二十九", 29) + .build(); + + public static final ImmutableMap ParserConfigurationDayOfWeek = ImmutableMap.builder() + .put("星期一", 1) + .put("星期二", 2) + .put("星期三", 3) + .put("星期四", 4) + .put("星期五", 5) + .put("星期六", 6) + .put("星期天", 0) + .put("星期日", 0) + .put("礼拜一", 1) + .put("礼拜二", 2) + .put("礼拜三", 3) + .put("礼拜四", 4) + .put("礼拜五", 5) + .put("礼拜六", 6) + .put("礼拜天", 0) + .put("礼拜日", 0) + .put("周一", 1) + .put("周二", 2) + .put("周三", 3) + .put("周四", 4) + .put("周五", 5) + .put("周六", 6) + .put("周日", 0) + .put("周天", 0) + .put("禮拜一", 1) + .put("禮拜二", 2) + .put("禮拜三", 3) + .put("禮拜四", 4) + .put("禮拜五", 5) + .put("禮拜六", 6) + .put("禮拜天", 0) + .put("禮拜日", 0) + .put("週一", 1) + .put("週二", 2) + .put("週三", 3) + .put("週四", 4) + .put("週五", 5) + .put("週六", 6) + .put("週日", 0) + .put("週天", 0) + .build(); + + public static final ImmutableMap ParserConfigurationMonthOfYear = ImmutableMap.builder() + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .put("一月", 1) + .put("二月", 2) + .put("三月", 3) + .put("四月", 4) + .put("五月", 5) + .put("六月", 6) + .put("七月", 7) + .put("八月", 8) + .put("九月", 9) + .put("十月", 10) + .put("十一月", 11) + .put("十二月", 12) + .put("1月", 1) + .put("2月", 2) + .put("3月", 3) + .put("4月", 4) + .put("5月", 5) + .put("6月", 6) + .put("7月", 7) + .put("8月", 8) + .put("9月", 9) + .put("10月", 10) + .put("11月", 11) + .put("12月", 12) + .put("01月", 1) + .put("02月", 2) + .put("03月", 3) + .put("04月", 4) + .put("05月", 5) + .put("06月", 6) + .put("07月", 7) + .put("08月", 8) + .put("09月", 9) + .put("正月", 13) + .put("大年", 13) + .build(); + + public static final String DateTimeSimpleAmRegex = "(?早|晨)"; + + public static final String DateTimeSimplePmRegex = "(?晚)"; + + public static final String DateTimePeriodMORegex = "(凌晨|清晨|早上|早间|早|上午)"; + + public static final String DateTimePeriodMIRegex = "(中午)"; + + public static final String DateTimePeriodAFRegex = "(下午|午后|傍晚)"; + + public static final String DateTimePeriodEVRegex = "(晚上|夜里|夜晚|晚)"; + + public static final String DateTimePeriodNIRegex = "(半夜|夜间|深夜)"; + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("早", "(? DurationUnitValueMap = ImmutableMap.builder() + .put("Y", 31536000L) + .put("Mon", 2592000L) + .put("W", 604800L) + .put("D", 86400L) + .put("H", 3600L) + .put("M", 60L) + .put("S", 1L) + .build(); + + public static final ImmutableMap HolidayNoFixedTimex = ImmutableMap.builder() + .put("父亲节", "-06-WXX-6-3") + .put("母亲节", "-05-WXX-7-2") + .put("感恩节", "-11-WXX-4-4") + .build(); + + public static final String MergedBeforeRegex = "(前|之前)$"; + + public static final String MergedAfterRegex = "(后|後|之后|之後)$"; + + public static final ImmutableMap TimeNumberDictionary = ImmutableMap.builder() + .put('零', 0) + .put('一', 1) + .put('二', 2) + .put('三', 3) + .put('四', 4) + .put('五', 5) + .put('六', 6) + .put('七', 7) + .put('八', 8) + .put('九', 9) + .put('〇', 0) + .put('两', 2) + .put('十', 10) + .build(); + + public static final ImmutableMap TimeLowBoundDesc = ImmutableMap.builder() + .put("中午", 11) + .put("下午", 12) + .put("午后", 12) + .put("晚上", 18) + .put("夜里", 18) + .put("夜晚", 18) + .put("夜间", 18) + .put("深夜", 18) + .put("傍晚", 18) + .put("晚", 18) + .put("pm", 12) + .build(); + + public static final String DefaultLanguageFallback = "YMD"; + + public static final List MorningTermList = Arrays.asList("早", "上午", "早间", "早上", "清晨"); + + public static final List MidDayTermList = Arrays.asList("中午", "正午"); + + public static final List AfternoonTermList = Arrays.asList("下午", "午后"); + + public static final List EveningTermList = Arrays.asList("晚", "晚上", "夜里", "傍晚", "夜晚"); + + public static final List DaytimeTermList = Arrays.asList("白天", "日间"); + + public static final List NightTermList = Arrays.asList("深夜"); + + public static final ImmutableMap DynastyYearMap = ImmutableMap.builder() + .put("贞观", 627) + .put("开元", 713) + .put("神龙", 705) + .put("洪武", 1368) + .put("建文", 1399) + .put("永乐", 1403) + .put("景泰", 1450) + .put("天顺", 1457) + .put("成化", 1465) + .put("嘉靖", 1522) + .put("万历", 1573) + .put("崇祯", 1628) + .put("顺治", 1644) + .put("康熙", 1662) + .put("雍正", 1723) + .put("乾隆", 1736) + .put("嘉庆", 1796) + .put("道光", 1821) + .put("咸丰", 1851) + .put("同治", 1862) + .put("光绪", 1875) + .put("宣统", 1909) + .put("民国", 1912) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java new file mode 100644 index 000000000..d24b8ef6a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java @@ -0,0 +1,1446 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishDateTime { + + public static final String LangMarker = "Eng"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(to|(un)?till?|thru|through)\\b(\\s+the\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String RangeConnectorRegex = "(?\\b(and|through|to)\\b(\\s+the\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String LastNegPrefix = "(?following|next|(up)?coming|this|{LastNegPrefix}last|past|previous|current|the)\\b" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String StrictRelativeRegex = "\\b(?following|next|(up)?coming|this|{LastNegPrefix}last|past|previous|current)\\b" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String UpcomingPrefixRegex = "((this\\s+)?((up)?coming))"; + + public static final String NextPrefixRegex = "\\b(following|next|{UpcomingPrefixRegex})\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String AfterNextSuffixRegex = "\\b(after\\s+(the\\s+)?next)\\b"; + + public static final String PastPrefixRegex = "((this\\s+)?past)\\b"; + + public static final String PreviousPrefixRegex = "({LastNegPrefix}last|previous|{PastPrefixRegex})\\b" + .replace("{LastNegPrefix}", LastNegPrefix) + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "(this|current)\\b"; + + public static final String RangePrefixRegex = "(from|between)"; + + public static final String CenturySuffixRegex = "(^century)\\b"; + + public static final String ReferencePrefixRegex = "(that|same)\\b"; + + public static final String FutureSuffixRegex = "\\b(in\\s+the\\s+)?(future|hence)\\b"; + + public static final String DayRegex = "(the\\s*)?(?(?:3[0-1]|[1-2]\\d|0?[1-9])(?:th|nd|rd|st)?)(?=\\b|t)"; + + public static final String ImplicitDayRegex = "(the\\s*)?(?(?:3[0-1]|[0-2]?\\d)(?:th|nd|rd|st))\\b"; + + public static final String MonthNumRegex = "(?1[0-2]|(0)?[1-9])\\b"; + + public static final String WrittenOneToNineRegex = "(?:one|two|three|four|five|six|seven|eight|nine)"; + + public static final String WrittenElevenToNineteenRegex = "(?:eleven|twelve|(?:thir|four|fif|six|seven|eigh|nine)teen)"; + + public static final String WrittenTensRegex = "(?:ten|twenty|thirty|fou?rty|fifty|sixty|seventy|eighty|ninety)"; + + public static final String WrittenNumRegex = "(?:{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}(\\s+{WrittenOneToNineRegex})?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex); + + public static final String WrittenCenturyFullYearRegex = "(?:(one|two)\\s+thousand(\\s+and)?(\\s+{WrittenOneToNineRegex}\\s+hundred(\\s+and)?)?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String WrittenCenturyOrdinalYearRegex = "(?:twenty(\\s+(one|two))?|ten|eleven|twelve|thirteen|fifteen|eigthteen|(?:four|six|seven|nine)(teen)?|one|two|three|five|eight)"; + + public static final String CenturyRegex = "\\b(?{WrittenCenturyFullYearRegex}|{WrittenCenturyOrdinalYearRegex}(\\s+hundred)?(\\s+and)?)\\b" + .replace("{WrittenCenturyFullYearRegex}", WrittenCenturyFullYearRegex) + .replace("{WrittenCenturyOrdinalYearRegex}", WrittenCenturyOrdinalYearRegex); + + public static final String LastTwoYearNumRegex = "(?:zero\\s+{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}(\\s+{WrittenOneToNineRegex})?)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex); + + public static final String FullTextYearRegex = "\\b((?{CenturyRegex})\\s+(?{LastTwoYearNumRegex})\\b|\\b(?{WrittenCenturyFullYearRegex}|{WrittenCenturyOrdinalYearRegex}\\s+hundred(\\s+and)?))\\b" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{WrittenCenturyFullYearRegex}", WrittenCenturyFullYearRegex) + .replace("{WrittenCenturyOrdinalYearRegex}", WrittenCenturyOrdinalYearRegex) + .replace("{LastTwoYearNumRegex}", LastTwoYearNumRegex); + + public static final String OclockRegex = "(?o\\s*((’|‘|')\\s*)?clock|sharp)"; + + public static final String SpecialDescRegex = "((?)p\\b)"; + + public static final String AmDescRegex = "(?:{BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "(:?{BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "(:?{BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(:?(:?({OclockRegex}\\s+)?(?({AmPmDescRegex}|{AmDescRegex}|{PmDescRegex}|{SpecialDescRegex})))|{OclockRegex})" + .replace("{OclockRegex}", OclockRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex) + .replace("{SpecialDescRegex}", SpecialDescRegex); + + public static final String OfPrepositionRegex = "(\\bof\\b)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String YearRegex = "(?:{BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String WeekDayRegex = "\\b(?(?:sun|mon|tues?|thurs?|fri)(day)?|thu|wedn(esday)?|weds?|sat(urday)?)s?\\b"; + + public static final String SingleWeekDayRegex = "\\b(?(?((day\\s+)?of\\s+)?{RelativeRegex}\\s+month)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WrittenMonthRegex = "(((the\\s+)?month of\\s+)?(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?))"; + + public static final String MonthSuffixRegex = "(?(?:(in|of|on)\\s+)?({RelativeMonthRegex}|{WrittenMonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex); + + public static final String DateUnitRegex = "(?decades?|years?|months?|weeks?|(?(business\\s+|week\\s*))?days?|fortnights?|weekends?|(?<=\\s+\\d{1,4})[ymwd])\\b"; + + public static final String DateTokenPrefix = "on "; + + public static final String TimeTokenPrefix = "at "; + + public static final String TokenBeforeDate = "on "; + + public static final String TokenBeforeTime = "at "; + + public static final String FromRegex = "\\b(from(\\s+the)?)$"; + + public static final String BetweenTokenRegex = "\\b(between(\\s+the)?)$"; + + public static final String SimpleCasesRegex = "\\b({RangePrefixRegex}\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex}\\s+{MonthSuffixRegex}|{MonthSuffixRegex}\\s+{DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b({RangePrefixRegex}\\s+)?{MonthSuffixRegex}\\s+((from)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+(between\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{YearRegex}", YearRegex); + + public static final String BetweenRegex = "\\b(between\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthWithYear = "\\b(({WrittenMonthRegex}[\\.]?(\\s*)[/\\\\\\-\\.,]?(\\s+(of|in))?(\\s*)({YearRegex}|(?following|next|last|this)\\s+year))|(({YearRegex}|(?following|next|last|this)\\s+year)(\\s*),?(\\s*){WrittenMonthRegex}))\\b" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{YearRegex}", YearRegex); + + public static final String SpecialYearPrefixes = "(calendar|(?fiscal|school))"; + + public static final String OneWordPeriodRegex = "\\b((((the\\s+)?month of\\s+)?({StrictRelativeRegex}\\s+)?(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?))|(month|year) to date|(?((un)?till?|to)\\s+date)|({RelativeRegex}\\s+)?(my\\s+)?((?working\\s+week|workweek)|week(end)?|month|(({SpecialYearPrefixes}\\s+)?year))(?!((\\s+of)?\\s+\\d+(?!({BaseDateTime.BaseAmDescRegex}|{BaseDateTime.BasePmDescRegex}))|\\s+to\\s+date))(\\s+{AfterNextSuffixRegex})?)\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{AfterNextSuffixRegex}", AfterNextSuffixRegex) + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes) + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex) + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String MonthNumWithYear = "\\b(({BaseDateTime.FourDigitYearRegex}(\\s*)[/\\-\\.](\\s*){MonthNumRegex})|({MonthNumRegex}(\\s*)[/\\-](\\s*){BaseDateTime.FourDigitYearRegex}))\\b" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "\\b(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+week\\s+{MonthSuffixRegex}(\\s+{BaseDateTime.FourDigitYearRegex}|{RelativeRegex}\\s+year)?)\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WeekOfYearRegex = "\\b(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+week(\\s+of)?\\s+({YearRegex}|{RelativeRegex}\\s+year))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterTermRegex = "\\b(((?first|1st|second|2nd|third|3rd|fourth|4th)[ -]+quarter)|(q(?[1-4])))\\b"; + + public static final String RelativeQuarterTermRegex = "\\b(?{StrictRelativeRegex})\\s+quarter\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String QuarterRegex = "((the\\s+)?{QuarterTermRegex}(?:((\\s+of)?\\s+|\\s*[,-]\\s*)({YearRegex}|{RelativeRegex}\\s+year))?)|{RelativeQuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex) + .replace("{RelativeQuarterTermRegex}", RelativeQuarterTermRegex); + + public static final String QuarterRegexYearFront = "(?:{YearRegex}|{RelativeRegex}\\s+year)('s)?(?:\\s*-\\s*|\\s+(the\\s+)?)?{QuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex); + + public static final String HalfYearTermRegex = "(?first|1st|second|2nd)\\s+half"; + + public static final String HalfYearFrontRegex = "(?((1[5-9]|20)\\d{2})|2100)(\\s*-\\s*|\\s+(the\\s+)?)?h(?[1-2])" + .replace("{YearRegex}", YearRegex); + + public static final String HalfYearBackRegex = "(the\\s+)?(h(?[1-2])|({HalfYearTermRegex}))(\\s+of|\\s*,\\s*)?\\s+({YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{HalfYearTermRegex}", HalfYearTermRegex); + + public static final String HalfYearRelativeRegex = "(the\\s+)?{HalfYearTermRegex}(\\s+of|\\s*,\\s*)?\\s+({RelativeRegex}\\s+year)" + .replace("{RelativeRegex}", RelativeRegex) + .replace("{HalfYearTermRegex}", HalfYearTermRegex); + + public static final String AllHalfYearRegex = "({HalfYearFrontRegex})|({HalfYearBackRegex})|({HalfYearRelativeRegex})" + .replace("{HalfYearFrontRegex}", HalfYearFrontRegex) + .replace("{HalfYearBackRegex}", HalfYearBackRegex) + .replace("{HalfYearRelativeRegex}", HalfYearRelativeRegex); + + public static final String EarlyPrefixRegex = "\\b(?early|beginning of|start of|(?earlier(\\s+in)?))\\b"; + + public static final String MidPrefixRegex = "\\b(?mid-?|middle of)\\b"; + + public static final String LaterPrefixRegex = "\\b(?late|end of|(?later(\\s+in)?))\\b"; + + public static final String PrefixPeriodRegex = "({EarlyPrefixRegex}|{MidPrefixRegex}|{LaterPrefixRegex})" + .replace("{EarlyPrefixRegex}", EarlyPrefixRegex) + .replace("{MidPrefixRegex}", MidPrefixRegex) + .replace("{LaterPrefixRegex}", LaterPrefixRegex); + + public static final String PrefixDayRegex = "\\b((?early)|(?mid(dle)?)|(?later?))(\\s+in)?(\\s+the\\s+day)?$"; + + public static final String SeasonDescRegex = "(?spring|summer|fall|autumn|winter)"; + + public static final String SeasonRegex = "\\b(?({PrefixPeriodRegex}\\s+)?({RelativeRegex}\\s+)?{SeasonDescRegex}((\\s+of|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+year))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{SeasonDescRegex}", SeasonDescRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex); + + public static final String WhichWeekRegex = "\\b(week)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(the\\s+)?((week)(\\s+(of|(commencing|starting|beginning)(\\s+on)?))|w/c)(\\s+the)?"; + + public static final String MonthOfRegex = "(month)(\\s*)(of)"; + + public static final String MonthRegex = "(?apr(il)?|aug(ust)?|dec(ember)?|feb(ruary)?|jan(uary)?|july?|june?|mar(ch)?|may|nov(ember)?|oct(ober)?|sept(ember)?|sept?)"; + + public static final String DateYearRegex = "(?{BaseDateTime.FourDigitYearRegex}|(?(3[0-1]|[0-2]?\\d)(?:th|nd|rd|st))s?)\\b"; + + public static final String PrefixWeekDayRegex = "(\\s*((,?\\s*on)|[-—–]))"; + + public static final String ThisRegex = "\\b(this(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}((\\s+of)?\\s+this\\s*week))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String LastDateRegex = "\\b({PreviousPrefixRegex}(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}(\\s+(of\\s+)?last\\s*week))\\b" + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String NextDateRegex = "\\b({NextPrefixRegex}(\\s*week{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|((on\\s+)?{WeekDayRegex}((\\s+of)?\\s+(the\\s+following|(the\\s+)?next)\\s*week))\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String SpecialDayRegex = "\\b((the\\s+)?day before yesterday|(the\\s+)?day after (tomorrow|tmr)|the\\s+day\\s+(before|after)(?!=\\s+day)|((the\\s+)?({RelativeRegex}|my)\\s+day)|yesterday|tomorrow|tmr|today|otd)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String SpecialDayWithNumRegex = "\\b((?{WrittenNumRegex})\\s+days?\\s+from\\s+(?yesterday|tomorrow|tmr|today))\\b" + .replace("{WrittenNumRegex}", WrittenNumRegex); + + public static final String RelativeDayRegex = "\\b(((the\\s+)?{RelativeRegex}\\s+day))\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String SetWeekDayRegex = "\\b(?on\\s+)?(?morning|afternoon|evening|night|(sun|mon|tues|wednes|thurs|fri|satur)day)s\\b"; + + public static final String WeekDayOfMonthRegex = "(?(the\\s+)?(?first|1st|second|2nd|third|3rd|fourth|4th|fifth|5th|last)\\s+(week\\s+{MonthSuffixRegex}[\\.]?\\s+(on\\s+)?{WeekDayRegex}|{WeekDayRegex}\\s+{MonthSuffixRegex}))" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "\\b({WrittenNumRegex}\\s+{WeekDayRegex}\\s+(from\\s+now|later))\\b" + .replace("{WrittenNumRegex}", WrittenNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDate = "(?=\\b(on|at)\\s+the\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String DatePreposition = "\\b(on|in)"; + + public static final String DateExtractorYearTermRegex = "(\\s+|\\s*,\\s*|\\s+of\\s+){DateYearRegex}" + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}\\s*[,-]?\\s*)?(({MonthRegex}[\\.]?\\s*[/\\\\.,-]?\\s*{DayRegex})|(\\({MonthRegex}\\s*[-.]\\s*{DayRegex}\\)))(\\s*\\(\\s*{WeekDayRegex}\\s*\\))?({DateExtractorYearTermRegex}\\b)?" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}[\\.]?(\\s+|\\s*,\\s*|\\s+of\\s+|\\s*-\\s*){MonthRegex}[\\.]?((\\s+in)?{DateExtractorYearTermRegex})?\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor4 = "\\b{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}[\\.]?\\s*[/\\\\\\-]\\s*{DateYearRegex}" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor5 = "\\b{DayRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor6 = "(?<={DatePreposition}\\s+)({StrictRelativeRegex}\\s+)?({WeekDayRegex}\\s+)?{MonthNumRegex}[\\-\\.]{DayRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DatePreposition}", DatePreposition) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String DateExtractor7L = "\\b({WeekDayRegex}\\s+)?{MonthNumRegex}\\s*/\\s*{DayRegex}{DateExtractorYearTermRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor7S = "\\b({WeekDayRegex}\\s+)?{MonthNumRegex}\\s*/\\s*{DayRegex}(?![%])\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractor8 = "(?<={DatePreposition}\\s+)({StrictRelativeRegex}\\s+)?({WeekDayRegex}\\s+)?{DayRegex}[\\\\\\-]{MonthNumRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DatePreposition}", DatePreposition) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String DateExtractor9L = "\\b({WeekDayRegex}\\s+)?{DayRegex}\\s*/\\s*{MonthNumRegex}{DateExtractorYearTermRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateExtractorYearTermRegex}", DateExtractorYearTermRegex); + + public static final String DateExtractor9S = "\\b({WeekDayRegex}\\s+)?{DayRegex}\\s*/\\s*{MonthNumRegex}(?![%])\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractorA = "\\b({WeekDayRegex}\\s+)?{BaseDateTime.FourDigitYearRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DayRegex}" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String OfMonth = "^\\s*(day\\s+)?of\\s*{MonthRegex}" + .replace("{MonthRegex}", MonthRegex); + + public static final String MonthEnd = "{MonthRegex}\\s*(the)?\\s*$" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "(this\\s+)?{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String RangeUnitRegex = "\\b(?years?|months?|weeks?)\\b"; + + public static final String HourNumRegex = "\\b(?zero|one|two|three|four|five|six|seven|eight|nine|ten|eleven|twelve)\\b"; + + public static final String MinuteNumRegex = "(?ten|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|twenty|thirty|forty|fifty|one|two|three|five|eight)"; + + public static final String DeltaMinuteNumRegex = "(?ten|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|twenty|thirty|forty|fifty|one|two|three|five|eight)"; + + public static final String PmRegex = "(?(((?:at|in|around|on|for)\\s+(the\\s+)?)?(afternoon|evening|midnight|lunchtime))|((at|in|around|on|for)\\s+(the\\s+)?night))"; + + public static final String PmRegexFull = "(?((?:at|in|around|on|for)\\s+(the\\s+)?)?(afternoon|evening|(mid)?night|lunchtime))"; + + public static final String AmRegex = "(?((?:at|in|around|on|for)\\s+(the\\s+)?)?(morning))"; + + public static final String LunchRegex = "\\blunchtime\\b"; + + public static final String NightRegex = "\\b(mid)?night\\b"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String LessThanOneHour = "(?(a\\s+)?quarter|three quarter(s)?|half( an hour)?|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutes?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutes?|mins?)))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s+({MinuteNumRegex}|(?twenty|thirty|fou?rty|fifty)\\s+{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}\\s+(past|to))" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?{AmRegex}|{PmRegex}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeSuffixFull = "(?{AmRegex}|{PmRegexFull}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegexFull}", PmRegexFull) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "\\b(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex}(?![%\\d]))" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidnightRegex = "(?mid\\s*(-\\s*)?night)"; + + public static final String MidmorningRegex = "(?mid\\s*(-\\s*)?morning)"; + + public static final String MidafternoonRegex = "(?mid\\s*(-\\s*)?afternoon)"; + + public static final String MiddayRegex = "(?mid\\s*(-\\s*)?day|((12\\s)?noon))"; + + public static final String MidTimeRegex = "(?({MidnightRegex}|{MidmorningRegex}|{MidafternoonRegex}|{MiddayRegex}))" + .replace("{MidnightRegex}", MidnightRegex) + .replace("{MidmorningRegex}", MidmorningRegex) + .replace("{MidafternoonRegex}", MidafternoonRegex) + .replace("{MiddayRegex}", MiddayRegex); + + public static final String AtRegex = "\\b(?:(?:(?<=\\bat\\s+)(?:{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}(?!\\.\\d)(\\s*((?a)|(?p)))?|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String IshRegex = "\\b({BaseDateTime.HourRegex}(-|——)?ish|noon(ish)?)\\b" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimeUnitRegex = "([^A-Za-z]{1,}|\\b)(?h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?)\\b"; + + public static final String RestrictedTimeUnitRegex = "(?hour|minute)\\b"; + + public static final String FivesRegex = "(?(?:fifteen|(?:twen|thir|fou?r|fif)ty(\\s*five)?|ten|five))\\b"; + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String PeriodHourNumRegex = "\\b(?twenty(\\s+(one|two|three|four))?|eleven|twelve|thirteen|fifteen|eighteen|(four|six|seven|nine)(teen)?|zero|one|two|three|five|eight|ten)\\b"; + + public static final String ConnectNumRegex = "\\b{BaseDateTime.HourRegex}(?[0-5][0-9])\\s*{DescRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegexWithDotConnector = "({BaseDateTime.HourRegex}(\\s*\\.\\s*){BaseDateTime.MinuteRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex); + + public static final String TimeRegex1 = "\\b({TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s*|[.]){DescRegex}" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?(?a)?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b{TimePrefix}\\s+{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex5 = "\\b{TimePrefix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex6 = "{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffixFull}\\s+(at\\s+)?{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffixFull}", TimeSuffixFull) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = ".^" + .replace("{TimeSuffixFull}", TimeSuffixFull) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b{PeriodHourNumRegex}(\\s+|-){FivesRegex}((\\s*{DescRegex})|\\b)" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{FivesRegex}", FivesRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex10 = "\\b({TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex11 = "\\b((?:({TimeTokenPrefix})?{TimeRegexWithDotConnector}(\\s*{DescRegex}))|(?:(?:{TimeTokenPrefix}{TimeRegexWithDotConnector})(?!\\s*per\\s*cent|%)))" + .replace("{TimeTokenPrefix}", TimeTokenPrefix) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex); + + public static final String FirstTimeRegexInTimeRange = "\\b{TimeRegexWithDotConnector}(\\s*{DescRegex})?" + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex); + + public static final String PureNumFromTo = "({RangePrefixRegex}\\s+)?({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?\\s*{TillRegex}\\s*({HourRegex}|{PeriodHourNumRegex})(?\\s*({PmRegex}|{AmRegex}|{DescRegex}))?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String PureNumBetweenAnd = "(between\\s+)(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?\\s*{RangeConnectorRegex}\\s*(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{HourRegex}|{PeriodHourNumRegex})(?\\s*({PmRegex}|{AmRegex}|{DescRegex}))?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{BaseDateTime.TwoDigitHourRegex}", BaseDateTime.TwoDigitHourRegex) + .replace("{BaseDateTime.TwoDigitMinuteRegex}", BaseDateTime.TwoDigitMinuteRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String SpecificTimeFromTo = "({RangePrefixRegex}\\s+)?(?(({TimeRegex2}|{FirstTimeRegexInTimeRange})|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{TillRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(?\\s*{DescRegex}))|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{FirstTimeRegexInTimeRange}", FirstTimeRegexInTimeRange) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{TillRegex}", TillRegex) + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String SpecificTimeBetweenAnd = "(between\\s+)(?(({TimeRegex2}|{FirstTimeRegexInTimeRange})|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{RangeConnectorRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(?\\s*{DescRegex}))|({HourRegex}|{PeriodHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{FirstTimeRegexInTimeRange}", FirstTimeRegexInTimeRange) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String SuffixAfterRegex = "\\b(((at)\\s)?(or|and)\\s+(above|after|later|greater)(?!\\s+than))\\b"; + + public static final String PrepositionRegex = "(?^(at|on|of)(\\s+the)?$)"; + + public static final String LaterEarlyRegex = "((?early(\\s+|-))|(?late(r?\\s+|-)))"; + + public static final String MealTimeRegex = "\\b(at\\s+)?(?breakfast|brunch|lunch(\\s*time)?|dinner(\\s*time)?|supper)\\b"; + + public static final String UnspecificTimePeriodRegex = "({MealTimeRegex})" + .replace("{MealTimeRegex}", MealTimeRegex); + + public static final String TimeOfDayRegex = "\\b(?((((in\\s+the\\s+)?{LaterEarlyRegex}?(in(\\s+the)?\\s+)?(morning|afternoon|night|evening)))|{MealTimeRegex}|(((in\\s+(the)?\\s+)?)(daytime|business\\s+hour)))s?)\\b" + .replace("{LaterEarlyRegex}", LaterEarlyRegex) + .replace("{MealTimeRegex}", MealTimeRegex); + + public static final String SpecificTimeOfDayRegex = "\\b(({StrictRelativeRegex}\\s+{TimeOfDayRegex})\\b|\\btoni(ght|te))s?\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final List BusinessHourSplitStrings = Arrays.asList("business", "hour"); + + public static final String NowRegex = "\\b(?(right\\s+)?now|at th(e|is) minute|as soon as possible|asap|recently|previously)\\b"; + + public static final String NowParseRegex = "\\b({NowRegex}|^(date)$)\\b" + .replace("{NowRegex}", NowRegex); + + public static final String SuffixRegex = "^\\s*(in the\\s+)?(morning|afternoon|evening|night)\\b"; + + public static final String NonTimeContextTokens = "(building)"; + + public static final String DateTimeTimeOfDayRegex = "\\b(?morning|(?afternoon|night|evening))\\b"; + + public static final String DateTimeSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{DateTimeTimeOfDayRegex})\\b|\\btoni(ght|te))\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?(in\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+(at|around|in|on))?\\s*$" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "(?{DateUnitRegex}|h(ou)?rs?|h|min(ute)?s?|sec(ond)?s?|nights?)\\b" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String SuffixAndRegex = "(?\\s*(and)\\s+(an?\\s+)?(?half|quarter))"; + + public static final String PeriodicRegex = "\\b(?((?semi|bi|tri)(\\s*|-))?(daily|monthly|weekly|quarterly|yearly|annual(ly)?))\\b"; + + public static final String EachUnitRegex = "\\b(?(each|every|any|once an?)(?\\s+other)?\\s+({DurationUnitRegex}|(?quarters?|weekends?)|{WeekDayRegex})|(?weekends))" + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String EachPrefixRegex = "\\b(?(each|every|once an?)\\s*$)"; + + public static final String SetEachRegex = "\\b(?(each|every)(?\\s+other)?\\s*)(?!the|that)\\b"; + + public static final String SetLastRegex = "(?following|next|upcoming|this|{LastNegPrefix}last|past|previous|current)" + .replace("{LastNegPrefix}", LastNegPrefix); + + public static final String EachDayRegex = "^\\s*(each|every)\\s*day\\b"; + + public static final String DurationFollowedUnit = "(^\\s*{DurationUnitRegex}\\s+{SuffixAndRegex})|(^\\s*{SuffixAndRegex}?(\\s+|-)?{DurationUnitRegex})" + .replace("{SuffixAndRegex}", SuffixAndRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String NumberCombinedWithDurationUnit = "\\b(?\\d+(\\.\\d*)?)(-)?{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String AnUnitRegex = "(\\b((?(half)\\s+)?an?|another)|(?(1/2|½|half)))\\s+{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String DuringRegex = "\\b(for|during)\\s+the\\s+(?year|month|week|day)\\b"; + + public static final String AllRegex = "\\b(?(all|full|whole)(\\s+|-)(?year|month|week|day))\\b"; + + public static final String HalfRegex = "((an?\\s*)|\\b)(?half\\s+(?year|month|week|day|hour))\\b"; + + public static final String ConjunctionRegex = "\\b((and(\\s+for)?)|with)\\b"; + + public static final String HolidayList1 = "(?mardi gras|(washington|mao)'s birthday|juneteenth|(jubilee|freedom)(\\s+day)|chinese new year|(new\\s+(years'|year\\s*'s|years?)\\s+eve)|(new\\s+(years'|year\\s*'s|years?)(\\s+day)?)|may\\s*day|yuan dan|christmas eve|(christmas|xmas)(\\s+day)?|black friday|yuandan|easter(\\s+(sunday|saturday|monday))?|clean monday|ash wednesday|palm sunday|maundy thursday|good friday|white\\s+(sunday|monday)|trinity sunday|pentecost|corpus christi|cyber monday)"; + + public static final String HolidayList2 = "(?(thanks\\s*giving|all saint's|white lover|s(?:ain)?t?(\\.)?\\s+(?:patrick|george)(?:')?(?:s)?|us independence|all hallow|all souls|guy fawkes|cinco de mayo|halloween|qingming|dragon boat|april fools|tomb\\s*sweeping)(\\s+day)?)"; + + public static final String HolidayList3 = "(?(?:independence|presidents(?:')?|mlk|martin luther king( jr)?|canberra|ascension|columbus|tree( planting)?|arbor|labou?r|((international|int'?l)\\s+)?workers'?|mother'?s?|father'?s?|female|women('s)?|single|teacher'?s|youth|children|girls|lovers?|earth|inauguration|groundhog|valentine'?s|baptiste|bastille|veterans(?:')?|memorial|mid[ \\-]autumn|moon|spring|lantern)\\s+day)"; + + public static final String HolidayRegex = "\\b(({StrictRelativeRegex}\\s+({HolidayList1}|{HolidayList2}|{HolidayList3}))|(({HolidayList1}|{HolidayList2}|{HolidayList3})(\\s+(of\\s+)?({YearRegex}|{RelativeRegex}\\s+year))?))\\b" + .replace("{HolidayList1}", HolidayList1) + .replace("{HolidayList2}", HolidayList2) + .replace("{HolidayList3}", HolidayList3) + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String AMTimeRegex = "(?morning)"; + + public static final String PMTimeRegex = "\\b(?afternoon|evening|night)\\b"; + + public static final String NightTimeRegex = "(night)"; + + public static final String NowTimeRegex = "(now|at th(e|is) minute)"; + + public static final String RecentlyTimeRegex = "(recently|previously)"; + + public static final String AsapTimeRegex = "(as soon as possible|asap)"; + + public static final String InclusiveModPrepositions = "(?((on|in|at)\\s+or\\s+)|(\\s+or\\s+(on|in|at)))"; + + public static final String AroundRegex = "(?:\\b(?:around|circa)\\s*?\\b)(\\s+the)?"; + + public static final String BeforeRegex = "((\\b{InclusiveModPrepositions}?(?:before|in\\s+advance\\s+of|prior\\s+to|(no\\s+later|earlier|sooner)\\s+than|ending\\s+(with|on)|by|(un)?till?|(?as\\s+late\\s+as)){InclusiveModPrepositions}?\\b\\s*?)|(?)((?<\\s*=)|<))(\\s+the)?" + .replace("{InclusiveModPrepositions}", InclusiveModPrepositions); + + public static final String AfterRegex = "((\\b{InclusiveModPrepositions}?((after|(starting|beginning)(\\s+on)?(?!\\sfrom)|(?>\\s*=)|>))(\\s+the)?" + .replace("{InclusiveModPrepositions}", InclusiveModPrepositions); + + public static final String SinceRegex = "(?:(?:\\b(?:since|after\\s+or\\s+equal\\s+to|starting\\s+(?:from|on|with)|as\\s+early\\s+as|(any\\s+time\\s+)from)\\b\\s*?)|(?=))(\\s+the)?"; + + public static final String SinceRegexExp = "({SinceRegex}|\\bfrom(\\s+the)?\\b)" + .replace("{SinceRegex}", SinceRegex); + + public static final String AgoRegex = "\\b(ago|before\\s+(?yesterday|today))\\b"; + + public static final String LaterRegex = "\\b(?:later(?!((\\s+in)?\\s*{OneWordPeriodRegex})|(\\s+{TimeOfDayRegex})|\\s+than\\b)|from now|(from|after)\\s+(?tomorrow|tmr|today))\\b" + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String BeforeAfterRegex = "\\b((?before)|(?from|after))\\b"; + + public static final String InConnectorRegex = "\\b(in)\\b"; + + public static final String SinceYearSuffixRegex = "(^\\s*{SinceRegex}(\\s*(the\\s+)?year\\s*)?{YearSuffix})" + .replace("{SinceRegex}", SinceRegex) + .replace("{YearSuffix}", YearSuffix); + + public static final String WithinNextPrefixRegex = "\\b(within(\\s+the)?(\\s+(?{NextPrefixRegex}))?)\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex); + + public static final String TodayNowRegex = "\\b(today|now)\\b"; + + public static final String MorningStartEndRegex = "(^(morning|{AmDescRegex}))|((morning|{AmDescRegex})$)" + .replace("{AmDescRegex}", AmDescRegex); + + public static final String AfternoonStartEndRegex = "(^(afternoon|{PmDescRegex}))|((afternoon|{PmDescRegex})$)" + .replace("{PmDescRegex}", PmDescRegex); + + public static final String EveningStartEndRegex = "(^(evening))|((evening)$)"; + + public static final String NightStartEndRegex = "(^(over|to)?ni(ght|te))|((over|to)?ni(ght|te)$)"; + + public static final String InexactNumberRegex = "\\b((a\\s+)?few|some|several|(?(a\\s+)?couple(\\s+of)?))\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+({DurationUnitRegex})" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String RelativeTimeUnitRegex = "(?:(?:(?:{NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+({TimeUnitRegex}))|((the|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{TimeUnitRegex}", TimeUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String RelativeDurationUnitRegex = "(?:(?:(?<=({NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+)({DurationUnitRegex}))|((the|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String ReferenceDatePeriodRegex = "\\b{ReferencePrefixRegex}\\s+(?week|month|year|decade|weekend)\\b" + .replace("{ReferencePrefixRegex}", ReferencePrefixRegex); + + public static final String ConnectorRegex = "^(-|,|for|t|around|@)$"; + + public static final String FromToRegex = "(\\b(from).+(to|and|or)\\b.+)"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String SingleAmbiguousTermsRegex = "^(the\\s+)?(day|week|month|year)$"; + + public static final String UnspecificDatePeriodRegex = "^(week|month|year)$"; + + public static final String PrepositionSuffixRegex = "\\b(on|in|at|around|from|to)$"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?[A-Za-z\\d]+)"; + + public static final String ForTheRegex = "\\b((((?<=for\\s+)the\\s+{FlexibleDayRegex})|((?<=on\\s+)(the\\s+)?{FlexibleDayRegex}(?<=(st|nd|rd|th))))(?\\s*(,|\\.(?!\\d)|!|\\?|$)))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+(the\\s+{FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+(?!(the)){DayRegex}(?!([-:]|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String RestOfDateRegex = "\\b(rest|remaining)\\s+(of\\s+)?((the|my|this|current)\\s+)?(?week|month|year|decade)\\b"; + + public static final String RestOfDateTimeRegex = "\\b(rest|remaining)\\s+(of\\s+)?((the|my|this|current)\\s+)?(?day)\\b"; + + public static final String AmbiguousRangeModifierPrefix = "(from)"; + + public static final String NumberEndingPattern = "^(?:\\s+(?meeting|appointment|conference|((skype|teams|zoom|facetime)\\s+)?call)\\s+to\\s+(?{PeriodHourNumRegex}|{HourRegex})([\\.]?$|(\\.,|,|!|\\?)))" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{HourRegex}", HourRegex); + + public static final String OneOnOneRegex = "\\b(1\\s*:\\s*1(?!\\d))|(one (on )?one|one\\s*-\\s*one|one\\s*:\\s*one)\\b"; + + public static final String LaterEarlyPeriodRegex = "\\b(({PrefixPeriodRegex})\\s*\\b\\s*(?{OneWordPeriodRegex}|(?{BaseDateTime.FourDigitYearRegex}))|({UnspecificEndOfRangeRegex}))\\b" + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex) + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{UnspecificEndOfRangeRegex}", UnspecificEndOfRangeRegex); + + public static final String WeekWithWeekDayRangeRegex = "\\b((?({NextPrefixRegex}|{PreviousPrefixRegex}|this)\\s+week)((\\s+between\\s+{WeekDayRegex}\\s+and\\s+{WeekDayRegex})|(\\s+from\\s+{WeekDayRegex}\\s+to\\s+{WeekDayRegex})))\\b" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String GeneralEndingRegex = "^\\s*((\\.,)|\\.|,|!|\\?)?\\s*$"; + + public static final String MiddlePauseRegex = "\\s*(,)\\s*"; + + public static final String DurationConnectorRegex = "^\\s*(?\\s+|and|,)\\s*$"; + + public static final String PrefixArticleRegex = "\\bthe\\s+"; + + public static final String OrRegex = "\\s*((\\b|,\\s*)(or|and)\\b|,)\\s*"; + + public static final String SpecialYearTermsRegex = "\\b((({SpecialYearPrefixes}\\s+)?year)|(cy|(?fy|sy)))" + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes); + + public static final String YearPlusNumberRegex = "\\b({SpecialYearTermsRegex}\\s*((?(\\d{2,4}))|{FullTextYearRegex}))\\b" + .replace("{FullTextYearRegex}", FullTextYearRegex) + .replace("{SpecialYearTermsRegex}", SpecialYearTermsRegex); + + public static final String NumberAsTimeRegex = "\\b({WrittenTimeRegex}|{PeriodHourNumRegex}|{BaseDateTime.HourRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimeBeforeAfterRegex = "\\b(((?<=\\b(before|no later than|by|after)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String DateNumberConnectorRegex = "^\\s*(?\\s+at)\\s*$"; + + public static final String DecadeRegex = "(?(?:nough|twen|thir|fou?r|fif|six|seven|eight|nine)ties|two\\s+thousands)"; + + public static final String DecadeWithCenturyRegex = "(the\\s+)?(((?\\d|1\\d|2\\d)?(')?(?\\d0)(')?(\\s)?s\\b)|(({CenturyRegex}(\\s+|-)(and\\s+)?)?{DecadeRegex})|({CenturyRegex}(\\s+|-)(and\\s+)?(?tens|hundreds)))" + .replace("{CenturyRegex}", CenturyRegex) + .replace("{DecadeRegex}", DecadeRegex); + + public static final String RelativeDecadeRegex = "\\b((the\\s+)?{RelativeRegex}\\s+((?[\\w,]+)\\s+)?decades?)\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String YearPeriodRegex = "((((from|during|in)\\s+)?{YearRegex}\\s*({TillRegex})\\s*{YearRegex})|(((between)\\s+){YearRegex}\\s*({RangeConnectorRegex})\\s*{YearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{TillRegex}", TillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String StrictTillRegex = "(?\\b(to|(un)?till?|thru|through)\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*(h[1-2]|q[1-4])(?!(\\s+of|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StrictRangeConnectorRegex = "(?\\b(and|through|to)\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*(h[1-2]|q[1-4])(?!(\\s+of|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StartMiddleEndRegex = "\\b((?((the\\s+)?(start|beginning)\\s+of\\s+)?)(?((the\\s+)?middle\\s+of\\s+)?)(?((the\\s+)?end\\s+of\\s+)?))"; + + public static final String ComplexDatePeriodRegex = "(?:((from|during|in)\\s+)?{StartMiddleEndRegex}(?.+)\\s*({StrictTillRegex})\\s*{StartMiddleEndRegex}(?.+)|((between)\\s+){StartMiddleEndRegex}(?.+)\\s*({StrictRangeConnectorRegex})\\s*{StartMiddleEndRegex}(?.+))" + .replace("{StrictTillRegex}", StrictTillRegex) + .replace("{StrictRangeConnectorRegex}", StrictRangeConnectorRegex) + .replace("{StartMiddleEndRegex}", StartMiddleEndRegex); + + public static final String FailFastRegex = "{BaseDateTime.DeltaMinuteRegex}|\\b(?:{BaseDateTime.BaseAmDescRegex}|{BaseDateTime.BasePmDescRegex})|{BaseDateTime.BaseAmPmDescRegex}|\\b(?:zero|{WrittenOneToNineRegex}|{WrittenElevenToNineteenRegex}|{WrittenTensRegex}|{WrittenMonthRegex}|{SeasonDescRegex}|{DecadeRegex}|centur(y|ies)|weekends?|quarters?|hal(f|ves)|yesterday|to(morrow|day|night)|tmr|noonish|\\d(-|——)?ish|((the\\s+\\w*)|\\d)(th|rd|nd|st)|(mid\\s*(-\\s*)?)?(night|morning|afternoon|day)s?|evenings?||noon|lunch(time)?|dinner(time)?|(day|night)time|overnight|dawn|dusk|sunset|hours?|hrs?|h|minutes?|mins?|seconds?|secs?|eo[dmy]|mardi[ -]?gras|birthday|eve|christmas|xmas|thanksgiving|halloween|yuandan|easter|yuan dan|april fools|cinco de mayo|all (hallow|souls)|guy fawkes|(st )?patrick|hundreds?|noughties|aughts|thousands?)\\b|{WeekDayRegex}|{SetWeekDayRegex}|{NowRegex}|{PeriodicRegex}|\\b({DateUnitRegex}|{ImplicitDayRegex})" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex) + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex) + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex) + .replace("{ImplicitDayRegex}", ImplicitDayRegex) + .replace("{DateUnitRegex}", DateUnitRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{SetWeekDayRegex}", SetWeekDayRegex) + .replace("{NowRegex}", NowRegex) + .replace("{PeriodicRegex}", PeriodicRegex) + .replace("{DecadeRegex}", DecadeRegex) + .replace("{SeasonDescRegex}", SeasonDescRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{WrittenTensRegex}", WrittenTensRegex) + .replace("{WrittenElevenToNineteenRegex}", WrittenElevenToNineteenRegex) + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("decades", "10Y") + .put("decade", "10Y") + .put("years", "Y") + .put("year", "Y") + .put("months", "MON") + .put("month", "MON") + .put("quarters", "3MON") + .put("quarter", "3MON") + .put("semesters", "6MON") + .put("semestres", "6MON") + .put("semester", "6MON") + .put("semestre", "6MON") + .put("weeks", "W") + .put("week", "W") + .put("weekends", "WE") + .put("weekend", "WE") + .put("fortnights", "2W") + .put("fortnight", "2W") + .put("weekdays", "D") + .put("weekday", "D") + .put("days", "D") + .put("day", "D") + .put("nights", "D") + .put("night", "D") + .put("hours", "H") + .put("hour", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutes", "M") + .put("minute", "M") + .put("mins", "M") + .put("min", "M") + .put("seconds", "S") + .put("second", "S") + .put("secs", "S") + .put("sec", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("decades", 315360000L) + .put("decade", 315360000L) + .put("years", 31536000L) + .put("year", 31536000L) + .put("months", 2592000L) + .put("month", 2592000L) + .put("fortnights", 1209600L) + .put("fortnight", 1209600L) + .put("weekends", 172800L) + .put("weekend", 172800L) + .put("weeks", 604800L) + .put("week", 604800L) + .put("days", 86400L) + .put("day", 86400L) + .put("nights", 86400L) + .put("night", 86400L) + .put("hours", 3600L) + .put("hour", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("seconds", 1L) + .put("second", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("fiscal", "FY") + .put("school", "SY") + .put("fy", "FY") + .put("sy", "SY") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("spring", "SP") + .put("summer", "SU") + .put("fall", "FA") + .put("autumn", "FA") + .put("winter", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("first", 1) + .put("1st", 1) + .put("second", 2) + .put("2nd", 2) + .put("third", 3) + .put("3rd", 3) + .put("fourth", 4) + .put("4th", 4) + .put("fifth", 5) + .put("5th", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("monday", 1) + .put("tuesday", 2) + .put("wednesday", 3) + .put("thursday", 4) + .put("friday", 5) + .put("saturday", 6) + .put("sunday", 0) + .put("mon", 1) + .put("tue", 2) + .put("tues", 2) + .put("wed", 3) + .put("wedn", 3) + .put("weds", 3) + .put("thu", 4) + .put("thur", 4) + .put("thurs", 4) + .put("fri", 5) + .put("sat", 6) + .put("sun", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("january", 1) + .put("february", 2) + .put("march", 3) + .put("april", 4) + .put("may", 5) + .put("june", 6) + .put("july", 7) + .put("august", 8) + .put("september", 9) + .put("october", 10) + .put("november", 11) + .put("december", 12) + .put("jan", 1) + .put("feb", 2) + .put("mar", 3) + .put("apr", 4) + .put("jun", 6) + .put("jul", 7) + .put("aug", 8) + .put("sep", 9) + .put("sept", 9) + .put("oct", 10) + .put("nov", 11) + .put("dec", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("one", 1) + .put("a", 1) + .put("an", 1) + .put("two", 2) + .put("three", 3) + .put("four", 4) + .put("five", 5) + .put("six", 6) + .put("seven", 7) + .put("eight", 8) + .put("nine", 9) + .put("ten", 10) + .put("eleven", 11) + .put("twelve", 12) + .put("thirteen", 13) + .put("fourteen", 14) + .put("fifteen", 15) + .put("sixteen", 16) + .put("seventeen", 17) + .put("eighteen", 18) + .put("nineteen", 19) + .put("twenty", 20) + .put("twenty one", 21) + .put("twenty two", 22) + .put("twenty three", 23) + .put("twenty four", 24) + .put("twenty five", 25) + .put("twenty six", 26) + .put("twenty seven", 27) + .put("twenty eight", 28) + .put("twenty nine", 29) + .put("thirty", 30) + .put("thirty one", 31) + .put("thirty two", 32) + .put("thirty three", 33) + .put("thirty four", 34) + .put("thirty five", 35) + .put("thirty six", 36) + .put("thirty seven", 37) + .put("thirty eight", 38) + .put("thirty nine", 39) + .put("forty", 40) + .put("forty one", 41) + .put("forty two", 42) + .put("forty three", 43) + .put("forty four", 44) + .put("forty five", 45) + .put("forty six", 46) + .put("forty seven", 47) + .put("forty eight", 48) + .put("forty nine", 49) + .put("fifty", 50) + .put("fifty one", 51) + .put("fifty two", 52) + .put("fifty three", 53) + .put("fifty four", 54) + .put("fifty five", 55) + .put("fifty six", 56) + .put("fifty seven", 57) + .put("fifty eight", 58) + .put("fifty nine", 59) + .put("sixty", 60) + .put("sixty one", 61) + .put("sixty two", 62) + .put("sixty three", 63) + .put("sixty four", 64) + .put("sixty five", 65) + .put("sixty six", 66) + .put("sixty seven", 67) + .put("sixty eight", 68) + .put("sixty nine", 69) + .put("seventy", 70) + .put("seventy one", 71) + .put("seventy two", 72) + .put("seventy three", 73) + .put("seventy four", 74) + .put("seventy five", 75) + .put("seventy six", 76) + .put("seventy seven", 77) + .put("seventy eight", 78) + .put("seventy nine", 79) + .put("eighty", 80) + .put("eighty one", 81) + .put("eighty two", 82) + .put("eighty three", 83) + .put("eighty four", 84) + .put("eighty five", 85) + .put("eighty six", 86) + .put("eighty seven", 87) + .put("eighty eight", 88) + .put("eighty nine", 89) + .put("ninety", 90) + .put("ninety one", 91) + .put("ninety two", 92) + .put("ninety three", 93) + .put("ninety four", 94) + .put("ninety five", 95) + .put("ninety six", 96) + .put("ninety seven", 97) + .put("ninety eight", 98) + .put("ninety nine", 99) + .put("one hundred", 100) + .build(); + + public static final ImmutableMap DayOfMonth = ImmutableMap.builder() + .put("1st", 1) + .put("2nd", 2) + .put("3rd", 3) + .put("4th", 4) + .put("5th", 5) + .put("6th", 6) + .put("7th", 7) + .put("8th", 8) + .put("9th", 9) + .put("10th", 10) + .put("11th", 11) + .put("11st", 11) + .put("12th", 12) + .put("12nd", 12) + .put("13th", 13) + .put("13rd", 13) + .put("14th", 14) + .put("15th", 15) + .put("16th", 16) + .put("17th", 17) + .put("18th", 18) + .put("19th", 19) + .put("20th", 20) + .put("21st", 21) + .put("21th", 21) + .put("22nd", 22) + .put("22th", 22) + .put("23rd", 23) + .put("23th", 23) + .put("24th", 24) + .put("25th", 25) + .put("26th", 26) + .put("27th", 27) + .put("28th", 28) + .put("29th", 29) + .put("30th", 30) + .put("31st", 31) + .put("01st", 1) + .put("02nd", 2) + .put("03rd", 3) + .put("04th", 4) + .put("05th", 5) + .put("06th", 6) + .put("07th", 7) + .put("08th", 8) + .put("09th", 9) + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("half", 0.5D) + .put("quarter", 0.25D) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("easterday", new String[]{"easterday", "easter", "eastersunday"}) + .put("ashwednesday", new String[]{"ashwednesday"}) + .put("palmsunday", new String[]{"palmsunday"}) + .put("maundythursday", new String[]{"maundythursday"}) + .put("goodfriday", new String[]{"goodfriday"}) + .put("eastersaturday", new String[]{"eastersaturday"}) + .put("eastermonday", new String[]{"eastermonday"}) + .put("ascensionday", new String[]{"ascensionday"}) + .put("whitesunday", new String[]{"whitesunday", "pentecost", "pentecostday"}) + .put("whitemonday", new String[]{"whitemonday"}) + .put("trinitysunday", new String[]{"trinitysunday"}) + .put("corpuschristi", new String[]{"corpuschristi"}) + .put("earthday", new String[]{"earthday"}) + .put("fathers", new String[]{"fatherday", "fathersday"}) + .put("mothers", new String[]{"motherday", "mothersday"}) + .put("thanksgiving", new String[]{"thanksgivingday", "thanksgiving"}) + .put("blackfriday", new String[]{"blackfriday"}) + .put("cybermonday", new String[]{"cybermonday"}) + .put("martinlutherking", new String[]{"mlkday", "martinlutherkingday", "martinlutherkingjrday"}) + .put("washingtonsbirthday", new String[]{"washingtonsbirthday", "washingtonbirthday", "presidentsday"}) + .put("canberra", new String[]{"canberraday"}) + .put("labour", new String[]{"labourday", "laborday"}) + .put("columbus", new String[]{"columbusday"}) + .put("memorial", new String[]{"memorialday"}) + .put("yuandan", new String[]{"yuandan"}) + .put("maosbirthday", new String[]{"maosbirthday"}) + .put("teachersday", new String[]{"teachersday", "teacherday"}) + .put("singleday", new String[]{"singleday"}) + .put("allsaintsday", new String[]{"allsaintsday"}) + .put("youthday", new String[]{"youthday"}) + .put("childrenday", new String[]{"childrenday", "childday"}) + .put("femaleday", new String[]{"femaleday"}) + .put("treeplantingday", new String[]{"treeplantingday"}) + .put("arborday", new String[]{"arborday"}) + .put("girlsday", new String[]{"girlsday"}) + .put("whiteloverday", new String[]{"whiteloverday"}) + .put("loverday", new String[]{"loverday", "loversday"}) + .put("christmas", new String[]{"christmasday", "christmas"}) + .put("xmas", new String[]{"xmasday", "xmas"}) + .put("newyear", new String[]{"newyear"}) + .put("newyearday", new String[]{"newyearday"}) + .put("newyearsday", new String[]{"newyearsday"}) + .put("inaugurationday", new String[]{"inaugurationday"}) + .put("groundhougday", new String[]{"groundhougday"}) + .put("valentinesday", new String[]{"valentinesday"}) + .put("stpatrickday", new String[]{"stpatrickday", "stpatricksday", "stpatrick"}) + .put("aprilfools", new String[]{"aprilfools"}) + .put("stgeorgeday", new String[]{"stgeorgeday"}) + .put("mayday", new String[]{"mayday", "intlworkersday", "internationalworkersday", "workersday"}) + .put("cincodemayoday", new String[]{"cincodemayoday"}) + .put("baptisteday", new String[]{"baptisteday"}) + .put("usindependenceday", new String[]{"usindependenceday"}) + .put("independenceday", new String[]{"independenceday"}) + .put("bastilleday", new String[]{"bastilleday"}) + .put("halloweenday", new String[]{"halloweenday", "halloween"}) + .put("allhallowday", new String[]{"allhallowday"}) + .put("allsoulsday", new String[]{"allsoulsday"}) + .put("guyfawkesday", new String[]{"guyfawkesday"}) + .put("veteransday", new String[]{"veteransday"}) + .put("christmaseve", new String[]{"christmaseve"}) + .put("newyeareve", new String[]{"newyearseve", "newyeareve"}) + .put("juneteenth", new String[]{"juneteenth", "freedomday", "jubileeday"}) + .build(); + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("hundreds", 0) + .put("tens", 10) + .put("twenties", 20) + .put("thirties", 30) + .put("forties", 40) + .put("fifties", 50) + .put("sixties", 60) + .put("seventies", 70) + .put("eighties", 80) + .put("nineties", 90) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("noughties", 2000) + .put("aughts", 2000) + .put("two thousands", 2000) + .build(); + + public static final String DefaultLanguageFallback = "MDY"; + + public static final List SuperfluousWordList = Arrays.asList("preferably", "how about", "maybe", "perhaps", "say", "like"); + + public static final List DurationDateRestrictions = Arrays.asList("today", "now"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^(morning|afternoon|evening|night|day)\\b", "\\b(good\\s+(morning|afternoon|evening|night|day))|(nighty\\s+night)\\b") + .put("\\bnow\\b", "\\b(^now,)|\\b((is|are)\\s+now\\s+for|for\\s+now)\\b") + .put("\\bmay\\b", "\\b((((!|\\.|\\?|,|;|)\\s+|^)may i)|(i|you|he|she|we|they)\\s+may|(may\\s+((((also|not|(also not)|well)\\s+)?(be|ask|contain|constitute|e-?mail|take|have|result|involve|get|work|reply|differ))|(or may not))))\\b") + .put("\\b(a|one) second\\b", "\\b(? MorningTermList = Arrays.asList("morning"); + + public static final List AfternoonTermList = Arrays.asList("afternoon"); + + public static final List EveningTermList = Arrays.asList("evening"); + + public static final List MealtimeBreakfastTermList = Arrays.asList("breakfast"); + + public static final List MealtimeBrunchTermList = Arrays.asList("brunch"); + + public static final List MealtimeLunchTermList = Arrays.asList("lunch", "lunchtime"); + + public static final List MealtimeDinnerTermList = Arrays.asList("dinner", "dinnertime", "supper"); + + public static final List DaytimeTermList = Arrays.asList("daytime"); + + public static final List NightTermList = Arrays.asList("night"); + + public static final List SameDayTerms = Arrays.asList("today", "otd"); + + public static final List PlusOneDayTerms = Arrays.asList("tomorrow", "tmr", "day after"); + + public static final List MinusOneDayTerms = Arrays.asList("yesterday", "day before"); + + public static final List PlusTwoDayTerms = Arrays.asList("day after tomorrow", "day after tmr"); + + public static final List MinusTwoDayTerms = Arrays.asList("day before yesterday"); + + public static final List FutureTerms = Arrays.asList("this", "next"); + + public static final List LastCardinalTerms = Arrays.asList("last"); + + public static final List MonthTerms = Arrays.asList("month"); + + public static final List MonthToDateTerms = Arrays.asList("month to date"); + + public static final List WeekendTerms = Arrays.asList("weekend"); + + public static final List WeekTerms = Arrays.asList("week"); + + public static final List YearTerms = Arrays.asList("year"); + + public static final List GenericYearTerms = Arrays.asList("y"); + + public static final List YearToDateTerms = Arrays.asList("year to date"); + + public static final String DoubleMultiplierRegex = "^(bi)(-|\\s)?"; + + public static final String HalfMultiplierRegex = "^(semi)(-|\\s)?"; + + public static final String DayTypeRegex = "((week)?da(il)?ys?)$"; + + public static final String WeekTypeRegex = "(week(s|ly)?)$"; + + public static final String WeekendTypeRegex = "(weekends?)$"; + + public static final String MonthTypeRegex = "(month(s|ly)?)$"; + + public static final String QuarterTypeRegex = "(quarter(s|ly)?)$"; + + public static final String YearTypeRegex = "((years?|annual)(ly)?)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java new file mode 100644 index 000000000..89b29c012 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishTimeZone.java @@ -0,0 +1,374 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishTimeZone { + + public static final String DirectUtcRegex = "\\b(utc|gmt)(\\s*[+\\-\\u00B1]?\\s*[\\d]{1,2}h?(\\s*:\\s*[\\d]{1,2})?)?\\b"; + + public static final List AbbreviationsList = Arrays.asList("ABST", "ACDT", "ACST", "ACT", "ADT", "AEDT", "AEST", "AET", "AFT", "AKDT", "AKST", "AMST", "AMT", "AOE", "AoE", "ARBST", "ARST", "ART", "AST", "AWDT", "AWST", "AZOST", "AZOT", "AZST", "AZT", "BIT", "BST", "BTT", "CADT", "CAST", "CBST", "CBT", "CCST", "CDT", "CDTM", "CEST", "CET", "COT", "CST", "CSTM", "CT", "CVT", "EAT", "ECT", "EDT", "EDTM", "EEST", "EET", "EGST", "ESAST", "ESAT", "EST", "ESTM", "ET", "FJST", "FJT", "GET", "GMT", "GNDT", "GNST", "GST", "GTBST", "HADT", "HAST", "HDT", "HKT", "HST", "IRDT", "IRKT", "IRST", "ISDT", "ISST", "IST", "JDT", "JST", "KRAT", "KST", "LINT", "MAGST", "MAGT", "MAT", "MDT", "MDTM", "MEST", "MOST", "MSK", "MSK+1", "MSK+2", "MSK+3", "MSK+4", "MSK+5", "MSK+6", "MSK+7", "MSK+8", "MSK+9", "MSK-1", "MST", "MSTM", "MUT", "MVST", "MYST", "NCAST", "NDT", "NMDT", "NMST", "NPT", "NST", "NZDT", "NZST", "NZT", "PDST", "PDT", "PDTM", "PETT", "PKT", "PSAST", "PSAT", "PST", "PSTM", "PT", "PYST", "PYT", "RST", "SAEST", "SAPST", "SAST", "SAWST", "SBT", "SGT", "SLT", "SMST", "SNST", "SST", "TADT", "TAST", "THA", "TIST", "TOST", "TOT", "TRT", "TST", "ULAT", "UTC", "VET", "VLAT", "WAST", "WAT", "WEST", "WET", "WPST", "YAKT", "YEKT"); + + public static final List FullNameList = Arrays.asList("Acre Time", "Afghanistan Standard Time", "Alaskan Standard Time", "Anywhere on Earth", "Arab Standard Time", "Arabian Standard Time", "Arabic Standard Time", "Argentina Standard Time", "Atlantic Standard Time", "AUS Central Standard Time", "Australian Central Time", "AUS Eastern Standard Time", "Australian Eastern Time", "Australian Eastern Standard Time", "Australian Central Daylight Time", "Australian Eastern Daylight Time", "Azerbaijan Standard Time", "Azores Standard Time", "Bahia Standard Time", "Bangladesh Standard Time", "Belarus Standard Time", "Canada Central Standard Time", "Cape Verde Standard Time", "Caucasus Standard Time", "Cen. Australia Standard Time", "Central America Standard Time", "Central Asia Standard Time", "Central Brazilian Standard Time", "Central Daylight Time", "Europe Central Time", "European Central Time", "Central Europe Standard Time", "Central Europe Std Time", "Central European Std Time", "Central European Standard Time", "Central Pacific Standard Time", "Central Standard Time", "Central Standard Time (Mexico)", "China Standard Time", "Dateline Standard Time", "E. Africa Standard Time", "E. Australia Standard Time", "E. Europe Standard Time", "E. South America Standard Time", "Eastern Time", "Eastern Daylight Time", "Eastern Standard Time", "Eastern Standard Time (Mexico)", "Egypt Standard Time", "Ekaterinburg Standard Time", "Fiji Standard Time", "FLE Standard Time", "Georgian Standard Time", "GMT Standard Time", "Greenland Standard Time", "Greenwich Standard Time", "GTB Standard Time", "Hawaiian Standard Time", "India Standard Time", "Iran Standard Time", "Israel Standard Time", "Jordan Standard Time", "Kaliningrad Standard Time", "Kamchatka Standard Time", "Korea Standard Time", "Libya Standard Time", "Line Islands Standard Time", "Magadan Standard Time", "Mauritius Standard Time", "Mid-Atlantic Standard Time", "Middle East Standard Time", "Montevideo Standard Time", "Morocco Standard Time", "Mountain Standard Time", "Mountain Standard Time (Mexico)", "Myanmar Standard Time", "N. Central Asia Standard Time", "Namibia Standard Time", "Nepal Standard Time", "New Zealand Standard Time", "Newfoundland Standard Time", "North Asia East Standard Time", "North Asia Standard Time", "North Korea Standard Time", "Pacific SA Standard Time", "Pacific Standard Time", "Pacific Daylight Time", "Pacific Time", "Pacific Standard Time", "Pacific Standard Time (Mexico)", "Pakistan Standard Time", "Paraguay Standard Time", "Romance Standard Time", "Russia Time Zone 1", "Russia Time Zone 2", "Russia Time Zone 3", "Russia Time Zone 4", "Russia Time Zone 5", "Russia Time Zone 6", "Russia Time Zone 7", "Russia Time Zone 8", "Russia Time Zone 9", "Russia Time Zone 10", "Russia Time Zone 11", "Russian Standard Time", "SA Eastern Standard Time", "SA Pacific Standard Time", "SA Western Standard Time", "Samoa Standard Time", "SE Asia Standard Time", "Singapore Standard Time", "Singapore Time", "South Africa Standard Time", "Sri Lanka Standard Time", "Syria Standard Time", "Taipei Standard Time", "Tasmania Standard Time", "Tokyo Standard Time", "Tonga Standard Time", "Turkey Standard Time", "Ulaanbaatar Standard Time", "US Eastern Standard Time", "US Mountain Standard Time", "Mountain", "Venezuela Standard Time", "Vladivostok Standard Time", "W. Australia Standard Time", "W. Central Africa Standard Time", "W. Europe Standard Time", "West Asia Standard Time", "West Pacific Standard Time", "Yakutsk Standard Time", "Pacific Daylight Saving Time", "Austrialian Western Daylight Time", "Austrialian West Daylight Time", "Australian Western Daylight Time", "Australian West Daylight Time", "Colombia Time", "Hong Kong Time", "Central Europe Time", "Central European Time", "Central Europe Summer Time", "Central European Summer Time", "Central Europe Standard Time", "Central European Standard Time", "Central Europe Std Time", "Central European Std Time", "West Coast Time", "West Coast", "Central Time", "Central", "Pacific", "Eastern"); + + public static final String BaseTimeZoneSuffixRegex = "((\\s+|-)(friendly|compatible))?(\\s+|-)time(zone)?"; + + public static final String LocationTimeSuffixRegex = "({BaseTimeZoneSuffixRegex})\\b" + .replace("{BaseTimeZoneSuffixRegex}", BaseTimeZoneSuffixRegex); + + public static final String TimeZoneEndRegex = "({BaseTimeZoneSuffixRegex})$" + .replace("{BaseTimeZoneSuffixRegex}", BaseTimeZoneSuffixRegex); + + public static final List AmbiguousTimezoneList = Arrays.asList("bit", "get", "art", "cast", "eat", "lint", "mat", "most", "west", "vet", "wet", "cot", "pt", "et", "eastern", "pacific", "central", "mountain", "west coast"); + + public static final ImmutableMap AbbrToMinMapping = ImmutableMap.builder() + .put("abst", 180) + .put("acdt", 630) + .put("acst", 570) + .put("act", -10000) + .put("adt", -10000) + .put("aedt", 660) + .put("aest", 600) + .put("aet", 600) + .put("aft", 270) + .put("akdt", -480) + .put("akst", -540) + .put("amst", -10000) + .put("amt", -10000) + .put("aoe", -720) + .put("arbst", 180) + .put("arst", 180) + .put("art", -180) + .put("ast", -10000) + .put("awdt", 540) + .put("awst", 480) + .put("azost", 0) + .put("azot", -60) + .put("azst", 300) + .put("azt", 240) + .put("bit", -720) + .put("bst", -10000) + .put("btt", 360) + .put("cadt", -360) + .put("cast", 480) + .put("cbst", -240) + .put("cbt", -240) + .put("ccst", -360) + .put("cdt", -10000) + .put("cdtm", -360) + .put("cest", 120) + .put("cet", 60) + .put("cot", -300) + .put("cst", -10000) + .put("cstm", -360) + .put("ct", -360) + .put("cvt", -60) + .put("eat", 180) + .put("ect", -10000) + .put("edt", -240) + .put("edtm", -300) + .put("eest", 180) + .put("eet", 120) + .put("egst", 0) + .put("esast", -180) + .put("esat", -180) + .put("est", -300) + .put("estm", -300) + .put("et", -240) + .put("fjst", 780) + .put("fjt", 720) + .put("get", 240) + .put("gmt", 0) + .put("gndt", -180) + .put("gnst", -180) + .put("gst", -10000) + .put("gtbst", 120) + .put("hadt", -540) + .put("hast", -600) + .put("hdt", -540) + .put("hkt", 480) + .put("hst", -600) + .put("irdt", 270) + .put("irkt", 480) + .put("irst", 210) + .put("isdt", 120) + .put("isst", 120) + .put("ist", -10000) + .put("jdt", 120) + .put("jst", 540) + .put("krat", 420) + .put("kst", -10000) + .put("lint", 840) + .put("magst", 720) + .put("magt", 660) + .put("mat", -120) + .put("mdt", -360) + .put("mdtm", -420) + .put("mest", 120) + .put("most", 0) + .put("msk+1", 240) + .put("msk+2", 300) + .put("msk+3", 360) + .put("msk+4", 420) + .put("msk+5", 480) + .put("msk+6", 540) + .put("msk+7", 600) + .put("msk+8", 660) + .put("msk+9", 720) + .put("msk-1", 120) + .put("msk", 180) + .put("mst", -420) + .put("mstm", -420) + .put("mut", 240) + .put("mvst", -180) + .put("myst", 390) + .put("ncast", 420) + .put("ndt", -150) + .put("nmdt", 60) + .put("nmst", 60) + .put("npt", 345) + .put("nst", -210) + .put("nzdt", 780) + .put("nzst", 720) + .put("nzt", 720) + .put("pdst", -420) + .put("pdt", -420) + .put("pdtm", -480) + .put("pett", 720) + .put("pkt", 300) + .put("psast", -240) + .put("psat", -240) + .put("pst", -480) + .put("pstm", -480) + .put("pt", -420) + .put("pyst", -10000) + .put("pyt", -10000) + .put("rst", 60) + .put("saest", -180) + .put("sapst", -300) + .put("sast", 120) + .put("sawst", -240) + .put("sbt", 660) + .put("sgt", 480) + .put("slt", 330) + .put("smst", 780) + .put("snst", 480) + .put("sst", -10000) + .put("tadt", 600) + .put("tast", 600) + .put("tha", 420) + .put("tist", 480) + .put("tost", 840) + .put("tot", 780) + .put("trt", 180) + .put("tst", 540) + .put("ulat", 480) + .put("utc", 0) + .put("vet", -240) + .put("vlat", 600) + .put("wast", 120) + .put("wat", -10000) + .put("west", 60) + .put("wet", 0) + .put("wpst", 600) + .put("yakt", 540) + .put("yekt", 300) + .build(); + + public static final ImmutableMap FullToMinMapping = ImmutableMap.builder() + .put("beijing", 480) + .put("shanghai", 480) + .put("shenzhen", 480) + .put("suzhou", 480) + .put("tianjian", 480) + .put("chengdu", 480) + .put("guangzhou", 480) + .put("wuxi", 480) + .put("xiamen", 480) + .put("chongqing", 480) + .put("shenyang", 480) + .put("china", 480) + .put("redmond", -480) + .put("seattle", -480) + .put("bellevue", -480) + .put("pacific daylight", -420) + .put("pacific", -480) + .put("afghanistan standard", 270) + .put("alaskan standard", -540) + .put("anywhere on earth", -720) + .put("arab standard", 180) + .put("arabian standard", 180) + .put("arabic standard", 180) + .put("argentina standard", -180) + .put("atlantic standard", -240) + .put("aus central standard", 570) + .put("aus eastern standard", 600) + .put("australian eastern", 600) + .put("australian eastern standard", 600) + .put("australian central daylight", 630) + .put("australian eastern daylight", 660) + .put("azerbaijan standard", 240) + .put("azores standard", -60) + .put("bahia standard", -180) + .put("bangladesh standard", 360) + .put("belarus standard", 180) + .put("canada central standard", -360) + .put("cape verde standard", -60) + .put("caucasus standard", 240) + .put("cen. australia standard", 570) + .put("central australia standard", 570) + .put("central america standard", -360) + .put("central asia standard", 360) + .put("central brazilian standard", -240) + .put("central daylight", -10000) + .put("central europe", 60) + .put("central european", 60) + .put("central europe std", 60) + .put("central european std", 60) + .put("central europe standard", 60) + .put("central european standard", 60) + .put("central europe summer", 120) + .put("central european summer", 120) + .put("central pacific standard", 660) + .put("central standard time (mexico)", -360) + .put("central standard", -360) + .put("china standard", 480) + .put("dateline standard", -720) + .put("e. africa standard", 180) + .put("e. australia standard", 600) + .put("e. europe standard", 120) + .put("e. south america standard", -180) + .put("europe central", 60) + .put("european central", 60) + .put("central", -300) + .put("eastern", -240) + .put("eastern daylight", -10000) + .put("eastern standard time (mexico)", -300) + .put("eastern standard", -300) + .put("egypt standard", 120) + .put("ekaterinburg standard", 300) + .put("fiji standard", 720) + .put("fle standard", 120) + .put("georgian standard", 240) + .put("gmt standard", 0) + .put("greenland standard", -180) + .put("greenwich standard", 0) + .put("gtb standard", 120) + .put("hawaiian standard", -600) + .put("india standard", 330) + .put("iran standard", 210) + .put("israel standard", 120) + .put("jordan standard", 120) + .put("kaliningrad standard", 120) + .put("kamchatka standard", 720) + .put("korea standard", 540) + .put("libya standard", 120) + .put("line islands standard", 840) + .put("magadan standard", 660) + .put("mauritius standard", 240) + .put("mid-atlantic standard", -120) + .put("middle east standard", 120) + .put("montevideo standard", -180) + .put("morocco standard", 0) + .put("mountain", -360) + .put("mountain standard", -420) + .put("mountain standard time (mexico)", -420) + .put("myanmar standard", 390) + .put("n. central asia standard", 420) + .put("namibia standard", 60) + .put("nepal standard", 345) + .put("new zealand standard", 720) + .put("newfoundland standard", -210) + .put("north asia east standard", 480) + .put("north asia standard", 420) + .put("north korea standard", 510) + .put("west coast", -420) + .put("pacific sa standard", -240) + .put("pacific standard", -480) + .put("pacific standard time (mexico)", -480) + .put("pakistan standard", 300) + .put("paraguay standard", -240) + .put("romance standard", 60) + .put("russia time zone 1", 120) + .put("russia time zone 2", 180) + .put("russia time zone 3", 240) + .put("russia time zone 4", 300) + .put("russia time zone 5", 360) + .put("russia time zone 6", 420) + .put("russia time zone 7", 480) + .put("russia time zone 8", 540) + .put("russia time zone 9", 600) + .put("russia time zone 10", 660) + .put("russia time zone 11", 720) + .put("russian standard", 180) + .put("sa eastern standard", -180) + .put("sa pacific standard", -300) + .put("sa western standard", -240) + .put("samoa standard", -660) + .put("se asia standard", 420) + .put("singapore standard", 480) + .put("singapore", 480) + .put("south africa standard", 120) + .put("sri lanka standard", 330) + .put("syria standard", 120) + .put("taipei standard", 480) + .put("tasmania standard", 600) + .put("tokyo standard", 540) + .put("tonga standard", 780) + .put("turkey standard", 180) + .put("ulaanbaatar standard", 480) + .put("us eastern standard", -300) + .put("us mountain standard", -420) + .put("venezuela standard", -240) + .put("vladivostok standard", 600) + .put("w. australia standard", 480) + .put("w. central africa standard", 60) + .put("w. europe standard", 0) + .put("western european", 0) + .put("west europe standard", 0) + .put("west europe std", 0) + .put("western europe standard", 0) + .put("western europe summer", 60) + .put("w. europe summer", 60) + .put("western european summer", 60) + .put("west europe summer", 60) + .put("west asia standard", 300) + .put("west pacific standard", 600) + .put("yakutsk standard", 540) + .put("pacific daylight saving", -420) + .put("australian western daylight", 540) + .put("australian west daylight", 540) + .put("austrialian western daylight", 540) + .put("austrialian west daylight", 540) + .put("colombia", -300) + .put("hong kong", 480) + .put("madrid", 60) + .put("bilbao", 60) + .put("seville", 60) + .put("valencia", 60) + .put("malaga", 60) + .put("las Palmas", 60) + .put("zaragoza", 60) + .put("alicante", 60) + .put("alche", 60) + .put("oviedo", 60) + .put("gijón", 60) + .put("avilés", 60) + .build(); + + public static final List MajorLocations = Arrays.asList("Dominican Republic", "Dominica", "Guinea Bissau", "Guinea-Bissau", "Guinea", "Equatorial Guinea", "Papua New Guinea", "New York City", "New York", "York", "Mexico City", "New Mexico", "Mexico", "Aberdeen", "Adelaide", "Anaheim", "Atlanta", "Auckland", "Austin", "Bangkok", "Baltimore", "Baton Rouge", "Beijing", "Belfast", "Birmingham", "Bolton", "Boston", "Bournemouth", "Bradford", "Brisbane", "Bristol", "Calgary", "Canberra", "Cardiff", "Charlotte", "Chicago", "Christchurch", "Colchester", "Colorado Springs", "Coventry", "Dallas", "Denver", "Derby", "Detroit", "Dubai", "Dublin", "Dudley", "Dunedin", "Edinburgh", "Edmonton", "El Paso", "Glasgow", "Gold Coast", "Hamilton", "Hialeah", "Houston", "Ipswich", "Jacksonville", "Jersey City", "Kansas City", "Kingston-upon-Hull", "Leeds", "Leicester", "Lexington", "Lincoln", "Liverpool", "London", "Long Beach", "Los Angeles", "Louisville", "Lubbock", "Luton", "Madison", "Manchester", "Mansfield", "Melbourne", "Memphis", "Mesa", "Miami", "Middlesbrough", "Milan", "Milton Keynes", "Minneapolis", "Montréal", "Montreal", "Nashville", "New Orleans", "Newark", "Newcastle-upon-Tyne", "Newcastle", "Northampton", "Norwich", "Nottingham", "Oklahoma City", "Oldham", "Omaha", "Orlando", "Ottawa", "Perth", "Peterborough", "Philadelphia", "Phoenix", "Plymouth", "Portland", "Portsmouth", "Preston", "Québec City", "Quebec City", "Québec", "Quebec", "Raleigh", "Reading", "Redmond", "Richmond", "Rome", "San Antonio", "San Diego", "San Francisco", "San José", "Santa Ana", "Seattle", "Sheffield", "Southampton", "Southend-on-Sea", "Spokane", "St Louis", "St Paul", "St Petersburg", "St. Louis", "St. Paul", "St. Petersburg", "Stockton-on-Tees", "Stockton", "Stoke-on-Trent", "Sunderland", "Swansea", "Swindon", "Sydney", "Tampa", "Tauranga", "Telford", "Toronto", "Vancouver", "Virginia Beach", "Walsall", "Warrington", "Washington", "Wellington", "Wolverhampton", "Abilene", "Akron", "Albuquerque", "Alexandria", "Allentown", "Amarillo", "Anchorage", "Ann Arbor", "Antioch", "Arlington", "Arvada", "Athens", "Augusta", "Aurora", "Bakersfield", "Beaumont", "Bellevue", "Berkeley", "Billings", "Boise", "Boulder", "Bridgeport", "Broken Arrow", "Brownsville", "Buffalo", "Burbank", "Cambridge", "Cape Coral", "Carlsbad", "Carrollton", "Cary", "Cedar Rapids", "Centennial", "Chandler", "Charleston", "Chattanooga", "Chengdu", "Chesapeake", "Chongqing", "Chula Vista", "Cincinnati", "Clarksville", "Clearwater", "Cleveland", "Clovis", "College Station", "Columbia", "Columbus", "Concord", "Coral Springs", "Corona", "Costa Mesa", "Daly City", "Davenport", "Dayton", "Denton", "Des Moines", "Downey", "Durham", "Edison", "El Cajon", "El Monte", "Elgin", "Elizabeth", "Elk Grove", "Erie", "Escondido", "Eugene", "Evansville", "Everett", "Fairfield", "Fargo", "Farmington Hills", "Fayetteville", "Fontana", "Fort Collins", "Fort Lauderdale", "Fort Wayne", "Fort Worth", "Fremont", "Fresno", "Frisco", "Fullerton", "Gainesville", "Garden Grove", "Garland", "Gilbert", "Glendale", "Grand Prairie", "Grand Rapids", "Green Bay", "Greensboro", "Gresham", "Guangzhou", "Hampton", "Hartford", "Hayward", "Henderson", "High Point", "Hollywood", "Honolulu", "Huntington Beach", "Huntsville", "Independence", "Indianapolis", "Inglewood", "Irvine", "Irving", "Jackson", "Joliet", "Kent", "Killeen", "Knoxville", "Lafayette", "Lakeland", "Lakewood", "Lancaster", "Lansing", "Laredo", "Las Cruces", "Las Vegas", "Lewisville", "Little Rock", "Lowell", "Macon", "McAllen", "McKinney", "Mesquite", "Miami Gardens", "Midland", "Milwaukee", "Miramar", "Mobile", "Modesto", "Montgomery", "Moreno Valley", "Murfreesboro", "Murrieta", "Naperville", "New Haven", "Newport News", "Norfolk", "Norman", "North Charleston", "North Las Vegas", "Norwalk", "Oakland", "Oceanside", "Odessa", "Olathe", "Ontario", "Orange", "Overland Park", "Oxnard", "Palm Bay", "Palmdale", "Pasadena", "Paterson", "Pearland", "Pembroke Pines", "Peoria", "Pittsburgh", "Plano", "Pomona", "Pompano Beach", "Providence", "Provo", "Pueblo", "Rancho Cucamonga", "Reno", "Rialto", "Richardson", "Riverside", "Rochester", "Rockford", "Roseville", "Round Rock", "Sacramento", "Saint Paul", "Salem", "Salinas", "Salt Lake City", "San Bernardino", "San Jose", "San Mateo", "Sandy Springs", "Santa Clara", "Santa Clarita", "Santa Maria", "Santa Rosa", "Savannah", "Scottsdale", "Shanghai", "Shenyang", "Shenzhen", "Shreveport", "Simi Valley", "Sioux Falls", "South Bend", "Springfield", "Stamford", "Sterling Heights", "Sunnyvale", "Surprise", "Suzhou", "Syracuse", "Tacoma", "Tallahassee", "Temecula", "Tempe", "Thornton", "Thousand Oaks", "Tianjing", "Toledo", "Topeka", "Torrance", "Tucson", "Tulsa", "Tyler", "Vallejo", "Ventura", "Victorville", "Visalia", "Waco", "Warren", "Waterbury", "West Covina", "West Jordan", "West Palm Beach", "West Valley City", "Westminster", "Wichita", "Wichita Falls", "Wilmington", "Winston-Salem", "Worcester", "Wuxi", "Xiamen", "Yonkers", "Bentonville", "Afghanistan", "AK", "AL", "Alabama", "Åland", "Åland Islands", "Alaska", "Albania", "Algeria", "American Samoa", "Andorra", "Angola", "Anguilla", "Antarctica", "Antigua and Barbuda", "AR", "Argentina", "Arizona", "Arkansas", "Armenia", "Aruba", "Australia", "Austria", "AZ", "Azerbaijan", "Bahamas", "Bahrain", "Bangladesh", "Barbados", "Belarus", "Belgium", "Belize", "Benin", "Bermuda", "Bhutan", "Bolivia", "Bonaire", "Bosnia", "Bosnia and Herzegovina", "Botswana", "Bouvet Island", "Brazil", "British Indian Ocean Territory", "British Virgin Islands", "Brunei", "Bulgaria", "Burkina Faso", "Burundi", "CA", "Cabo Verde", "California", "Cambodia", "Cameroon", "Canada", "Cayman Islands", "Central African Republic", "Chad", "Chile", "China", "Christmas Island", "CO", "Cocos Islands", "Colombia", "Colorado", "Comoros", "Congo", "Congo (DRC)", "Connecticut", "Cook Islands", "Costa Rica", "Côte d’Ivoire", "Croatia", "CT", "Cuba", "Curaçao", "Cyprus", "Czechia", "DE", "Delaware", "Denmark", "Djibouti", "Ecuador", "Egypt", "El Salvador", "Eritrea", "Estonia", "eSwatini", "Ethiopia", "Falkland Islands", "Falklands", "Faroe Islands", "Fiji", "Finland", "FL", "Florida", "France", "French Guiana", "French Polynesia", "French Southern Territories", "FYROM", "GA", "Gabon", "Gambia", "Georgia", "Georgia", "Germany", "Ghana", "Gibraltar", "Greece", "Greenland", "Grenada", "Guadeloupe", "Guam", "Guatemala", "Guernsey", "Guyana", "Haiti", "Hawaii", "Herzegovina", "HI", "Honduras", "Hong Kong", "Hungary", "IA", "Iceland", "ID", "Idaho", "IL", "Illinois", "IN", "India", "Indiana", "Indonesia", "Iowa", "Iran", "Iraq", "Ireland", "Isle of Man", "Israel", "Italy", "Ivory Coast", "Jamaica", "Jan Mayen", "Japan", "Jersey", "Jordan", "Kansas", "Kazakhstan", "Keeling Islands", "Kentucky", "Kenya", "Kiribati", "Korea", "Kosovo", "KS", "Kuwait", "KY", "Kyrgyzstan", "LA", "Laos", "Latvia", "Lebanon", "Lesotho", "Liberia", "Libya", "Liechtenstein", "Lithuania", "Louisiana", "Luxembourg", "MA", "Macao", "Macedonia", "Madagascar", "Maine", "Malawi", "Malaysia", "Maldives", "Mali", "Malta", "Marshall Islands", "Martinique", "Maryland", "Massachusetts", "Mauritania", "Mauritius", "Mayotte", "MD", "ME", "MI", "Michigan", "Micronesia", "Minnesota", "Mississippi", "Missouri", "MN", "MO", "Moldova", "Monaco", "Mongolia", "Montana", "Montenegro", "Montserrat", "Morocco", "Mozambique", "MS", "MT", "Myanmar", "Namibia", "Nauru", "NC", "ND", "NE", "Nebraska", "Nepal", "Netherlands", "Nevada", "New Caledonia", "New Hampshire", "New Jersey", "New Zealand", "NH", "Nicaragua", "Niger", "Nigeria", "Niue", "NJ", "NM", "Norfolk Island", "North Carolina", "North Dakota", "North Korea", "Northern Mariana Islands", "Norway", "NV", "NY", "OH", "Ohio", "OK", "Oklahoma", "Oman", "OR", "Oregon", "PA", "Pakistan", "Palau", "Palestinian Authority", "Panama", "Paraguay", "Pennsylvania", "Peru", "Philippines", "Pitcairn Islands", "Poland", "Portugal", "Puerto Rico", "Qatar", "Réunion", "Rhode Island", "RI", "Romania", "Russia", "Rwanda", "Saba", "Saint Barthélemy", "Saint Kitts and Nevis", "Saint Lucia", "Saint Martin", "Saint Pierre and Miquelon", "Saint Vincent and the Grenadines", "Samoa", "San Marino", "São Tomé and Príncipe", "Saudi Arabia", "SC", "SD", "Senegal", "Serbia", "Seychelles", "Sierra Leone", "Singapore", "Sint Eustatius", "Sint Maarten", "Slovakia", "Slovenia", "Solomon Islands", "Somalia", "South Africa", "South Carolina", "South Dakota", "South Sudan", "Spain", "Sri Lanka", "Sudan", "Suriname", "Svalbard", "Swaziland", "Sweden", "Switzerland", "Syria", "Taiwan", "Tajikistan", "Tanzania", "Tennessee", "Texas", "Thailand", "Timor-Leste", "TN", "Togo", "Tokelau", "Tonga", "Trinidad and Tobago", "Tunisia", "Turkey", "Turkmenistan", "Turks and Caicos Islands", "Tuvalu", "TX", "U.S. Outlying Islands", "US Outlying Islands", "U.S. Virgin Islands", "US Virgin Islands", "Uganda", "UK", "Ukraine", "United Arab Emirates", "United Kingdom", "United States", "Uruguay", "US", "USA", "UT", "Utah", "Uzbekistan", "VA", "Vanuatu", "Vatican City", "Venezuela", "Vermont", "Vietnam", "Virginia", "VT", "WA", "Wallis and Futuna", "West Virginia", "WI", "Wisconsin", "WV", "WY", "Wyoming", "Yemen", "Zambia", "Zimbabwe", "Paris", "Tokyo", "Shanghai", "Sao Paulo", "Rio de Janeiro", "Rio", "Brasília", "Brasilia", "Recife", "Milan", "Mumbai", "Moscow", "Frankfurt", "Munich", "Berlim", "Madrid", "Lisbon", "Warsaw", "Johannesburg", "Seoul", "Istanbul", "Kuala Kumpur", "Jakarta", "Amsterdam", "Brussels", "Valencia", "Seville", "Bilbao", "Malaga", "Las Palmas", "Zaragoza", "Alicante", "Elche", "Oviedo", "Gijón", "Avilés", "West Coast", "Central", "Pacific", "Eastern", "Mountain"); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java new file mode 100644 index 000000000..0ab5214d3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/FrenchDateTime.java @@ -0,0 +1,1234 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchDateTime { + + public static final String LangMarker = "Fre"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?au|et|(jusqu')?[aà]|avant|--|-|—|——)"; + + public static final String RangeConnectorRegex = "(?de la|au|[aà]|et(\\s*la)?|--|-|—|——)"; + + public static final String RelativeRegex = "(?prochaine?|de|du|ce(tte)?|l[ae]|derni[eè]re|pr[eé]c[eé]dente|au\\s+cours+(de|du\\s*))"; + + public static final String StrictRelativeRegex = "(?prochaine?|derni[eè]re|pr[eé]c[eé]dente|au\\s+cours+(de|du\\s*))"; + + public static final String NextSuffixRegex = "(?prochaines?|prochain|suivante)\\b"; + + public static final String PastSuffixRegex = "(?derni[eè]re?|pr[eé]c[eé]dente)\\b"; + + public static final String ThisPrefixRegex = "(?ce(tte)?|au\\s+cours+(du|de))\\b"; + + public static final String RangePrefixRegex = "(du|depuis|des?|entre)"; + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11e?|12e?|13e?|14e?|15e?|16e?|17e?|18e?|19e?|1er|1|21e?|20e?|22e?|23e?|24e?|25e?|26e?|27e?|28e?|29e?|2e?|30e?|31e?|3e?|4e?|5e?|6e?|7e?|8e?|9e?)(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String SpecialDescRegex = "(p\\b)"; + + public static final String AmDescRegex = "(h\\b|{BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "(h\\b|{BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "(h\\b|{BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?{AmPmDescRegex}|{AmDescRegex}|{PmDescRegex}|{SpecialDescRegex})" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{AmPmDescRegex}", AmPmDescRegex) + .replace("{SpecialDescRegex}", SpecialDescRegex); + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String FullTextYearRegex = "^[\\*]"; + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String WeekDayRegex = "(?dimanche|lundi|mardi|mercredi|jeudi|vendredi|samedi|lun(\\.)?|mar(\\.)?|mer(\\.)?|jeu(\\.)?|ven(\\.)?|sam(\\.)?|dim(\\.)?)"; + + public static final String RelativeMonthRegex = "(?({ThisPrefixRegex}\\s+mois)|(mois\\s+{PastSuffixRegex})|(mois\\s+{NextSuffixRegex}))\\b" + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String WrittenMonthRegex = "(?avril|avr(\\.)?|ao[uû]t|d[eé]cembre|d[eé]c(\\.)?|f[eé]vrier|f[eé]vr?(\\.)?|janvier|janv?(\\.)?|juillet|jui?[ln](\\.)?|mars?(\\.)?|mai|novembre|nov(\\.)?|octobre|oct(\\.)?|septembre|sept?(\\.)?)"; + + public static final String MonthSuffixRegex = "(?(en\\s*|le\\s*|de\\s*|dans\\s*)?({RelativeMonthRegex}|{WrittenMonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{WrittenMonthRegex}", WrittenMonthRegex); + + public static final String DateUnitRegex = "(?(l')?ann[eé]es?|an|mois|semaines?|journ[eé]es?|jours?)\\b"; + + public static final String SimpleCasesRegex = "\\b((d[ue])|entre\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b((d[ue]|entre)\\s+)?{MonthSuffixRegex}\\s+((d[ue]|entre)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+(entre|d[ue]\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{YearRegex}", YearRegex); + + public static final String BetweenRegex = "\\b(entre\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String YearWordRegex = "\\b(?l'ann[ée]e)\\b"; + + public static final String MonthWithYear = "\\b({WrittenMonthRegex}(\\s*),?(\\s+de)?(\\s*)({YearRegex}|{TwoDigitYearRegex}|(?cette)\\s*{YearWordRegex})|{YearWordRegex}\\s*({PastSuffixRegex}|{NextSuffixRegex}))" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex) + .replace("{YearWordRegex}", YearWordRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String OneWordPeriodRegex = "\\b(({RelativeRegex}\\s+)?{WrittenMonthRegex}|(la\\s+)?(weekend|(fin de )?semaine|week-end|mois|ans?|l'année)\\s+{StrictRelativeRegex}|{RelativeRegex}\\s+(weekend|(fin de )?semaine|week-end|mois|ans?|l'année)|weekend|week-end|(mois|l'année))\\b" + .replace("{WrittenMonthRegex}", WrittenMonthRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String MonthNumWithYear = "({YearRegex}(\\s*)[/\\-\\.](\\s*){MonthNumRegex})|({MonthNumRegex}(\\s*)[/\\-](\\s*){YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+semaine(\\s+de)?\\s+{MonthSuffixRegex})" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String WeekOfYearRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+semaine(\\s+de)?\\s+({YearRegex}|{RelativeRegex}\\s+ann[ée]e))" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterRegex = "(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4)\\s+quart(\\s+de|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+l'ann[eé]e)" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|l'année\\s+({PastSuffixRegex}|{NextSuffixRegex})|{RelativeRegex}\\s+ann[eé]e)\\s+(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4)\\s+quarts" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String AllHalfYearRegex = "^[.]"; + + public static final String PrefixDayRegex = "\\b((?t[ôo]t\\sdans)|(?au\\smilieu\\sde)|(?tard\\sdans))(\\s+la\\s+journ[ée]e)?$"; + + public static final String CenturySuffixRegex = "^[.]"; + + public static final String SeasonRegex = "\\b((printemps|été|automne|hiver)+\\s*({NextSuffixRegex}|{PastSuffixRegex}))|(?({RelativeRegex}\\s+)?(?printemps|[ée]t[ée]|automne|hiver)((\\s+de|\\s*,\\s*)?\\s+({YearRegex}|{RelativeRegex}\\s+l'ann[eé]e))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex); + + public static final String WhichWeekRegex = "\\b(semaine)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(semaine)(\\s*)(de)"; + + public static final String MonthOfRegex = "(mois)(\\s*)(de)"; + + public static final String MonthRegex = "(?avril|avr(\\.)?|ao[uû]t|d[eé]cembre|d[eé]c(\\.)?|f[eé]vrier|f[eé]vr?(\\.)?|janvier|janv?(\\.)?|juillet|jui?[ln](\\.)?|mars?(\\.)?|mai|novembre|nov(\\.)?|octobre|oct(\\.)?|septembre|sept?(\\.)?)"; + + public static final String OnRegex = "(?<=\\b(en|sur\\s*l[ea]|sur)\\s+)({DayRegex}s?)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(en|le|dans|sur\\s*l[ea]|du|sur)\\s+)((?10e|11e|12e|13e|14e|15e|16e|17e|18e|19e|1er|20e|21e|22e|23e|24e|25e|26e|27e|28e|29e|2e|30e|31e|3e|4e|5e|6e|7e|8e|9e)s?)\\b"; + + public static final String ThisRegex = "\\b((cette(\\s*semaine)?\\s+){WeekDayRegex})|({WeekDayRegex}(\\s+cette\\s*semaine))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String LastDateRegex = "\\b(({WeekDayRegex}(\\s*(de)?\\s*la\\s*semaine\\s+{PastSuffixRegex}))|({WeekDayRegex}(\\s+{PastSuffixRegex})))\\b" + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String NextDateRegex = "\\b(({WeekDayRegex}(\\s+{NextSuffixRegex}))|({WeekDayRegex}(\\s*(de)?\\s*la\\s*semaine\\s+{NextSuffixRegex})))\\b" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "\\b(avant[\\s|-]hier|apr[eè]s(-demain|\\s*demain)|(le\\s)?jour suivant|(le\\s+)?dernier jour|hier|lendemain|demain|(de\\s)?la journ[ée]e|aujourd'hui)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String StrictWeekDay = "\\b(?dim(anche)?|lun(di)?|mar(di)?|mer(credi)?|jeu(di)?|ven(dredi)?|sam(edi)?)s?\\b"; + + public static final String SetWeekDayRegex = "\\b(?le\\s+)?(?matin([ée]e)?|apr[eè]s-midi|soir([ée]e)?|dimanche|lundi|mardi|mercredi|jeudi|vendredi|samedi)s\\b"; + + public static final String WeekDayOfMonthRegex = "(?(le\\s+)?(?premier|1er|duexi[èe]me|2|troisi[èe]me|3|quatri[èe]me|4|cinqi[èe]me|5)\\s+{WeekDayRegex}\\s+{MonthSuffixRegex})" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String SpecialDate = "(?<=\\b(au|le)\\s+){DayRegex}(?!:)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String DateYearRegex = "(?{YearRegex}|{TwoDigitYearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{MonthRegex}\\s*[/\\\\\\.\\-]?\\s*{DayRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor2 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}(\\s+|\\s*,\\s*|\\s+){MonthRegex}\\s*[\\.\\-]?\\s*{DateYearRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?(?(l')?ann[eé]e(s)?|mois|semaines?)\\b"; + + public static final String HourNumRegex = "\\b(?zero|[aá]\\s+une?|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|dix-six|dix-sept|dix-huit|dix-neuf|vingt|vingt-et-un|vingt-deux|vingt-trois|dix)\\b"; + + public static final String MinuteNumRegex = "(?un|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt|trente|quarante|cinquante|dix)"; + + public static final String DeltaMinuteNumRegex = "(?un|deux|trois|quatre|cinq|six|sept|huit|neuf|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt|trente|quarante|cinquante|dix)"; + + public static final String OclockRegex = "(?heures?|h)"; + + public static final String PmRegex = "(?(dans l'\\s*)?apr[eè]s(\\s*|-)midi|(du|ce|de|le)\\s*(soir([ée]e)?)|(dans l[ea]\\s+)?(nuit|soir[eé]e))"; + + public static final String AmRegex = "(?(du|de|ce|(du|de|dans)\\s*l[ea]|le)?\\s*matin[ée]e|(du|de|ce|dans l[ea]|le)?\\s*matin)"; + + public static final String LessThanOneHour = "(?(une\\s+)?quart|trois quart(s)?|demie( heure)?|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutes?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutes?|mins?)))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s+({MinuteNumRegex}|(?vingt|trente|quarante|cinquante)\\s+{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimePrefix = "(?(heures\\s*et\\s+{LessThanOneHour}|et {LessThanOneHour}|{LessThanOneHour} [àa]))" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?{AmRegex}|{PmRegex}|{OclockRegex})" + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidnightRegex = "(?minuit)"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String MorningRegex = "(?matin([ée]e)?)"; + + public static final String AfternoonRegex = "(?(d'|l')?apr[eè]s(-|\\s*)midi)"; + + public static final String MidmorningRegex = "(?milieu\\s*d[ue]\\s*{MorningRegex})" + .replace("{MorningRegex}", MorningRegex); + + public static final String MiddayRegex = "(?milieu(\\s*|-)d[eu]\\s*(jour|midi)|apr[eè]s(-|\\s*)midi)"; + + public static final String MidafternoonRegex = "(?milieu\\s*d'+{AfternoonRegex})" + .replace("{AfternoonRegex}", AfternoonRegex); + + public static final String MidTimeRegex = "(?({MidnightRegex}|{MidmorningRegex}|{MidafternoonRegex}|{MiddayRegex}))" + .replace("{MidnightRegex}", MidnightRegex) + .replace("{MidmorningRegex}", MidmorningRegex) + .replace("{MidafternoonRegex}", MidafternoonRegex) + .replace("{MiddayRegex}", MiddayRegex); + + public static final String AtRegex = "\\b(((?<=\\b[àa]\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))|{MidTimeRegex})\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String IshRegex = "\\b(peu\\s*pr[èe]s\\s*{BaseDateTime.HourRegex}|peu\\s*pr[èe]s\\s*{WrittenTimeRegex}|peu\\s*pr[èe]s\\s*[àa]\\s*{BaseDateTime.HourRegex}|peu pr[èe]s midi)\\b" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex); + + public static final String TimeUnitRegex = "(?heures?|hrs?|h|minutes?|mins?|secondes?|secs?)\\b"; + + public static final String RestrictedTimeUnitRegex = "(?huere|minute)\\b"; + + public static final String ConnectNumRegex = "{BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String FivesRegex = "(?(quinze|vingt(\\s*|-*(cinq))?|trente(\\s*|-*(cinq))?|quarante(\\s*|-*(cinq))??|cinquante(\\s*|-*(cinq))?|dix|cinq))\\b"; + + public static final String PeriodHourNumRegex = "(?vingt-et-un|vingt-deux|vingt-trois|vingt-quatre|zero|une|deux|trois|quatre|cinq|six|sept|huit|neuf|dix|onze|douze|treize|quatorze|quinze|seize|dix-sept|dix-huit|dix-neuf|vingt)"; + + public static final String TimeRegex1 = "\\b({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*{DescRegex}(\\s+{TimePrefix})?\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "\\b{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})(\\s+{TimePrefix})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b{BasicTime}(\\s*{DescRegex})?(\\s+{TimePrefix})?\\s+{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex5 = "\\b{BasicTime}((\\s*{DescRegex})(\\s+{TimePrefix})?|\\s+{TimePrefix})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex6 = "{BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+[àa]\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b{PeriodHourNumRegex}\\s+{FivesRegex}((\\s*{DescRegex})|\\b)" + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{FivesRegex}", FivesRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex10 = "\\b{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?(\\s+{TimePrefix})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String HourRegex = "\\b{BaseDateTime.HourRegex}" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String PeriodDescRegex = "(?pm|am|p\\.m\\.|a\\.m\\.|p)"; + + public static final String PeriodPmRegex = "(?dans l'apr[eè]s-midi|ce soir|d[eu] soir|dans l[ea] soir[eé]e|dans la nuit|d[eu] soir[ée]e)s?"; + + public static final String PeriodAmRegex = "(?d[eu] matin|matin([ée]e)s?"; + + public static final String PureNumFromTo = "((du|depuis|des?)\\s+)?({HourRegex}|{PeriodHourNumRegex})(\\s*(?{PeriodDescRegex}))?\\s*{TillRegex}\\s*({HourRegex}|{PeriodHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{PeriodDescRegex})?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{PeriodDescRegex}", PeriodDescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(entre\\s+)({HourRegex}|{PeriodHourNumRegex})(\\s*(?{PeriodDescRegex}))?\\s*{RangeConnectorRegex}\\s*({HourRegex}|{PeriodHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{PeriodDescRegex})?" + .replace("{HourRegex}", HourRegex) + .replace("{PeriodHourNumRegex}", PeriodHourNumRegex) + .replace("{PeriodDescRegex}", PeriodDescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String SpecificTimeFromTo = "^[.]"; + + public static final String SpecificTimeBetweenAnd = "^[.]"; + + public static final String PrepositionRegex = "(?^([aà](\\s+?la)?|en|sur(\\s*l[ea])?|de)$)"; + + public static final String TimeOfDayRegex = "\\b(?((((dans\\s+(l[ea])?\\s+)?((?d[eé]but(\\s+|-)|t[oô]t(\\s+|-)(l[ea]\\s*)?)|(?fin\\s*|fin de(\\s+(la)?)|tard\\s*))?(matin([ée]e)?|((d|l)?'?)apr[eè]s[-|\\s*]midi|nuit|soir([eé]e)?)))|(((\\s+(l[ea])?\\s+)?)jour(n[eé]e)?))s?)\\b"; + + public static final String SpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{TimeOfDayRegex})|({TimeOfDayRegex}\\s*({NextSuffixRegex}))\\b|\\bsoir|\\bdu soir)s?\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?){TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String NowRegex = "\\b(?(ce\\s+)?moment|maintenant|d[eè]s que possible|dqp|r[eé]cemment|auparavant)\\b"; + + public static final String SuffixRegex = "^\\s*((dans\\s+l[ea]\\s+)|(en\\s+)|(d(u|\\'))?(matin([eé]e)?|apr[eè]s-midi|soir[eé]e|nuit))\\b"; + + public static final String DateTimeTimeOfDayRegex = "\\b(?matin([eé]e)?|apr[eè]s-midi|nuit|soir)\\b"; + + public static final String DateTimeSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{DateTimeTimeOfDayRegex})\\b|\\b(ce(tte)?\\s+)(soir|nuit))\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?(en|dans|du\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+([àa]|pour))?\\s*$" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "({HourNumRegex}|{BaseDateTime.HourRegex})\\s*(,\\s*)?(en|[àa]\\s+)?{DateTimeSpecificTimeOfDayRegex}" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayBeforeRegex = "{DateTimeSpecificTimeOfDayRegex}(\\s*,)?(\\s+([àa]|vers))?\\s*({HourNumRegex}|{BaseDateTime.HourRegex})" + .replace("{DateTimeSpecificTimeOfDayRegex}", DateTimeSpecificTimeOfDayRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String SpecificEndOfRegex = "(la\\s+)?fin(\\s+de\\s*|\\s*de*l[ea])?\\s*$"; + + public static final String UnspecificEndOfRegex = "^[.]"; + + public static final String UnspecificEndOfRangeRegex = "^[.]"; + + public static final String PeriodTimeOfDayRegex = "\\b((dans\\s+(le)?\\s+)?((?d[eé]but(\\s+|-|d[ue]|de la)|t[oô]t)|(?tard\\s*|fin(\\s+|-|d[eu])?))?(?matin|((d|l)?'?)apr[eè]s-midi|nuit|soir([eé]e)?))\\b"; + + public static final String PeriodSpecificTimeOfDayRegex = "\\b(({RelativeRegex}\\s+{PeriodTimeOfDayRegex})\\b|\\b(ce(tte)?\\s+)(soir|nuit))\\b" + .replace("{PeriodTimeOfDayRegex}", PeriodTimeOfDayRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b(({TimeOfDayRegex}))\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String LessThanRegex = "^[.]"; + + public static final String MoreThanRegex = "^[.]"; + + public static final String DurationUnitRegex = "(?ann[eé]es?|ans?|mois|semaines?|jours?|heures?|hrs?|h|minutes?|mins?|secondes?|secs?|journ[eé]e)\\b"; + + public static final String SuffixAndRegex = "(?\\s*(et)\\s+(une?\\s+)?(?demi|quart))"; + + public static final String PeriodicRegex = "\\b(?quotidien(ne)?|journellement|mensuel(le)?|jours?|hebdomadaire|bihebdomadaire|annuel(lement)?)\\b"; + + public static final String EachUnitRegex = "(?(chaque|toutes les|tous les)(?\\s+autres)?\\s*{DurationUnitRegex})" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String EachPrefixRegex = "\\b(?(chaque|tous les|(toutes les))\\s*$)"; + + public static final String SetEachRegex = "\\b(?(chaque|tous les|(toutes les))\\s*)"; + + public static final String SetLastRegex = "(?prochain|dernier|derni[eè]re|pass[ée]s|pr[eé]c[eé]dent|courant|en\\s*cours)"; + + public static final String EachDayRegex = "^\\s*(chaque|tous les)\\s*(jour|jours)\\b"; + + public static final String DurationFollowedUnit = "^\\s*{SuffixAndRegex}?(\\s+|-)?{DurationUnitRegex}" + .replace("{SuffixAndRegex}", SuffixAndRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String NumberCombinedWithDurationUnit = "\\b(?\\d+(\\.\\d*)?)(-)?{DurationUnitRegex}" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String AnUnitRegex = "\\b(((?demi\\s+)?(-)\\s+{DurationUnitRegex}))" + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?toute\\s(l['ea])\\s?(?ann[eé]e|mois|semaines?|jours?|journ[eé]e))\\b"; + + public static final String HalfRegex = "((une?\\s*)|\\b)(?demi?(\\s*|-)+(?ann[eé]e|ans?|mois|semaine|jour|heure))\\b"; + + public static final String ConjunctionRegex = "\\b((et(\\s+de|pour)?)|avec)\\b"; + + public static final String HolidayRegex1 = "\\b(?vendredi saint|mercredi des cendres|p[aâ]ques|l'action de gr[âa]ce|mardi gras|la saint-sylvestre|la saint sylvestre|la saint-valentin|la saint valentin|nouvel an chinois|nouvel an|r[eé]veillon de nouvel an|jour de l'an|premier-mai|ler-mai|1-mai|poisson d'avril|r[eé]veillon de no[eë]l|veille de no[eë]l|noël|noel|thanksgiving|halloween|yuandan)(\\s+((d[ue]\\s+|d'))?({YearRegex}|({ThisPrefixRegex}\\s+)ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex2 = "\\b(?martin luther king|martin luther king jr|toussaint|st patrick|st george|cinco de mayo|l'ind[eé]pendance(\\s+am[eé]ricaine)?|guy fawkes)(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex3 = "(?(jour\\s*(d[eu]|des)\\s*(canberra|p[aâ]ques|colomb|bastille|la prise de la bastille|thanks\\s*giving|bapt[êe]me|nationale|d'armistice|inaugueration|marmotte|assomption|femme|comm[ée]moratif)))(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String HolidayRegex4 = "(?(f[eê]te\\s*(d[eu]|des)\\s*)(travail|m[eè]res?|p[eè]res?))(\\s+(de\\s+)?({YearRegex}|{ThisPrefixRegex}\\s+ann[eé]e|ann[eé]e\\s+({PastSuffixRegex}|{NextSuffixRegex})))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex); + + public static final String DateTokenPrefix = "le "; + + public static final String TimeTokenPrefix = "à "; + + public static final String TokenBeforeDate = "le "; + + public static final String TokenBeforeTime = "à "; + + public static final String AMTimeRegex = "(?matin([ée]e)?)"; + + public static final String PMTimeRegex = "\\b(?(d'|l')?apr[eè]s-midi|nuit|((\\s*ce|du)\\s+)?soir)\\b"; + + public static final String BeforeRegex = "\\b(avant)\\b"; + + public static final String BeforeRegex2 = "\\b(entre\\s*(le|la(s)?)?)\\b"; + + public static final String AfterRegex = "\\b(apres)\\b"; + + public static final String SinceRegex = "\\b(depuis)\\b"; + + public static final String AroundRegex = "^[.]"; + + public static final String AgoPrefixRegex = "\\b(y a)\\b"; + + public static final String LaterRegex = "\\b(plus tard)\\b"; + + public static final String AgoRegex = "^[.]"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(dans|en|sur)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "^[.]"; + + public static final String TodayNowRegex = "\\b(aujourd'hui|maintenant)\\b"; + + public static final String MorningStartEndRegex = "(^(matin))|((matin)$)"; + + public static final String AfternoonStartEndRegex = "(^((d'|l')?apr[eè]s-midi))|(((d'|l')?apr[eè]s-midi)$)"; + + public static final String EveningStartEndRegex = "(^(soir[ée]e|soir))|((soir[ée]e|soir)$)"; + + public static final String NightStartEndRegex = "(^(nuit))|((nuit)$)"; + + public static final String InexactNumberRegex = "\\b(quel qu[ée]s|quelqu[ée]s?|plusieurs?|divers)\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+({DurationUnitRegex})" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String RelativeTimeUnitRegex = "(((({ThisPrefixRegex})?)\\s+({TimeUnitRegex}(\\s*{NextSuffixRegex}|{PastSuffixRegex})?))|((le))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{TimeUnitRegex}", TimeUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String RelativeDurationUnitRegex = "((\\b({DurationUnitRegex})(\\s+{NextSuffixRegex}|{PastSuffixRegex})?)|((le|my))\\s+({RestrictedTimeUnitRegex}))" + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PastSuffixRegex}", PastSuffixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex) + .replace("{RestrictedTimeUnitRegex}", RestrictedTimeUnitRegex); + + public static final String ReferenceDatePeriodRegex = "^[.]"; + + public static final String UpcomingPrefixRegex = ".^"; + + public static final String NextPrefixRegex = ".^"; + + public static final String PastPrefixRegex = ".^"; + + public static final String PreviousPrefixRegex = ".^"; + + public static final String RelativeDayRegex = "\\b(((la\\s+)?{RelativeRegex}\\s+journ[ée]e))\\b" + .replace("{RelativeRegex}", RelativeRegex); + + public static final String ConnectorRegex = "^(,|pour|t|vers)$"; + + public static final String ConnectorAndRegex = "\\b(et\\s*(le|las?)?)\\b.+"; + + public static final String FromRegex = "((de|du)?)$"; + + public static final String FromRegex2 = "((depuis|de)(\\s*las?)?)$"; + + public static final String FromToRegex = "\\b(du|depuis|des?).+(au|à|a)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(le\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[.]"; + + public static final String PrepositionSuffixRegex = "\\b(du|de|[àa]|vers|dans)$"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?[A-Za-z\\d]+)"; + + public static final String ForTheRegex = "\\b(((pour le {FlexibleDayRegex})|(dans (le\\s+)?{FlexibleDayRegex}(?<=(st|nd|rd|th))))(?\\s*(,|\\.|!|\\?|$)))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+(le\\s+{FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+(?!(the)){DayRegex}(?!([-:]|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String RestOfDateRegex = "\\b(reste|fin)\\s+(d[eu]\\s+)?((le|ce(tte)?)\\s+)?(?semaine|mois|l'ann[ée]e)\\b"; + + public static final String RestOfDateTimeRegex = "\\b(reste|fin)\\s+(d[eu]\\s+)?((le|ce(tte)?)\\s+)?(?jour)\\b"; + + public static final String LaterEarlyPeriodRegex = "^[.]"; + + public static final String WeekWithWeekDayRangeRegex = "^[.]"; + + public static final String GeneralEndingRegex = "^[.]"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String PrefixArticleRegex = "^[\\.]"; + + public static final String OrRegex = "^[.]"; + + public static final String YearPlusNumberRegex = "^[.]"; + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "^[.]"; + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "^[.]"; + + public static final String DecadeWithCenturyRegex = "^[.]"; + + public static final String RelativeDecadeRegex = "^[.]"; + + public static final String YearSuffix = "(,?\\s*({DateYearRegex}|{FullTextYearRegex}))" + .replace("{DateYearRegex}", DateYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String SuffixAfterRegex = "^[.]"; + + public static final String YearPeriodRegex = "^[.]"; + + public static final String FutureSuffixRegex = "^[.]"; + + public static final String ComplexDatePeriodRegex = "^[.]"; + + public static final String AmbiguousPointRangeRegex = "^(mar\\.?)$"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("annees", "Y") + .put("annee", "Y") + .put("an", "Y") + .put("ans", "Y") + .put("mois", "MON") + .put("semaines", "W") + .put("semaine", "W") + .put("journees", "D") + .put("journee", "D") + .put("jour", "D") + .put("jours", "D") + .put("heures", "H") + .put("heure", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutes", "M") + .put("minute", "M") + .put("mins", "M") + .put("min", "M") + .put("secondes", "S") + .put("seconde", "S") + .put("secs", "S") + .put("sec", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("annees", 31536000L) + .put("annee", 31536000L) + .put("l'annees", 31536000L) + .put("l'annee", 31536000L) + .put("an", 31536000L) + .put("ans", 31536000L) + .put("mois", 2592000L) + .put("semaines", 604800L) + .put("semaine", 604800L) + .put("journees", 86400L) + .put("journee", 86400L) + .put("jour", 86400L) + .put("jours", 86400L) + .put("heures", 3600L) + .put("heure", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutes", 60L) + .put("minute", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("secondes", 1L) + .put("seconde", 1L) + .put("secs", 1L) + .put("sec", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("printemps", "SP") + .put("été", "SU") + .put("automne", "FA") + .put("hiver", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("premier", 1) + .put("1er", 1) + .put("deuxième", 2) + .put("2e", 2) + .put("troisième", 3) + .put("troisieme", 3) + .put("3e", 3) + .put("quatrième", 4) + .put("4e", 4) + .put("cinqième", 5) + .put("5e", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("lundi", 1) + .put("mardi", 2) + .put("mercredi", 3) + .put("jeudi", 4) + .put("vendredi", 5) + .put("samedi", 6) + .put("dimanche", 0) + .put("lun", 1) + .put("mar", 2) + .put("mer", 3) + .put("jeu", 4) + .put("ven", 5) + .put("sam", 6) + .put("dim", 0) + .put("lun.", 1) + .put("mar.", 2) + .put("mer.", 3) + .put("jeu.", 4) + .put("ven.", 5) + .put("sam.", 6) + .put("dim.", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("janvier", 1) + .put("fevrier", 2) + .put("février", 2) + .put("mars", 3) + .put("mar", 3) + .put("mar.", 3) + .put("avril", 4) + .put("avr", 4) + .put("avr.", 4) + .put("mai", 5) + .put("juin", 6) + .put("jun", 6) + .put("jun.", 6) + .put("juillet", 7) + .put("aout", 8) + .put("août", 8) + .put("septembre", 9) + .put("octobre", 10) + .put("novembre", 11) + .put("decembre", 12) + .put("décembre", 12) + .put("janv", 1) + .put("janv.", 1) + .put("jan", 1) + .put("jan.", 1) + .put("fevr", 2) + .put("fevr.", 2) + .put("févr.", 2) + .put("févr", 2) + .put("fev", 2) + .put("fev.", 2) + .put("juil", 7) + .put("jul", 7) + .put("jul.", 7) + .put("sep", 9) + .put("sep.", 9) + .put("sept.", 9) + .put("sept", 9) + .put("oct", 10) + .put("oct.", 10) + .put("nov", 11) + .put("nov.", 11) + .put("dec", 12) + .put("dec.", 12) + .put("déc.", 12) + .put("déc", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("un", 1) + .put("une", 1) + .put("a", 1) + .put("deux", 2) + .put("trois", 3) + .put("quatre", 4) + .put("cinq", 5) + .put("six", 6) + .put("sept", 7) + .put("huit", 8) + .put("neuf", 9) + .put("dix", 10) + .put("onze", 11) + .put("douze", 12) + .put("treize", 13) + .put("quatorze", 14) + .put("quinze", 15) + .put("seize", 16) + .put("dix-sept", 17) + .put("dix-huit", 18) + .put("dix-neuf", 19) + .put("vingt-et-un", 21) + .put("vingt et un", 21) + .put("vingt", 20) + .put("vingt deux", 22) + .put("vingt-deux", 22) + .put("vingt trois", 23) + .put("vingt-trois", 23) + .put("vingt quatre", 24) + .put("vingt-quatre", 24) + .put("vingt cinq", 25) + .put("vingt-cinq", 25) + .put("vingt six", 26) + .put("vingt-six", 26) + .put("vingt sept", 27) + .put("vingt-sept", 27) + .put("vingt huit", 28) + .put("vingt-huit", 28) + .put("vingt neuf", 29) + .put("vingt-neuf", 29) + .put("trente", 30) + .put("trente et un", 31) + .put("trente-et-un", 31) + .put("trente deux", 32) + .put("trente-deux", 32) + .put("trente trois", 33) + .put("trente-trois", 33) + .put("trente quatre", 34) + .put("trente-quatre", 34) + .put("trente cinq", 35) + .put("trente-cinq", 35) + .put("trente six", 36) + .put("trente-six", 36) + .put("trente sept", 37) + .put("trente-sept", 37) + .put("trente huit", 38) + .put("trente-huit", 38) + .put("trente neuf", 39) + .put("trente-neuf", 39) + .put("quarante", 40) + .put("quarante et un", 41) + .put("quarante-et-un", 41) + .put("quarante deux", 42) + .put("quarante-duex", 42) + .put("quarante trois", 43) + .put("quarante-trois", 43) + .put("quarante quatre", 44) + .put("quarante-quatre", 44) + .put("quarante cinq", 45) + .put("quarante-cinq", 45) + .put("quarante six", 46) + .put("quarante-six", 46) + .put("quarante sept", 47) + .put("quarante-sept", 47) + .put("quarante huit", 48) + .put("quarante-huit", 48) + .put("quarante neuf", 49) + .put("quarante-neuf", 49) + .put("cinquante", 50) + .put("cinquante et un", 51) + .put("cinquante-et-un", 51) + .put("cinquante deux", 52) + .put("cinquante-deux", 52) + .put("cinquante trois", 53) + .put("cinquante-trois", 53) + .put("cinquante quatre", 54) + .put("cinquante-quatre", 54) + .put("cinquante cinq", 55) + .put("cinquante-cinq", 55) + .put("cinquante six", 56) + .put("cinquante-six", 56) + .put("cinquante sept", 57) + .put("cinquante-sept", 57) + .put("cinquante huit", 58) + .put("cinquante-huit", 58) + .put("cinquante neuf", 59) + .put("cinquante-neuf", 59) + .put("soixante", 60) + .put("soixante et un", 61) + .put("soixante-et-un", 61) + .put("soixante deux", 62) + .put("soixante-deux", 62) + .put("soixante trois", 63) + .put("soixante-trois", 63) + .put("soixante quatre", 64) + .put("soixante-quatre", 64) + .put("soixante cinq", 65) + .put("soixante-cinq", 65) + .put("soixante six", 66) + .put("soixante-six", 66) + .put("soixante sept", 67) + .put("soixante-sept", 67) + .put("soixante huit", 68) + .put("soixante-huit", 68) + .put("soixante neuf", 69) + .put("soixante-neuf", 69) + .put("soixante dix", 70) + .put("soixante-dix", 70) + .put("soixante et onze", 71) + .put("soixante-et-onze", 71) + .put("soixante douze", 72) + .put("soixante-douze", 72) + .put("soixante treize", 73) + .put("soixante-treize", 73) + .put("soixante quatorze", 74) + .put("soixante-quatorze", 74) + .put("soixante quinze", 75) + .put("soixante-quinze", 75) + .put("soixante seize", 76) + .put("soixante-seize", 76) + .put("soixante dix sept", 77) + .put("soixante-dix-sept", 77) + .put("soixante dix huit", 78) + .put("soixante-dix-huit", 78) + .put("soixante dix neuf", 79) + .put("soixante-dix-neuf", 79) + .put("quatre vingt", 80) + .put("quatre-vingt", 80) + .put("quatre vingt un", 81) + .put("quatre-vingt-un", 81) + .put("quatre vingt deux", 82) + .put("quatre-vingt-duex", 82) + .put("quatre vingt trois", 83) + .put("quatre-vingt-trois", 83) + .put("quatre vingt quatre", 84) + .put("quatre-vingt-quatre", 84) + .put("quatre vingt cinq", 85) + .put("quatre-vingt-cinq", 85) + .put("quatre vingt six", 86) + .put("quatre-vingt-six", 86) + .put("quatre vingt sept", 87) + .put("quatre-vingt-sept", 87) + .put("quatre vingt huit", 88) + .put("quatre-vingt-huit", 88) + .put("quatre vingt neuf", 89) + .put("quatre-vingt-neuf", 89) + .put("quatre vingt dix", 90) + .put("quatre-vingt-dix", 90) + .put("quatre vingt onze", 91) + .put("quatre-vingt-onze", 91) + .put("quatre vingt douze", 92) + .put("quatre-vingt-douze", 92) + .put("quatre vingt treize", 93) + .put("quatre-vingt-treize", 93) + .put("quatre vingt quatorze", 94) + .put("quatre-vingt-quatorze", 94) + .put("quatre vingt quinze", 95) + .put("quatre-vingt-quinze", 95) + .put("quatre vingt seize", 96) + .put("quatre-vingt-seize", 96) + .put("quatre vingt dix sept", 97) + .put("quatre-vingt-dix-sept", 97) + .put("quatre vingt dix huit", 98) + .put("quatre-vingt-dix-huit", 98) + .put("quatre vingt dix neuf", 99) + .put("quatre-vingt-dix-neuf", 99) + .put("cent", 100) + .build(); + + public static final ImmutableMap DayOfMonth = ImmutableMap.builder() + .put("1er", 1) + .put("2e", 2) + .put("3e", 3) + .put("4e", 4) + .put("5e", 5) + .put("6e", 6) + .put("7e", 7) + .put("8e", 8) + .put("9e", 9) + .put("10e", 10) + .put("11e", 11) + .put("12e", 12) + .put("13e", 13) + .put("14e", 14) + .put("15e", 15) + .put("16e", 16) + .put("17e", 17) + .put("18e", 18) + .put("19e", 19) + .put("20e", 20) + .put("21e", 21) + .put("22e", 22) + .put("23e", 23) + .put("24e", 24) + .put("25e", 25) + .put("26e", 26) + .put("27e", 27) + .put("28e", 28) + .put("29e", 29) + .put("30e", 30) + .put("31e", 31) + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("demi", 0.5D) + .put("quart", 0.25D) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("fathers", new String[]{"peres", "pères", "fêtedespères", "fetedesperes"}) + .put("mothers", new String[]{"fêtedesmères", "fetedesmeres"}) + .put("thanksgiving", new String[]{"lactiondegrace", "lactiondegrâce", "jourdethanksgiving", "thanksgiving"}) + .put("martinlutherking", new String[]{"journeemartinlutherking", "martinlutherkingjr"}) + .put("washingtonsbirthday", new String[]{"washingtonsbirthday", "washingtonbirthday"}) + .put("canberra", new String[]{"canberraday"}) + .put("labour", new String[]{"fetedetravail", "travail", "fetedutravail"}) + .put("columbus", new String[]{"columbusday"}) + .put("memorial", new String[]{"jourcommémoratif", "jourcommemoratif"}) + .put("yuandan", new String[]{"yuandan", "nouvelanchinois"}) + .put("maosbirthday", new String[]{"maosbirthday"}) + .put("teachersday", new String[]{"teachersday", "teacherday"}) + .put("singleday", new String[]{"singleday"}) + .put("allsaintsday", new String[]{"allsaintsday"}) + .put("youthday", new String[]{"youthday"}) + .put("childrenday", new String[]{"childrenday", "childday"}) + .put("femaleday", new String[]{"femaleday"}) + .put("treeplantingday", new String[]{"treeplantingday"}) + .put("arborday", new String[]{"arborday"}) + .put("girlsday", new String[]{"girlsday"}) + .put("whiteloverday", new String[]{"whiteloverday"}) + .put("loverday", new String[]{"loverday"}) + .put("christmas", new String[]{"noel", "noël"}) + .put("xmas", new String[]{"xmas"}) + .put("newyear", new String[]{"nouvellesannees", "nouvelan"}) + .put("newyearday", new String[]{"jourdunouvelan"}) + .put("newyearsday", new String[]{"jourdel'an", "jourpremierdelannee", "jourpremierdelannée"}) + .put("inaugurationday", new String[]{"jourd'inaugueration", "inaugueration"}) + .put("groundhougday", new String[]{"marmotte"}) + .put("valentinesday", new String[]{"lasaint-valentin", "lasaintvalentin"}) + .put("stpatrickday", new String[]{"stpatrickday"}) + .put("aprilfools", new String[]{"poissond'avril"}) + .put("stgeorgeday", new String[]{"stgeorgeday"}) + .put("mayday", new String[]{"premier-mai", "ler-mai", "1-mai"}) + .put("cincodemayoday", new String[]{"cincodemayo"}) + .put("baptisteday", new String[]{"bapteme", "baptême"}) + .put("usindependenceday", new String[]{"l'independanceamericaine", "lindépendanceaméricaine"}) + .put("independenceday", new String[]{"l'indépendance", "lindependance"}) + .put("bastilleday", new String[]{"laprisedelabastille", "bastille"}) + .put("halloweenday", new String[]{"halloween"}) + .put("allhallowday", new String[]{"allhallowday"}) + .put("allsoulsday", new String[]{"allsoulsday"}) + .put("guyfawkesday", new String[]{"guyfawkesday"}) + .put("veteransday", new String[]{"veteransday"}) + .put("christmaseve", new String[]{"reveillondenoel", "réveillondenoël", "veilledenoel", "veilledenoël"}) + .put("newyeareve", new String[]{"réveillondenouvelan", "reveillondenouvelan", "lasaint-sylvestre", "lasaintsylvestre"}) + .build(); + + public static final String NightRegex = "\\b(minuit|nuit)\\b"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^([eé]t[eé])$", "(? AmbiguityTimeFiltersDict = ImmutableMap.builder() + .put("heures?$", "\\b(pour|durée\\s+de|pendant)\\s+(\\S+\\s+){1,2}heures?\\b") + .build(); + + public static final List MorningTermList = Arrays.asList("matinee", "matin", "matinée"); + + public static final List AfternoonTermList = Arrays.asList("apres-midi", "apres midi", "après midi", "après-midi"); + + public static final List EveningTermList = Arrays.asList("soir", "soiree", "soirée"); + + public static final List DaytimeTermList = Arrays.asList("jour", "journee", "journée"); + + public static final List NightTermList = Arrays.asList("nuit"); + + public static final List SameDayTerms = Arrays.asList("aujourd'hui", "auj"); + + public static final List PlusOneDayTerms = Arrays.asList("demain", "a2m1", "lendemain", "jour suivant"); + + public static final List MinusOneDayTerms = Arrays.asList("hier", "dernier"); + + public static final List PlusTwoDayTerms = Arrays.asList("après demain", "après-demain", "apres-demain"); + + public static final List MinusTwoDayTerms = Arrays.asList("avant-hier", "avant hier"); + + public static final List FutureStartTerms = Arrays.asList("cette"); + + public static final List FutureEndTerms = Arrays.asList("prochaine", "prochain"); + + public static final List LastCardinalTerms = Arrays.asList("dernières", "dernière", "dernieres", "derniere", "dernier"); + + public static final List MonthTerms = Arrays.asList("mois"); + + public static final List MonthToDateTerms = Arrays.asList("mois à ce jour"); + + public static final List WeekendTerms = Arrays.asList("fin de semaine", "le weekend"); + + public static final List WeekTerms = Arrays.asList("semaine"); + + public static final List YearTerms = Arrays.asList("années", "ans", "an", "l'annees", "l'annee"); + + public static final List YearToDateTerms = Arrays.asList("année à ce jour", "an à ce jour"); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java new file mode 100644 index 000000000..111190643 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/PortugueseDateTime.java @@ -0,0 +1,983 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseDateTime { + + public static final String LangMarker = "Por"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(at[eé]h?|[aà]s|ao?)\\b|--|-|—|——)(\\s+\\b(o|[aà](s)?)\\b)?"; + + public static final String RangeConnectorRegex = "(?(e\\s*(([àa]s?)|o)?)|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String DayRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String AmDescRegex = "({BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "({BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "({BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?({AmDescRegex}|{PmDescRegex}))" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String OfPrepositionRegex = "(\\bd(o|a|e)s?\\b)"; + + public static final String AfterNextSuffixRegex = "\\b(que\\s+vem|passad[oa])\\b"; + + public static final String RangePrefixRegex = "((de(sde)?|das?|entre)\\s+(a(s)?\\s+)?)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d)))\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String RelativeRegex = "(?((est[ae]|pr[oó]xim[oa]|([uú]ltim(o|as|os)))(\\s+fina(l|is)\\s+d[eao])?)|(fina(l|is)\\s+d[eao]))\\b"; + + public static final String StrictRelativeRegex = "(?((est[ae]|pr[oó]xim[oa]|([uú]ltim(o|as|os)))(\\s+fina(l|is)\\s+d[eao])?)|(fina(l|is)\\s+d[eao]))\\b"; + + public static final String WrittenOneToNineRegex = "(uma?|dois|duas|tr[eê]s|quatro|cinco|seis|sete|oito|nove)"; + + public static final String WrittenOneHundredToNineHundredRegex = "(duzent[oa]s|trezent[oa]s|[cq]uatrocent[ao]s|quinhent[ao]s|seiscent[ao]s|setecent[ao]s|oitocent[ao]s|novecent[ao]s|cem|(?((dois\\s+)?mil)((\\s+e)?\\s+{WrittenOneHundredToNineHundredRegex})?((\\s+e)?\\s+{WrittenOneToNinetyNineRegex})?)" + .replace("{WrittenOneToNinetyNineRegex}", WrittenOneToNinetyNineRegex) + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex); + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String RelativeMonthRegex = "(?([nd]?es[st]e|pr[óo]ximo|passsado|[uú]ltimo)\\s+m[eê]s)\\b"; + + public static final String MonthRegex = "(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)"; + + public static final String MonthSuffixRegex = "(?((em|no)\\s+|d[eo]\\s+)?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateUnitRegex = "(?anos?|meses|m[êe]s|semanas?|dias?)\\b"; + + public static final String PastRegex = "(?\\b(passad[ao](s)?|[uú]ltim[oa](s)?|anterior(es)?|h[aá]|pr[ée]vi[oa](s)?)\\b)"; + + public static final String FutureRegex = "(?\\b(seguinte(s)?|pr[oó]xim[oa](s)?|dentro\\s+de|em|daqui\\s+a)\\b)"; + + public static final String SimpleCasesRegex = "\\b((desde\\s+[oa]|desde|d[oa])\\s+)?(dia\\s+)?({DayRegex})\\s*{TillRegex}\\s*(o dia\\s+)?({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b{MonthSuffixRegex}\\s+((desde\\s+[oa]|desde|d[oa])\\s+)?(dia\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+((entre|entre\\s+[oa]s?)\\s+)(dias?\\s+)?({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String DayBetweenRegex = "\\b((entre|entre\\s+[oa]s?)\\s+)(dia\\s+)?({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*){YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String OneWordPeriodRegex = "\\b(((pr[oó]xim[oa]?|[nd]?es[st]e|aquel[ea]|[uú]ltim[oa]?|em)\\s+)?(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)|(?<=\\b(de|do|da|o|a)\\s+)?(pr[oó]xim[oa](s)?|[uú]ltim[oa]s?|est(e|a))\\s+(fim de semana|fins de semana|semana|m[êe]s|ano)|fim de semana|fins de semana|(m[êe]s|anos)? [àa] data)\\b"; + + public static final String MonthWithYearRegex = "\\b(((pr[oó]xim[oa](s)?|[nd]?es[st]e|aquele|[uú]ltim[oa]?|em)\\s+)?(?abr(il)?|ago(sto)?|dez(embro)?|fev(ereiro)?|jan(eiro)?|ju[ln](ho)?|mar([çc]o)?|maio?|nov(embro)?|out(ubro)?|sep?t(embro)?)\\s+((de|do|da|o|a)\\s+)?({YearRegex}|{TwoDigitYearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String MonthNumWithYearRegex = "({YearRegex}(\\s*?)[/\\-\\.](\\s*?){MonthNumRegex})|({MonthNumRegex}(\\s*?)[/\\-](\\s*?){YearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(a|na\\s+)?(?primeira?|1a|segunda|2a|terceira|3a|[qc]uarta|4a|quinta|5a|[uú]ltima)\\s+semana\\s+{MonthSuffixRegex})" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String WeekOfYearRegex = "(?(a|na\\s+)?(?primeira?|1a|segunda|2a|terceira|3a|[qc]uarta|4a|quinta|5a|[uú]ltima?)\\s+semana(\\s+d[oe]?)?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|[nd]?es[st]e)\\s+ano))" + .replace("{YearRegex}", YearRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterRegex = "(n?o\\s+)?(?primeiro|1[oº]|segundo|2[oº]|terceiro|3[oº]|[qc]uarto|4[oº])\\s+trimestre(\\s+d[oe]|\\s*,\\s*)?\\s+({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano)" + .replace("{YearRegex}", YearRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|[nd]?es[st]e)\\s+ano)\\s+(n?o\\s+)?(?(primeiro)|1[oº]|segundo|2[oº]|terceiro|3[oº]|[qc]uarto|4[oº])\\s+trimestre" + .replace("{YearRegex}", YearRegex); + + public static final String AllHalfYearRegex = "^[.]"; + + public static final String PrefixDayRegex = "^[.]"; + + public static final String SeasonRegex = "\\b(?(([uú]ltim[oa]|[nd]?es[st][ea]|n?[oa]|(pr[oó]xim[oa]s?|seguinte))\\s+)?(?primavera|ver[ãa]o|outono|inverno)((\\s+)?(seguinte|((de\\s+|,)?\\s*{YearRegex})|((do\\s+)?(?pr[oó]ximo|[uú]ltimo|[nd]?es[st]e)\\s+ano)))?)\\b" + .replace("{YearRegex}", YearRegex); + + public static final String WhichWeekRegex = "\\b(semana)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "(semana)(\\s*)((do|da|de))"; + + public static final String MonthOfRegex = "(mes)(\\s*)((do|da|de))"; + + public static final String RangeUnitRegex = "\\b(?anos?|meses|m[êe]s|semanas?)\\b"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(em)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "^[.]"; + + public static final String TodayNowRegex = "\\b(hoje|agora)\\b"; + + public static final String CenturySuffixRegex = "^[.]"; + + public static final String FromRegex = "((desde|de)(\\s*a(s)?)?)$"; + + public static final String BetweenRegex = "(entre\\s*([oa](s)?)?)"; + + public static final String WeekDayRegex = "\\b(?(domingos?|(segunda|ter[çc]a|quarta|quinta|sexta)s?([-\\s+]feiras?)?|s[aá]bados?|(2|3|4|5|6)[aª])\\b|(dom|seg|ter[cç]|qua|qui|sex|sab)\\b(\\.?(?=\\s|,|;|$)))"; + + public static final String OnRegex = "(?<=\\b(em|no)\\s+)({DayRegex}s?)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(em|n[oa]|d[oa])\\s+)(dia\\s+)?((?10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)s?)\\b"; + + public static final String ThisRegex = "\\b(([nd]?es[st][ea]\\s*){WeekDayRegex})|({WeekDayRegex}\\s*([nd]?es[st]a\\s+semana))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String LastDateRegex = "\\b(([uú]ltim[ao])\\s*{WeekDayRegex})|({WeekDayRegex}(\\s+(([nd]?es[st]a|na|da)\\s+([uú]ltima\\s+)?semana)))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String NextDateRegex = "\\b(((pr[oó]xim[oa]|seguinte)\\s*){WeekDayRegex})|({WeekDayRegex}((\\s+(pr[oó]xim[oa]|seguinte))|(\\s+(da\\s+)?(semana\\s+seguinte|pr[oó]xima\\s+semana))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String SpecialDayRegex = "\\b((d?o\\s+)?(dia\\s+antes\\s+de\\s+ontem|antes\\s+de\\s+ontem|anteontem)|((d?o\\s+)?(dia\\s+|depois\\s+|dia\\s+depois\\s+)?de\\s+amanh[aã])|(o\\s)?dia\\s+seguinte|(o\\s)?pr[oó]ximo\\s+dia|(o\\s+)?[uú]ltimo\\s+dia|ontem|amanh[ãa]|hoje)|(do\\s+dia$)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String ForTheRegex = ".^"; + + public static final String WeekDayAndDayOfMonthRegex = ".^"; + + public static final String WeekDayAndDayRegex = ".^"; + + public static final String WeekDayOfMonthRegex = "(?(n?[ao]\\s+)?(?primeir[ao]|1[ao]|segund[ao]|2[ao]|terceir[ao]|3[ao]|[qc]uart[ao]|4[ao]|quint[ao]|5[ao]|[uú]ltim[ao])\\s+{WeekDayRegex}\\s+{MonthSuffixRegex})" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String SpecialDateRegex = "(?<=\\bno\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String OfMonthRegex = "^\\s*de\\s*{MonthSuffixRegex}" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String MonthEndRegex = "({MonthRegex}\\s*(o)?\\s*$)" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String DateYearRegex = "(?{YearRegex}|{TwoDigitYearRegex})" + .replace("{YearRegex}", YearRegex) + .replace("{TwoDigitYearRegex}", TwoDigitYearRegex); + + public static final String DateExtractor1 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}?((\\s*(de)|[/\\\\\\.\\-])\\s*)?{MonthRegex}\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateExtractor2 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}\\s*([\\.\\-]|de)?\\s*{MonthRegex}(\\s*(,|de)\\s*){DateYearRegex}\\b" + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String DateExtractor3 = "\\b({WeekDayRegex}(\\s+|\\s*,\\s*))?{DayRegex}(\\s+|\\s*,\\s*|\\s+de\\s+|\\s*-\\s*){MonthRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor4 = "\\b{MonthNumRegex}\\s*[/\\\\\\-]\\s*{DayRegex}\\s*[/\\\\\\-]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor5 = "\\b{DayRegex}\\s*[/\\\\\\-\\.]\\s*({MonthNumRegex}|{MonthRegex})\\s*[/\\\\\\-\\.]\\s*{DateYearRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor6 = "(?<=\\b(em|no|o)\\s+){MonthNumRegex}[\\-\\.]{DayRegex}\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor7 = "\\b{MonthNumRegex}\\s*/\\s*{DayRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor8 = "(?<=\\b(em|no|o)\\s+){DayRegex}[\\\\\\-]{MonthNumRegex}\\b" + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor9 = "\\b{DayRegex}\\s*/\\s*{MonthNumRegex}((\\s+|\\s*(,|de)\\s*){DateYearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DateYearRegex}", DateYearRegex); + + public static final String DateExtractor10 = "\\b{YearRegex}\\s*[/\\\\\\-\\.]\\s*{MonthNumRegex}\\s*[/\\\\\\-\\.]\\s*{DayRegex}(?!\\s*[/\\\\\\-\\.]\\s*\\d+)" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex) + .replace("{DayRegex}", DayRegex); + + public static final String DateExtractor11 = "(?<=\\b(dia)\\s+){DayRegex}" + .replace("{DayRegex}", DayRegex); + + public static final String HourNumRegex = "\\b(?zero|uma|duas|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze)\\b"; + + public static final String MinuteNumRegex = "(?um|dois|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|catorze|quatorze|quinze|dez[ea]sseis|dez[ea]sete|dezoito|dez[ea]nove|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String DeltaMinuteNumRegex = "(?um|dois|tr[êe]s|[qc]uatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|catorze|quatorze|quinze|dez[ea]sseis|dez[ea]sete|dezoito|dez[ea]nove|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String OclockRegex = "(?em\\s+ponto)"; + + public static final String PmRegex = "(?((pela|de|da|\\b[àa]\\b|na)\\s+(tarde|noite)))|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia))"; + + public static final String AmRegex = "(?(pela|de|da|na)\\s+(manh[ãa]|madrugada))"; + + public static final String AmTimeRegex = "(?([dn]?es[st]a|(pela|de|da|na))\\s+(manh[ãa]|madrugada))"; + + public static final String PmTimeRegex = "(?(([dn]?es[st]a|\\b[àa]\\b|(pela|de|da|na))\\s+(tarde|noite)))|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia))"; + + public static final String LessThanOneHour = "(?((\\s+e\\s+)?(quinze|(um\\s+|dois\\s+|tr[êes]\\s+)?quartos?)|quinze|(\\s*)(um\\s+|dois\\s+|tr[êes]\\s+)?quartos?|(\\s+e\\s+)(meia|trinta)|{BaseDateTime.DeltaMinuteRegex}(\\s+(minuto|minutos|min|mins))|{DeltaMinuteNumRegex}(\\s+(minuto|minutos|min|mins))))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String TensTimeRegex = "(?dez|vinte|trinta|[qc]uarenta|cin[qc]uenta)"; + + public static final String WrittenTimeRegex = "(?({HourNumRegex}\\s*((e|menos)\\s+)?({MinuteNumRegex}|({TensTimeRegex}((\\s*e\\s+)?{MinuteNumRegex}))))|(({MinuteNumRegex}|({TensTimeRegex}((\\s*e\\s+)?{MinuteNumRegex})?))\\s*((para as|pras|antes da|antes das)\\s+)?({HourNumRegex}|{BaseDateTime.HourRegex})))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}(\\s+(passad[ao]s)\\s+(as)?|\\s+depois\\s+(das?|do)|\\s+pras?|\\s+(para|antes)?\\s+([àa]s?))?)" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?({LessThanOneHour}\\s+)?({AmRegex}|{PmRegex}|{OclockRegex}))" + .replace("{LessThanOneHour}", LessThanOneHour) + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String AtRegex = "\\b((?<=\\b([aà]s?)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s+horas?|\\s*h\\b)?|(?<=\\b(s(er)?[aã]o|v[aã]o\\s+ser|^[eé]h?)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})(\\s+horas?|\\s*h\\b))(\\s+{OclockRegex})?\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String ConnectNumRegex = "({BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex1 = "(\\b{TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*({DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex}(\\s*{DescRegex})" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex4 = "\\b(({DescRegex}?)|({BasicTime}?)({DescRegex}?))({TimePrefix}\\s*)({HourNumRegex}|{BaseDateTime.HourRegex})?(\\s+{TensTimeRegex}(\\s+e\\s+)?{MinuteNumRegex}?)?({OclockRegex})?\\b" + .replace("{DescRegex}", DescRegex) + .replace("{BasicTime}", BasicTime) + .replace("{TimePrefix}", TimePrefix) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeRegex5 = "\\b({TimePrefix}|{BasicTime}{TimePrefix})\\s+(\\s*{DescRegex})?{BasicTime}?\\s*{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex6 = "({BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b)" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+[àa]s?\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b(?{HourNumRegex}\\s+({TensTimeRegex}\\s*)(e\\s+)?{MinuteNumRegex}?)\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimeRegex10 = "(\\b([àa]|ao?)|na|de|da|pela)\\s+(madrugada|manh[ãa]|meio\\s*dia|meia\\s*noite|tarde|noite)"; + + public static final String TimeRegex11 = "\\b({WrittenTimeRegex})(\\s+{DescRegex})?\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex12 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String PrepositionRegex = "(?([àa]s?|em|por|pelo|pela|no|na|de|d[oa]?)?$)"; + + public static final String NowRegex = "\\b(?((logo|exatamente)\\s+)?agora(\\s+mesmo)?|neste\\s+momento|(assim\\s+que|t[ãa]o\\s+cedo\\s+quanto)\\s+(poss[ií]vel|possas?|possamos)|o\\s+mais\\s+(cedo|r[aá]pido)\\s+poss[íi]vel|recentemente|previamente)\\b"; + + public static final String SuffixRegex = "^\\s*((e|a|em|por|pelo|pela|no|na|de)\\s+)?(manh[ãa]|madrugada|meio\\s*dia|tarde|noite)\\b"; + + public static final String TimeOfDayRegex = "\\b(?manh[ãa]|madrugada|tarde|noite|((depois\\s+do|ap[óo]s\\s+o)\\s+(almo[çc]o|meio dia|meio-dia)))\\b"; + + public static final String SpecificTimeOfDayRegex = "\\b(((((a)?\\s+|[nd]?es[st]a|seguinte|pr[oó]xim[oa]|[uú]ltim[oa])\\s+)?{TimeOfDayRegex}))\\b" + .replace("{TimeOfDayRegex}", TimeOfDayRegex); + + public static final String TimeOfTodayAfterRegex = "^\\s*(,\\s*)?([àa]|em|por|pelo|pela|de|no|na?\\s+)?{SpecificTimeOfDayRegex}" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String TimeOfTodayBeforeRegex = "({SpecificTimeOfDayRegex}(\\s*,)?(\\s+(a\\s+la(s)?|para))?\\s*)" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayAfterRegex = "({HourNumRegex}|{BaseDateTime.HourRegex})\\s*(,\\s*)?((en|de(l)?)?\\s+)?{SpecificTimeOfDayRegex}" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex); + + public static final String SimpleTimeOfTodayBeforeRegex = "({SpecificTimeOfDayRegex}(\\s*,)?(\\s+(a\\s+la|para))?\\s*({HourNumRegex}|{BaseDateTime.HourRegex}))" + .replace("{SpecificTimeOfDayRegex}", SpecificTimeOfDayRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex); + + public static final String SpecificEndOfRegex = "((no|ao)\\s+)?(fi(m|nal)|t[ée]rmin(o|ar))(\\s+d?o(\\s+dia)?(\\s+de)?)?\\s*$"; + + public static final String UnspecificEndOfRegex = "^[.]"; + + public static final String UnspecificEndOfRangeRegex = "^[.]"; + + public static final String UnitRegex = "(?anos|ano|meses|m[êe]s|semanas|semana|dias|dia|horas|hora|h|hr|hrs|hs|minutos|minuto|mins|min|segundos|segundo|segs|seg)\\b"; + + public static final String ConnectorRegex = "^(,|t|para [ao]|para as|pras|cerca de|cerca das|perto de|perto das|quase)$"; + + public static final String TimeHourNumRegex = "(?vinte e um|vinte e dois|vinte e tr[êe]s|vinte e quatro|zero|um|uma|dois|duas|tr[êe]s|quatro|cinco|seis|sete|oito|nove|dez|onze|doze|treze|quatorze|catorze|quinze|dez[ea]sseis|dez[ea]ssete|dezoito|dez[ea]nove|vinte)"; + + public static final String PureNumFromTo = "((desde|de|da|das)\\s+(a(s)?\\s+)?)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*{TillRegex}\\s*({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(entre\\s+((a|as)?\\s+)?)({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*e\\s*(a(s)?\\s+)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String SpecificTimeFromTo = "^[.]"; + + public static final String SpecificTimeBetweenAnd = "^[.]"; + + public static final String TimeUnitRegex = "(?horas|hora|h|minutos|minuto|mins|min|segundos|segundo|secs|sec)\\b"; + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b((e|[àa]|em|na|no|ao|pel[ao]|de)\\s+)?(?manh[ãa]|madrugada|(passado\\s+(o\\s+)?)?meio\\s+dia|tarde|noite)\\b"; + + public static final String RelativeTimeUnitRegex = "({PastRegex}|{FutureRegex})\\s+{UnitRegex}|{UnitRegex}\\s+({PastRegex}|{FutureRegex})" + .replace("{PastRegex}", PastRegex) + .replace("{FutureRegex}", FutureRegex) + .replace("{UnitRegex}", UnitRegex); + + public static final String SuffixAndRegex = "(?\\s*(e)\\s+(?meia|(um\\s+)?quarto))"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String LessThanRegex = "^[.]"; + + public static final String MoreThanRegex = "^[.]"; + + public static final String DurationNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String AnUnitRegex = "\\b(um(a)?)\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?tod[oa]?\\s+(o|a)\\s+(?ano|m[êe]s|semana|dia))\\b"; + + public static final String HalfRegex = "\\b(?mei[oa]\\s+(?ano|m[êe]s|semana|dia|hora))\\b"; + + public static final String ConjunctionRegex = "^[.]"; + + public static final String InexactNumberRegex = "\\b(poucos|pouco|algum|alguns|v[áa]rios)\\b"; + + public static final String InexactNumberUnitRegex = "\\b(poucos|pouco|algum|alguns|v[áa]rios)\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String HolidayRegex1 = "\\b(?sexta-feira santa|sexta-feira da paix[ãa]o|quarta-feira de cinzas|carnaval|dia (de|de los) presidentes?|ano novo chin[eê]s|ano novo|v[ée]spera de ano novo|natal|v[ée]spera de natal|dia de a[cç][ãa]o de gra[çc]as|a[cç][ãa]o de gra[çc]as|yuandan|halloween|dia das bruxas|p[áa]scoa)(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex2 = "\\b(?(dia\\s+(d[eoa]s?\\s+)?)?(martin luther king|todos os santos|s[ãa]o (patr[íi]cio|francisco|jorge|jo[ãa]o)|independ[êe]ncia))(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex3 = "\\b(?(dia\\s+d[eoa]s?\\s+)(trabalh(o|ador(es)?)|m[ãa]es?|pais?|mulher(es)?|crian[çc]as?|marmota|professor(es)?))(\\s+(d[eo]?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|[nd]?es[st][ea]|[uú]ltim[oa]?|em))\\s+ano))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String BeforeRegex = "(antes(\\s+(de|dos?|das?)?)?)"; + + public static final String AfterRegex = "((depois|ap[óo]s)(\\s*(de|d?os?|d?as?)?)?)"; + + public static final String SinceRegex = "(desde(\\s+(as?|o))?)"; + + public static final String AroundRegex = "^[.]"; + + public static final String PeriodicRegex = "\\b(?di[áa]ri[ao]|diariamente|mensalmente|semanalmente|quinzenalmente|anualmente)\\b"; + + public static final String EachExpression = "cada|tod[oa]s?\\s*([oa]s)?"; + + public static final String EachUnitRegex = "(?({EachExpression})\\s*{UnitRegex})" + .replace("{EachExpression}", EachExpression) + .replace("{UnitRegex}", UnitRegex); + + public static final String EachPrefixRegex = "(?({EachExpression})\\s*$)" + .replace("{EachExpression}", EachExpression); + + public static final String EachDayRegex = "\\s*({EachExpression})\\s*dias\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String BeforeEachDayRegex = "({EachExpression})\\s*dias(\\s+(as|ao))?\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String SetEachRegex = "(?({EachExpression})\\s*)" + .replace("{EachExpression}", EachExpression); + + public static final String LaterEarlyPeriodRegex = "^[.]"; + + public static final String WeekWithWeekDayRangeRegex = "^[.]"; + + public static final String GeneralEndingRegex = "^[.]"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String PrefixArticleRegex = "^[\\.]"; + + public static final String OrRegex = "^[.]"; + + public static final String YearPlusNumberRegex = "^[.]"; + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "^[.]"; + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String ComplexDatePeriodRegex = "^[.]"; + + public static final String AgoRegex = "\\b(antes|atr[áa]s|no passado)\\b"; + + public static final String LaterRegex = "\\b(depois d[eoa]s?|ap[óo]s (as)?|desde (as|o)|desde|no futuro|mais tarde)\\b"; + + public static final String Tomorrow = "amanh[ãa]"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("anos", "Y") + .put("ano", "Y") + .put("meses", "MON") + .put("mes", "MON") + .put("mês", "MON") + .put("semanas", "W") + .put("semana", "W") + .put("dias", "D") + .put("dia", "D") + .put("horas", "H") + .put("hora", "H") + .put("hrs", "H") + .put("hr", "H") + .put("h", "H") + .put("minutos", "M") + .put("minuto", "M") + .put("mins", "M") + .put("min", "M") + .put("segundos", "S") + .put("segundo", "S") + .put("segs", "S") + .put("seg", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("anos", 31536000L) + .put("ano", 31536000L) + .put("meses", 2592000L) + .put("mes", 2592000L) + .put("mês", 2592000L) + .put("semanas", 604800L) + .put("semana", 604800L) + .put("dias", 86400L) + .put("dia", 86400L) + .put("horas", 3600L) + .put("hora", 3600L) + .put("hrs", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutos", 60L) + .put("minuto", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("segundos", 1L) + .put("segundo", 1L) + .put("segs", 1L) + .put("seg", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("primavera", "SP") + .put("verao", "SU") + .put("verão", "SU") + .put("outono", "FA") + .put("inverno", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("primeiro", 1) + .put("primeira", 1) + .put("1o", 1) + .put("1a", 1) + .put("segundo", 2) + .put("segunda", 2) + .put("2o", 2) + .put("2a", 2) + .put("terceiro", 3) + .put("terceira", 3) + .put("3o", 3) + .put("3a", 3) + .put("cuarto", 4) + .put("quarto", 4) + .put("cuarta", 4) + .put("quarta", 4) + .put("4o", 4) + .put("4a", 4) + .put("quinto", 5) + .put("quinta", 5) + .put("5o", 5) + .put("5a", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("segunda-feira", 1) + .put("segundas-feiras", 1) + .put("segunda feira", 1) + .put("segundas feiras", 1) + .put("segunda", 1) + .put("segundas", 1) + .put("terça-feira", 2) + .put("terças-feiras", 2) + .put("terça feira", 2) + .put("terças feiras", 2) + .put("terça", 2) + .put("terças", 2) + .put("terca-feira", 2) + .put("tercas-feiras", 2) + .put("terca feira", 2) + .put("tercas feiras", 2) + .put("terca", 2) + .put("tercas", 2) + .put("quarta-feira", 3) + .put("quartas-feiras", 3) + .put("quarta feira", 3) + .put("quartas feiras", 3) + .put("quarta", 3) + .put("quartas", 3) + .put("quinta-feira", 4) + .put("quintas-feiras", 4) + .put("quinta feira", 4) + .put("quintas feiras", 4) + .put("quinta", 4) + .put("quintas", 4) + .put("sexta-feira", 5) + .put("sextas-feiras", 5) + .put("sexta feira", 5) + .put("sextas feiras", 5) + .put("sexta", 5) + .put("sextas", 5) + .put("sabado", 6) + .put("sabados", 6) + .put("sábado", 6) + .put("sábados", 6) + .put("domingo", 0) + .put("domingos", 0) + .put("seg", 1) + .put("seg.", 1) + .put("2a", 1) + .put("ter", 2) + .put("ter.", 2) + .put("3a", 2) + .put("qua", 3) + .put("qua.", 3) + .put("4a", 3) + .put("qui", 4) + .put("qui.", 4) + .put("5a", 4) + .put("sex", 5) + .put("sex.", 5) + .put("6a", 5) + .put("sab", 6) + .put("sab.", 6) + .put("dom", 0) + .put("dom.", 0) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("janeiro", 1) + .put("fevereiro", 2) + .put("março", 3) + .put("marco", 3) + .put("abril", 4) + .put("maio", 5) + .put("junho", 6) + .put("julho", 7) + .put("agosto", 8) + .put("septembro", 9) + .put("setembro", 9) + .put("outubro", 10) + .put("novembro", 11) + .put("dezembro", 12) + .put("jan", 1) + .put("fev", 2) + .put("mar", 3) + .put("abr", 4) + .put("mai", 5) + .put("jun", 6) + .put("jul", 7) + .put("ago", 8) + .put("sept", 9) + .put("set", 9) + .put("out", 10) + .put("nov", 11) + .put("dez", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("zero", 0) + .put("um", 1) + .put("uma", 1) + .put("dois", 2) + .put("tres", 3) + .put("três", 3) + .put("quatro", 4) + .put("cinco", 5) + .put("seis", 6) + .put("sete", 7) + .put("oito", 8) + .put("nove", 9) + .put("dez", 10) + .put("onze", 11) + .put("doze", 12) + .put("dezena", 12) + .put("dezenas", 12) + .put("treze", 13) + .put("catorze", 14) + .put("quatorze", 14) + .put("quinze", 15) + .put("dezesseis", 16) + .put("dezasseis", 16) + .put("dezessete", 17) + .put("dezassete", 17) + .put("dezoito", 18) + .put("dezenove", 19) + .put("dezanove", 19) + .put("vinte", 20) + .put("vinte e um", 21) + .put("vinte e uma", 21) + .put("vinte e dois", 22) + .put("vinte e duas", 22) + .put("vinte e tres", 23) + .put("vinte e três", 23) + .put("vinte e quatro", 24) + .put("vinte e cinco", 25) + .put("vinte e seis", 26) + .put("vinte e sete", 27) + .put("vinte e oito", 28) + .put("vinte e nove", 29) + .put("trinta", 30) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("pai", new String[]{"diadopai", "diadospais"}) + .put("mae", new String[]{"diadamae", "diadasmaes"}) + .put("acaodegracas", new String[]{"diadegracas", "diadeacaodegracas", "acaodegracas"}) + .put("trabalho", new String[]{"diadotrabalho", "diadotrabalhador", "diadostrabalhadores"}) + .put("pascoa", new String[]{"diadepascoa", "pascoa"}) + .put("natal", new String[]{"natal", "diadenatal"}) + .put("vesperadenatal", new String[]{"vesperadenatal"}) + .put("anonovo", new String[]{"anonovo", "diadeanonovo", "diadoanonovo"}) + .put("vesperadeanonovo", new String[]{"vesperadeanonovo", "vesperadoanonovo"}) + .put("yuandan", new String[]{"yuandan"}) + .put("todosossantos", new String[]{"todosossantos"}) + .put("professor", new String[]{"diadoprofessor", "diadosprofessores"}) + .put("crianca", new String[]{"diadacrianca", "diadascriancas"}) + .put("mulher", new String[]{"diadamulher"}) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("pai", "-06-WXX-7-3") + .put("mae", "-05-WXX-7-2") + .put("acaodegracas", "-11-WXX-4-4") + .put("memoria", "-03-WXX-2-4") + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("metade", 0.5D) + .put("quarto", 0.25D) + .build(); + + public static final String DateTokenPrefix = "em "; + + public static final String TimeTokenPrefix = "as "; + + public static final String TokenBeforeDate = "o "; + + public static final String TokenBeforeTime = "as "; + + public static final String UpcomingPrefixRegex = ".^"; + + public static final String NextPrefixRegex = "(pr[oó]xim[oa]|seguinte|{UpcomingPrefixRegex})\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String PastPrefixRegex = ".^"; + + public static final String PreviousPrefixRegex = "([uú]ltim[oa]|{PastPrefixRegex})\\b" + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "([nd]?es[st][ea])\\b"; + + public static final String RelativeDayRegex = "^[\\.]"; + + public static final String RestOfDateRegex = "^[\\.]"; + + public static final String RelativeDurationUnitRegex = "^[\\.]"; + + public static final String ReferenceDatePeriodRegex = "^[.]"; + + public static final String FromToRegex = "\\b(from).+(to)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[.]"; + + public static final String PrepositionSuffixRegex = "\\b(on|in|at|around|from|to)$"; + + public static final String RestOfDateTimeRegex = "^[\\.]"; + + public static final String SetWeekDayRegex = "^[\\.]"; + + public static final String NightRegex = "\\b(meia noite|noite|de noite)\\b"; + + public static final String CommonDatePrefixRegex = "\\b(dia)\\s+$"; + + public static final String DurationUnitRegex = "^[\\.]"; + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "^[.]"; + + public static final String DecadeWithCenturyRegex = "^[.]"; + + public static final String RelativeDecadeRegex = "^[.]"; + + public static final String YearSuffix = "((,|\\sde)?\\s*({YearRegex}|{FullTextYearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String SuffixAfterRegex = "^[.]"; + + public static final String YearPeriodRegex = "^[.]"; + + public static final String FutureSuffixRegex = "^[.]"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); + + public static final List EarlyMorningTermList = Arrays.asList("madrugada"); + + public static final List MorningTermList = Arrays.asList("manha", "manhã"); + + public static final List AfternoonTermList = Arrays.asList("passado o meio dia", "depois do meio dia"); + + public static final List EveningTermList = Arrays.asList("tarde"); + + public static final List NightTermList = Arrays.asList("noite"); + + public static final List SameDayTerms = Arrays.asList("hoje", "este dia", "esse dia", "o dia"); + + public static final List PlusOneDayTerms = Arrays.asList("amanha", "de amanha", "dia seguinte", "o dia de amanha", "proximo dia"); + + public static final List MinusOneDayTerms = Arrays.asList("ontem", "ultimo dia"); + + public static final List PlusTwoDayTerms = Arrays.asList("depois de amanha", "dia depois de amanha"); + + public static final List MinusTwoDayTerms = Arrays.asList("anteontem", "dia antes de ontem"); + + public static final List MonthTerms = Arrays.asList("mes", "meses"); + + public static final List MonthToDateTerms = Arrays.asList("mes ate agora", "mes ate hoje", "mes ate a data"); + + public static final List WeekendTerms = Arrays.asList("fim de semana"); + + public static final List WeekTerms = Arrays.asList("semana"); + + public static final List YearTerms = Arrays.asList("ano", "anos"); + + public static final List YearToDateTerms = Arrays.asList("ano ate agora", "ano ate hoje", "ano ate a data", "anos ate agora", "anos ate hoje", "anos ate a data"); + + public static final ImmutableMap SpecialCharactersEquivalent = ImmutableMap.builder() + .put('á', 'a') + .put('é', 'e') + .put('í', 'i') + .put('ó', 'o') + .put('ú', 'u') + .put('ê', 'e') + .put('ô', 'o') + .put('ü', 'u') + .put('ã', 'a') + .put('õ', 'o') + .put('ç', 'c') + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java new file mode 100644 index 000000000..fb51b40ac --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java @@ -0,0 +1,1204 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.datetime.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishDateTime { + + public static final String LangMarker = "Spa"; + + public static final Boolean CheckBothBeforeAfter = false; + + public static final String TillRegex = "(?\\b(hasta|hacia|al?)\\b(\\s+(el|la(s)?)\\b)?|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String StrictTillRegex = "(?\\b(hasta|hacia|al?)(\\s+(el|la(s)?))?\\b|{BaseDateTime.RangeConnectorSymbolRegex}(?!\\s*[qt][1-4](?!(\\s+de|\\s*,\\s*))))" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String RangeConnectorRegex = "(?\\b(y\\s*(el|(la(s)?)?))\\b|{BaseDateTime.RangeConnectorSymbolRegex})" + .replace("{BaseDateTime.RangeConnectorSymbolRegex}", BaseDateTime.RangeConnectorSymbolRegex); + + public static final String WrittenDayRegex = "(?uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|dieciséis|diecisiete|dieciocho|diecinueve|veinte|veintiuno|veintidós|veintitrés|veinticuatro|veinticinco|veintiséis|veintisiete|veintiocho|veintinueve|treinta(\\s+y\\s+uno)?)"; + + public static final String DayRegex = "\\b(?01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|1|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)(?:\\.[º°])?(?=\\b|t)"; + + public static final String MonthNumRegex = "(?01|02|03|04|05|06|07|08|09|10|11|12|1|2|3|4|5|6|7|8|9)\\b"; + + public static final String OclockRegex = "(?en\\s+punto)"; + + public static final String AmDescRegex = "({BaseDateTime.BaseAmDescRegex})" + .replace("{BaseDateTime.BaseAmDescRegex}", BaseDateTime.BaseAmDescRegex); + + public static final String PmDescRegex = "({BaseDateTime.BasePmDescRegex})" + .replace("{BaseDateTime.BasePmDescRegex}", BaseDateTime.BasePmDescRegex); + + public static final String AmPmDescRegex = "({BaseDateTime.BaseAmPmDescRegex})" + .replace("{BaseDateTime.BaseAmPmDescRegex}", BaseDateTime.BaseAmPmDescRegex); + + public static final String DescRegex = "(?({AmDescRegex}|{PmDescRegex}))" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String OfPrepositionRegex = "(\\bd(o|al?|el?)\\b)"; + + public static final String AfterNextSuffixRegex = "\\b(despu[eé]s\\s+de\\s+la\\s+pr[oó]xima)\\b"; + + public static final String NextSuffixRegex = "\\b(que\\s+viene|pr[oó]xim[oa]|siguiente)\\b"; + + public static final String PreviousSuffixRegex = "\\b(pasad[ao]|anterior(?!\\s+(al?|del?)\\b))\\b"; + + public static final String RelativeSuffixRegex = "({AfterNextSuffixRegex}|{NextSuffixRegex}|{PreviousSuffixRegex})" + .replace("{AfterNextSuffixRegex}", AfterNextSuffixRegex) + .replace("{NextSuffixRegex}", NextSuffixRegex) + .replace("{PreviousSuffixRegex}", PreviousSuffixRegex); + + public static final String RangePrefixRegex = "((de(l|sde)?|entre)(\\s+la(s)?)?)"; + + public static final String TwoDigitYearRegex = "\\b(?([0-24-9]\\d))(?!(\\s*((\\:\\d)|{AmDescRegex}|{PmDescRegex}|\\.\\d))|\\.?[º°ª])\\b" + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex); + + public static final String RelativeRegex = "(?est[ae]|pr[oó]xim[oa]|siguiente|(([uú]ltim|pasad)(o|as|os)))\\b"; + + public static final String StrictRelativeRegex = "(?est[ae]|pr[oó]xim[oa]|siguiente|(([uú]ltim|pasad)(o|as|os)))\\b"; + + public static final String WrittenOneToNineRegex = "(un[ao]?|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)"; + + public static final String WrittenOneHundredToNineHundredRegex = "(doscient[oa]s|trescient[oa]s|cuatrocient[ao]s|quinient[ao]s|seiscient[ao]s|setecient[ao]s|ochocient[ao]s|novecient[ao]s|cien(to)?)"; + + public static final String WrittenOneToNinetyNineRegex = "(((treinta|cuarenta|cincuenta|sesenta|setenta|ochenta|noventa)(\\s+y\\s+{WrittenOneToNineRegex})?)|diez|once|doce|trece|catorce|quince|dieciséis|dieciseis|diecisiete|dieciocho|diecinueve|veinte|veintiuno|veintiún|veintiun|veintiuna|veintidós|veintidos|veintitrés|veintitres|veinticuatro|veinticinco|veintiséis|veintisiete|veintiocho|veintinueve|un[ao]?|dos|tres|cuatro|cinco|seis|siete|ocho|nueve)" + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String FullTextYearRegex = "\\b(?((dos\\s+)?mil)(\\s+{WrittenOneHundredToNineHundredRegex})?(\\s+{WrittenOneToNinetyNineRegex})?)" + .replace("{WrittenOneToNinetyNineRegex}", WrittenOneToNinetyNineRegex) + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex); + + public static final String YearRegex = "({BaseDateTime.FourDigitYearRegex}|{FullTextYearRegex})" + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String RelativeMonthRegex = "(?(de\\s+)?((este|pr[oó]ximo|([uú]ltim(o|as|os)))\\s+mes)|(del\\s+)?(mes\\s+((que\\s+viene)|pasado)))\\b"; + + public static final String MonthRegex = "\\b(?abr(\\.|(il)?\\b)|ago(\\.|(sto)?\\b)|dic(\\.|(iembre)?\\b)|feb(\\.|(rero)?\\b)|ene(\\.|(ro)?\\b)|ju[ln](\\.|(io)?\\b)|mar(\\.|(zo)?\\b)|may(\\.|(o)?\\b)|nov(\\.|(iembre)?\\b)|oct(\\.|(ubre)?\\b)|sep?t(\\.|(iembre)?\\b)|sep(\\.|\\b))"; + + public static final String MonthSuffixRegex = "(?((del?|la|el)\\s+)?({RelativeMonthRegex}|{MonthRegex}))" + .replace("{RelativeMonthRegex}", RelativeMonthRegex) + .replace("{MonthRegex}", MonthRegex); + + public static final String DateUnitRegex = "(?años?|mes(es)?|semanas?|d[ií]as?(?\\s+(h[aá]biles|laborales))?)\\b"; + + public static final String PastRegex = "(?\\b(pasad(a|o)(s)?|[uú]ltim[oa](s)?|anterior(es)?|previo(s)?)\\b)"; + + public static final String FutureRegex = "\\b(siguiente(s)?|pr[oó]xim[oa](s)?|dentro\\s+de|en)\\b"; + + public static final String SimpleCasesRegex = "\\b((desde(\\s+el)?|entre|del?)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontSimpleCasesRegex = "\\b{MonthSuffixRegex}\\s+((desde(\\s+el)?|entre|del)\\s+)?({DayRegex})\\s*{TillRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{DayRegex}", DayRegex) + .replace("{TillRegex}", TillRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthFrontBetweenRegex = "\\b{MonthSuffixRegex}\\s+((entre(\\s+el)?)\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String DayBetweenRegex = "\\b((entre(\\s+el)?)\\s+)({DayRegex})\\s*{RangeConnectorRegex}\\s*({DayRegex})\\s+{MonthSuffixRegex}((\\s+|\\s*,\\s*)((en|del?)\\s+)?{YearRegex})?\\b" + .replace("{DayRegex}", DayRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{YearRegex}", YearRegex); + + public static final String SpecialYearPrefixes = "((del\\s+)?calend[aá]rio|(?fiscal|escolar))"; + + public static final String OneWordPeriodRegex = "\\b(((((la|el)\\s+)?mes\\s+(({OfPrepositionRegex})\\s+)?)|((pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?)\\s+))?({MonthRegex})|(((la|el)\\s+)?((({RelativeRegex}\\s+)({DateUnitRegex}|(fin\\s+de\\s+)?semana|finde)(\\s+{RelativeSuffixRegex})?)|{DateUnitRegex}(\\s+{RelativeSuffixRegex}))|va\\s+de\\s+{DateUnitRegex}|((año|mes)|((el\\s+)?fin\\s+de\\s+)?semana|(el\\s+)?finde))\\b)" + .replace("{MonthRegex}", MonthRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{OfPrepositionRegex}", OfPrepositionRegex) + .replace("{RelativeSuffixRegex}", RelativeSuffixRegex) + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String MonthWithYearRegex = "\\b(((pr[oó]xim[oa](s)?|est?[ae]|[uú]ltim[oa]?)\\s+)?({MonthRegex})(\\s+|(\\s*[,-]\\s*))((de(l|\\s+la)?|en)\\s+)?({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+año))\\b" + .replace("{MonthRegex}", MonthRegex) + .replace("{YearRegex}", YearRegex); + + public static final String MonthNumWithYearRegex = "\\b(({YearRegex}(\\s*?)[/\\-\\.~](\\s*?){MonthNumRegex})|({MonthNumRegex}(\\s*?)[/\\-\\.~](\\s*?){YearRegex}))\\b" + .replace("{YearRegex}", YearRegex) + .replace("{MonthNumRegex}", MonthNumRegex); + + public static final String WeekOfMonthRegex = "(?(la\\s+)?(?primera?|1ra|segunda|2da|tercera?|3ra|cuarta|4ta|quinta|5ta|([12345](\\.)?ª)|[uú]ltima)\\s+semana\\s+{MonthSuffixRegex}((\\s+de)?\\s+({BaseDateTime.FourDigitYearRegex}|{RelativeRegex}\\s+año))?)\\b" + .replace("{MonthSuffixRegex}", MonthSuffixRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String WeekOfYearRegex = "(?(la\\s+)?(?primera?|1ra|segunda|2da|tercera?|3ra|cuarta|4ta|quinta|5ta|[uú]ltima?|([12345]ª))\\s+semana(\\s+(del?|en))?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|este)\\s+año))" + .replace("{YearRegex}", YearRegex); + + public static final String FollowedDateUnit = "^\\s*{DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String NumberCombinedWithDateUnit = "\\b(?\\d+(\\.\\d*)?){DateUnitRegex}" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String QuarterTermRegex = "\\b((?primer|1er|segundo|2do|tercer|3ro|4to|([1234](\\.)?º))\\s+(trimestre|cuarto)|[tq](?[1-4]))\\b"; + + public static final String RelativeQuarterTermRegex = "\\b((?{StrictRelativeRegex})\\s+(trimestre|cuarto)|(trimestre|cuarto)\\s+(?(actual|pr[oó]ximo|siguiente|pasado|anterior)))\\b" + .replace("{StrictRelativeRegex}", StrictRelativeRegex); + + public static final String QuarterRegex = "(el\\s+)?{QuarterTermRegex}((\\s+(del?\\s+)?|\\s*[,-]\\s*)({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+a[ñn]o|a[ñn]o(\\s+{RelativeSuffixRegex}))|\\s+del\\s+a[ñn]o)?|{RelativeQuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex) + .replace("{RelativeRegex}", RelativeRegex) + .replace("{RelativeSuffixRegex}", RelativeSuffixRegex) + .replace("{RelativeQuarterTermRegex}", RelativeQuarterTermRegex); + + public static final String QuarterRegexYearFront = "({YearRegex}|(?pr[oó]ximo(s)?|[uú]ltimo?|este)\\s+a[ñn]o)(?:\\s*-\\s*|\\s+(el\\s+)?)?{QuarterTermRegex}" + .replace("{YearRegex}", YearRegex) + .replace("{QuarterTermRegex}", QuarterTermRegex); + + public static final String AllHalfYearRegex = "\\b(?primer|1er|segundo|2do|[12](\\.)?º)\\s+semestre(\\s+(de\\s+)?({YearRegex}|{RelativeRegex}\\s+año))?\\b" + .replace("{YearRegex}", YearRegex) + .replace("{RelativeRegex}", RelativeRegex); + + public static final String EarlyPrefixRegex = "\\b(?(?m[aá]s\\s+temprano(\\s+(del?|en))?)|((comienzos?|inicios?|principios?|temprano)\\s+({OfPrepositionRegex}(\\s+d[ií]a)?)))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String MidPrefixRegex = "\\b(?(media[dn]os\\s+({OfPrepositionRegex})))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String LaterPrefixRegex = "\\b(?((fin(al)?(es)?|[uú]ltimos)\\s+({OfPrepositionRegex}))|(?m[aá]s\\s+tarde(\\s+(del?|en))?))(\\s+(el|las?|los?))?\\b" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String PrefixPeriodRegex = "({EarlyPrefixRegex}|{MidPrefixRegex}|{LaterPrefixRegex})" + .replace("{EarlyPrefixRegex}", EarlyPrefixRegex) + .replace("{MidPrefixRegex}", MidPrefixRegex) + .replace("{LaterPrefixRegex}", LaterPrefixRegex); + + public static final String PrefixDayRegex = "\\b((?(comienzos?|inicios?|principios?|temprano))|(?mediados)|(?(fin((al)?es)?|m[aá]s\\s+tarde)))(\\s+(en|{OfPrepositionRegex}))?(\\s+([ae]l)(\\s+d[ií]a)?)?$" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String CenturySuffixRegex = "(^siglo)\\b"; + + public static final String SeasonRegex = "\\b(?(([uú]ltim[oa]|est[ea]|el|la|(pr[oó]xim[oa]s?|siguiente)|{PrefixPeriodRegex})\\s+)?(?primavera|verano|otoño|invierno)((\\s+(del?|en)|\\s*,\\s*)?\\s+({YearRegex}|(?pr[oó]ximo|[uú]ltimo|este)\\s+año))?)\\b" + .replace("{YearRegex}", YearRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex); + + public static final String WhichWeekRegex = "\\b(semana)(\\s*)(?5[0-3]|[1-4]\\d|0?[1-9])\\b"; + + public static final String WeekOfRegex = "((del?|el|la)\\s+)?(semana)(\\s*)({OfPrepositionRegex}|que\\s+(inicia|comienza)\\s+el|(que\\s+va|a\\s+partir)\\s+del)" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String MonthOfRegex = "(mes)(\\s+)({OfPrepositionRegex})" + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String RangeUnitRegex = "\\b(?años?|mes(es)?|semanas?)\\b"; + + public static final String BeforeAfterRegex = "^[.]"; + + public static final String InConnectorRegex = "\\b(en)\\b"; + + public static final String SinceYearSuffixRegex = "^[.]"; + + public static final String WithinNextPrefixRegex = "\\b(dentro\\s+de)\\b"; + + public static final String TodayNowRegex = "\\b(hoy|ahora|este entonces)\\b"; + + public static final String FromRegex = "((\\bde(sde)?)(\\s*la(s)?)?)$"; + + public static final String BetweenRegex = "(\\bentre\\s*(la(s)?)?)"; + + public static final String WeekDayRegex = "\\b(?(domingos?|lunes|martes|mi[eé]rcoles|jueves|viernes|s[aá]bados?)\\b|(lun|mar|mi[eé]|jue|vie|s[aá]b|dom|lu|ma|mi|ju|vi|s[aá]|do)(\\.|\\b))(?!ñ)"; + + public static final String OnRegex = "((?<=\\b(e[ln])\\s+)|(\\be[ln]\\s+d[ií]a\\s+))({DayRegex}s?)(?![.,]\\d)\\b" + .replace("{DayRegex}", DayRegex); + + public static final String RelaxedOnRegex = "(?<=\\b(en|d?el)\\s+)((?10|11|12|13|14|15|16|17|18|19|1st|20|21|22|23|24|25|26|27|28|29|2|30|31|3|4|5|6|7|8|9)s?)(?![.,]\\d)\\b"; + + public static final String SpecialDayRegex = "\\b((el\\s+)?(d[ií]a\\s+antes\\s+de\\s+ayer|anteayer)|((el\\s+)?d[ií]a\\s+(despu[eé]s\\s+)?de\\s+mañana|pasado\\s+mañana)|(el\\s)?d[ií]a\\s+(siguiente|anterior)|(el\\s)?pr[oó]ximo\\s+d[ií]a|(el\\s+)?[uú]ltimo\\s+d[ií]a|(d)?el\\s+d[ií]a(?!\\s+d)|ayer|mañana|hoy)\\b"; + + public static final String SpecialDayWithNumRegex = "^[.]"; + + public static final String FlexibleDayRegex = "(?([A-Za-z]+\\s)?({WrittenDayRegex}|{DayRegex}))" + .replace("{WrittenDayRegex}", WrittenDayRegex) + .replace("{DayRegex}", DayRegex); + + public static final String ForTheRegex = "\\b((((?<=para\\s+el\\s+){FlexibleDayRegex})|((?\\s*(,|\\.(?![º°ª])|!|\\?|-|$))(?!\\d))" + .replace("{FlexibleDayRegex}", FlexibleDayRegex) + .replace("{MonthRegex}", MonthRegex) + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayAndDayOfMonthRegex = "\\b{WeekDayRegex}\\s+((el\\s+(d[ií]a\\s+)?){FlexibleDayRegex})\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{FlexibleDayRegex}", FlexibleDayRegex); + + public static final String WeekDayAndDayRegex = "\\b{WeekDayRegex}\\s+({DayRegex}|{WrittenDayRegex})(?!([-:/]|\\.\\d|(\\s+({AmDescRegex}|{PmDescRegex}|{OclockRegex}))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{DayRegex}", DayRegex) + .replace("{WrittenDayRegex}", WrittenDayRegex) + .replace("{AmDescRegex}", AmDescRegex) + .replace("{PmDescRegex}", PmDescRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String WeekDayOfMonthRegex = "(?(el\\s+)?(?primera?|1era?|segund[ao]|2d[ao]|tercera?|3era?|cuart[ao]|4t[ao]|quint[ao]|5t[ao]|((1|2|3|4|5)(\\.)?[ºª])|[uú]ltim[ao])\\s+(semana\\s+{MonthSuffixRegex}\\s+el\\s+{WeekDayRegex}|{WeekDayRegex}\\s+{MonthSuffixRegex}))" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String RelativeWeekDayRegex = "^[.]"; + + public static final String AmbiguousRangeModifierPrefix = "^[.]"; + + public static final String NumberEndingPattern = "^[.]"; + + public static final String DateTokenPrefix = "en "; + + public static final String TimeTokenPrefix = "a las "; + + public static final String TokenBeforeDate = "el "; + + public static final String TokenBeforeTime = "a las "; + + public static final String HalfTokenRegex = "^((y\\s+)?media)"; + + public static final String QuarterTokenRegex = "^((y\\s+)?cuarto|(?menos\\s+cuarto))"; + + public static final String PastTokenRegex = "\\b(pasad[ao]s(\\s+(de\\s+)?las)?)$"; + + public static final String ToTokenRegex = "\\b((para|antes)(\\s+(de\\s+)?las?)|(?^menos))$"; + + public static final String SpecialDateRegex = "(?<=\\b(en)\\s+el\\s+){DayRegex}\\b" + .replace("{DayRegex}", DayRegex); + + public static final String OfMonthRegex = "^\\s*((d[ií]a\\s+)?d[eo]\\s+)?{MonthSuffixRegex}" + .replace("{MonthSuffixRegex}", MonthSuffixRegex); + + public static final String MonthEndRegex = "({MonthRegex}\\s*(el)?\\s*$)" + .replace("{MonthRegex}", MonthRegex); + + public static final String WeekDayEnd = "{WeekDayRegex}\\s*,?\\s*$" + .replace("{WeekDayRegex}", WeekDayRegex); + + public static final String WeekDayStart = "^[\\.]"; + + public static final String DateYearRegex = "(?{YearRegex}|(?cero|una|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce)\\b"; + + public static final String MinuteNumRegex = "(?uno?|d[óo]s|tr[eé]s|cuatro|cinco|s[eé]is|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis[eé]is|diecisiete|dieciocho|diecinueve|veinte|treinta|cuarenta|cincuenta)"; + + public static final String DeltaMinuteNumRegex = "(?uno?|d[óo]s|tr[eé]s|cuatro|cinco|s[eé]is|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis[eé]is|diecisiete|dieciocho|diecinueve|veinte|treinta|cuarenta|cincuenta)"; + + public static final String PmRegex = "(?((por|de|a|en)\\s+la)\\s+(tarde|noche))"; + + public static final String AmRegex = "(?((por|de|a|en)\\s+la)\\s+(mañana|madrugada))"; + + public static final String AmTimeRegex = "(?(esta|(por|de|a|en)\\s+la)\\s+(mañana|madrugada))"; + + public static final String PmTimeRegex = "(?(esta|(por|de|a|en)\\s+la)\\s+(tarde|noche))"; + + public static final String LessThanOneHour = "(?((\\s+y\\s+)?cuarto|(\\s*)menos cuarto|(\\s+y\\s+)media|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutos?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutos?|mins?))))" + .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) + .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); + + public static final String TensTimeRegex = "(?diez|veint(i|e)|treinta|cuarenta|cincuenta)"; + + public static final String WrittenTimeRegex = "(?{HourNumRegex}\\s*((y|(?menos))\\s+)?(({TensTimeRegex}(\\s*y\\s+)?)?{MinuteNumRegex}))" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex); + + public static final String TimePrefix = "(?{LessThanOneHour}(\\s+(pasad[ao]s)\\s+(de\\s+las|las)?|\\s+(para|antes\\s+de)?\\s+(las?))?)" + .replace("{LessThanOneHour}", LessThanOneHour); + + public static final String TimeSuffix = "(?({LessThanOneHour}\\s+)?({AmRegex}|{PmRegex}|{OclockRegex}))" + .replace("{LessThanOneHour}", LessThanOneHour) + .replace("{AmRegex}", AmRegex) + .replace("{PmRegex}", PmRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String BasicTime = "(?{WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}:{BaseDateTime.MinuteRegex}(:{BaseDateTime.SecondRegex})?|{BaseDateTime.HourRegex})" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex); + + public static final String MidTimeRegex = "(?((?media\\s*noche)|(?media\\s*mañana)|(?media\\s*tarde)|(?medio\\s*d[ií]a)))"; + + public static final String AtRegex = "\\b((?<=\\b((a|de(sde)?)\\s+las?|al)\\s+)(({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\b(\\s*\\bh\\b)?(DescRegex)?|{MidTimeRegex})|{MidTimeRegex})" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String ConnectNumRegex = "({BaseDateTime.HourRegex}(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59)\\s*{DescRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegexWithDotConnector = "({BaseDateTime.HourRegex}\\.{BaseDateTime.MinuteRegex})" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex); + + public static final String TimeRegex1 = "(\\b{TimePrefix}\\s+)?({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex})\\s*({DescRegex}|\\s*\\bh\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex2 = "(\\b{TimePrefix}\\s+)?(t)?{BaseDateTime.HourRegex}(\\s*)?:(\\s*)?{BaseDateTime.MinuteRegex}((\\s*)?:(\\s*)?{BaseDateTime.SecondRegex})?((\\s*{DescRegex})|\\b)" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{BaseDateTime.SecondRegex}", BaseDateTime.SecondRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex3 = "\\b(({TimePrefix}\\s+)?{TimeRegexWithDotConnector}(\\s*({DescRegex}|{TimeSuffix}|\\bh\\b))|((las\\s+{TimeRegexWithDotConnector})(?!\\s*(por\\s+cien(to)?|%))(\\s*({DescRegex}|{TimeSuffix}|\\bh\\b)|\\b)))" + .replace("{TimePrefix}", TimePrefix) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{DescRegex}", DescRegex) + .replace("{TimeTokenPrefix}", TimeTokenPrefix) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex4 = "\\b(({DescRegex}?)|({BasicTime}?)({DescRegex}?)){TimePrefix}(\\s*({HourNumRegex}|{BaseDateTime.HourRegex}))?(\\s+{TensTimeRegex}(\\s*(y\\s+)?{MinuteNumRegex})?)?(\\s*({OclockRegex}|{DescRegex})|\\b)" + .replace("{DescRegex}", DescRegex) + .replace("{BasicTime}", BasicTime) + .replace("{TimePrefix}", TimePrefix) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex) + .replace("{OclockRegex}", OclockRegex); + + public static final String TimeRegex5 = "\\b({TimePrefix}|{BasicTime}{TimePrefix})\\s+(\\s*{DescRegex})?{BasicTime}?\\s*{TimeSuffix}\\b" + .replace("{TimePrefix}", TimePrefix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex6 = "({BasicTime}(\\s*{DescRegex})?\\s+{TimeSuffix}\\b)" + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex) + .replace("{TimeSuffix}", TimeSuffix); + + public static final String TimeRegex7 = "\\b{TimeSuffix}\\s+a\\s+las\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex8 = "\\b{TimeSuffix}\\s+{BasicTime}((\\s*{DescRegex})|\\b)" + .replace("{TimeSuffix}", TimeSuffix) + .replace("{BasicTime}", BasicTime) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex9 = "\\b(?{HourNumRegex}\\s+({TensTimeRegex}\\s*)(y\\s+)?{MinuteNumRegex}?)\\b" + .replace("{HourNumRegex}", HourNumRegex) + .replace("{TensTimeRegex}", TensTimeRegex) + .replace("{MinuteNumRegex}", MinuteNumRegex); + + public static final String TimeRegex10 = "(a\\s+la|al)\\s+(madrugada|mañana|tarde|noche)"; + + public static final String TimeRegex11 = "\\b({WrittenTimeRegex})(\\s+{DescRegex})?\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeRegex12 = "(\\b{TimePrefix}\\s+)?{BaseDateTime.HourRegex}(\\s*h\\s*){BaseDateTime.MinuteRegex}(\\s*{DescRegex})?" + .replace("{TimePrefix}", TimePrefix) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{BaseDateTime.MinuteRegex}", BaseDateTime.MinuteRegex) + .replace("{DescRegex}", DescRegex); + + public static final String PrepositionRegex = "(?^(,\\s*)?(a(l)?|en|de(l)?)?(\\s*(la(s)?|el|los))?$)"; + + public static final String LaterEarlyRegex = "((?temprano)|(?fin(al)?(\\s+de)?|m[aá]s\\s+tarde))"; + + public static final String NowRegex = "\\b(?(justo\\s+)?ahora(\\s+mismo)?|en\\s+este\\s+momento|tan\\s+pronto\\s+como\\s+sea\\s+posible|tan\\s+pronto\\s+como\\s+(pueda|puedas|podamos|puedan)|lo\\s+m[aá]s\\s+pronto\\s+posible|recientemente|previamente|este entonces)\\b"; + + public static final String SuffixRegex = "^\\s*(((y|a|en|por)\\s+la|al)\\s+)?(mañana|madrugada|medio\\s*d[ií]a|(?(({LaterEarlyRegex}\\s+)((del?|en|por)(\\s+(el|los?|las?))?\\s+)?)?(mañana|madrugada|pasado\\s+(el\\s+)?medio\\s?d[ií]a|(?mañana|madrugada|(?pasado\\s+(el\\s+)?medio\\s?d[ií]a|tarde|noche))\\b"; + + public static final String PeriodTimeOfDayRegex = "\\b((en\\s+(el|la|lo)?\\s+)?({LaterEarlyRegex}\\s+)?(est[ae]\\s+)?{DateTimeTimeOfDayRegex})\\b" + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{LaterEarlyRegex}", LaterEarlyRegex); + + public static final String PeriodSpecificTimeOfDayRegex = "\\b(({LaterEarlyRegex}\\s+)?est[ae]\\s+{DateTimeTimeOfDayRegex}|({StrictRelativeRegex}\\s+{PeriodTimeOfDayRegex})|anoche)\\b" + .replace("{PeriodTimeOfDayRegex}", PeriodTimeOfDayRegex) + .replace("{StrictRelativeRegex}", StrictRelativeRegex) + .replace("{DateTimeTimeOfDayRegex}", DateTimeTimeOfDayRegex) + .replace("{LaterEarlyRegex}", LaterEarlyRegex); + + public static final String UnitRegex = "(?años?|(bi|tri|cuatri|se)mestre|mes(es)?|semanas?|fin(es)?\\s+de\\s+semana|finde|d[ií]as?|horas?|hra?s?|hs?|minutos?|mins?|segundos?|segs?|noches?)\\b"; + + public static final String ConnectorRegex = "^(,|t|(para|y|a|en|por) las?|(\\s*,\\s*)?(cerca|alrededor) de las?)$"; + + public static final String TimeHourNumRegex = "(?veintiuno|veintidos|veintitres|veinticuatro|cero|uno|dos|tres|cuatro|cinco|seis|siete|ocho|nueve|diez|once|doce|trece|catorce|quince|diecis([eé])is|diecisiete|dieciocho|diecinueve|veinte)"; + + public static final String PureNumFromTo = "((\\b(desde|de)\\s+(la(s)?\\s+)?)?({BaseDateTime.HourRegex}|{TimeHourNumRegex})(?!\\s+al?\\b)(\\s*(?{DescRegex}))?|(\\b(desde|de)\\s+(la(s)?\\s+)?)({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?)\\s*{TillRegex}\\s*({BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{TillRegex}", TillRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex); + + public static final String PureNumBetweenAnd = "(\\bentre\\s+(la(s)?\\s+)?)(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?\\s*{RangeConnectorRegex}\\s*(({BaseDateTime.TwoDigitHourRegex}{BaseDateTime.TwoDigitMinuteRegex})|{BaseDateTime.HourRegex}|{TimeHourNumRegex})\\s*(?{PmRegex}|{AmRegex}|{DescRegex})?" + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{PmRegex}", PmRegex) + .replace("{AmRegex}", AmRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{BaseDateTime.TwoDigitHourRegex}", BaseDateTime.TwoDigitHourRegex) + .replace("{BaseDateTime.TwoDigitMinuteRegex}", BaseDateTime.TwoDigitMinuteRegex); + + public static final String SpecificTimeFromTo = "({RangePrefixRegex}\\s+)?(?(({TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{TillRegex}\\s*(?(({TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{TimeRegex2}", TimeRegex2) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{TillRegex}", TillRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex) + .replace("{RangePrefixRegex}", RangePrefixRegex); + + public static final String SpecificTimeBetweenAnd = "({BetweenRegex}\\s+)(?(({TimeRegex1}|{TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))\\s*{RangeConnectorRegex}\\s*(?(({TimeRegex1}|{TimeRegex2}|{TimeRegexWithDotConnector}(\\s*{DescRegex})?)|({BaseDateTime.HourRegex}|{TimeHourNumRegex})(\\s*(?{DescRegex}))?))" + .replace("{BetweenRegex}", BetweenRegex) + .replace("{TimeRegex1}", TimeRegex1) + .replace("{TimeRegex2}", TimeRegex2) + .replace("{TimeRegexWithDotConnector}", TimeRegexWithDotConnector) + .replace("{RangeConnectorRegex}", RangeConnectorRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{TimeHourNumRegex}", TimeHourNumRegex) + .replace("{DescRegex}", DescRegex); + + public static final String TimeUnitRegex = "([^A-Za-z]{1,}|\\b)(?horas?|h|minutos?|mins?|segundos?|se[cg]s?)\\b"; + + public static final String TimeFollowedUnit = "^\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String TimeNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String DateTimePeriodNumberCombinedWithUnit = "\\b(?\\d+(\\.\\d*)?)\\s*{TimeUnitRegex}" + .replace("{TimeUnitRegex}", TimeUnitRegex); + + public static final String PeriodTimeOfDayWithDateRegex = "\\b(((y|a|en|por)\\s+(la\\s+)?|al\\s+)?((((?primeras\\s+horas\\s+)|(?(últimas|altas)\\s+horas\\s+))(de\\s+la\\s+)?|{LaterEarlyRegex}\\s+(est[ae]\\s+)?)?(?(mañana|madrugada|pasado\\s+(el\\s+)?medio\\s?d[ií]a|(?\\s*(y)\\s+((un[ao]?)\\s+)?(?media|cuarto))"; + + public static final String FollowedUnit = "^\\s*{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DurationNumberCombinedWithUnit = "\\b(?\\d+(\\,\\d*)?){UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String AnUnitRegex = "\\b(una?|otr[ao])\\s+{UnitRegex}" + .replace("{UnitRegex}", UnitRegex); + + public static final String DuringRegex = "^[.]"; + + public static final String AllRegex = "\\b(?tod[oa]?\\s+(el|la)\\s+(?año|mes|semana|d[ií]a)|((una?|el|la)\\s+)?(?año|mes|semana|d[ií]a)\\s+enter[ao])\\b"; + + public static final String HalfRegex = "\\b(?medi[oa]\\s+(?ano|mes|semana|d[íi]a|hora))\\b"; + + public static final String ConjunctionRegex = "^[.]"; + + public static final String InexactNumberRegex = "\\b(pocos?|algo|vari[ao]s|algun[ao]s|un[ao]s)\\b"; + + public static final String InexactNumberUnitRegex = "({InexactNumberRegex})\\s+{UnitRegex}" + .replace("{InexactNumberRegex}", InexactNumberRegex) + .replace("{UnitRegex}", UnitRegex); + + public static final String HolidayRegex1 = "\\b(?viernes santo|mi[eé]rcoles de ceniza|martes de carnaval|d[ií]a (de|de los) presidentes?|clebraci[oó]n de mao|año nuevo chino|año nuevo|noche vieja|(festividad de )?los mayos|d[ií]a de los inocentes|navidad|noche buena|d[ií]a de acci[oó]n de gracias|acci[oó]n de gracias|yuandan|halloween|noches de brujas|pascuas)(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex2 = "\\b(?(d[ií]a( del?( la)?)? )?(martin luther king|todos los santos|blanco|san patricio|san valent[ií]n|san jorge|cinco de mayo|independencia|raza|trabajador))(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String HolidayRegex3 = "\\b(?(d[ií]a( internacional)?( del?( l[ao]s?)?)? )(trabajador(es)?|madres?|padres?|[aá]rbol|mujer(es)?|solteros?|niños?|marmota|san valent[ií]n|maestro))(\\s+(del?\\s+)?({YearRegex}|(?(pr[oó]xim[oa]?|est[ea]|[uú]ltim[oa]?|en))\\s+año))?\\b" + .replace("{YearRegex}", YearRegex); + + public static final String BeforeRegex = "(\\b((ante(s|rior)|m[aá]s\\s+temprano|no\\s+m[aá]s\\s+tard(e|ar)|(?tan\\s+tarde\\s+como))(\\s+(del?|a|que)(\\s+(el|las?|los?))?)?)|(?)((?<\\s*=)|<))"; + + public static final String AfterRegex = "((\\b(despu[eé]s|(año\\s+)?posterior|m[aá]s\\s+tarde|a\\s+primeros)(\\s*(del?|en|a)(\\s+(el|las?|los?))?)?|(empi?en?zando|comenzando)(\\s+(el|las?|los?))?)\\b|(?>\\s*=)|>))"; + + public static final String SinceRegex = "\\b(((cualquier\\s+tiempo\\s+)?(desde|a\\s+partir\\s+del?)|tan\\s+(temprano|pronto)\\s+como(\\s+(de|a))?)(\\s+(el|las?|los?))?)\\b"; + + public static final String SinceRegexExp = "({SinceRegex}|\\bde\\b)" + .replace("{SinceRegex}", SinceRegex); + + public static final String AroundRegex = "(?:\\b(?:cerca|alrededor|aproximadamente)(\\s+(de\\s+(las?|el)|del?))?\\s*\\b)"; + + public static final String PeriodicRegex = "\\b(?a\\s*diario|diaria(s|mente)|(bi|tri)?(semanal|quincenal|mensual|semestral|anual)(es|mente)?)\\b"; + + public static final String EachExpression = "\\b(cada|tod[oa]s\\s*(l[oa]s)?)\\b\\s*(?!\\s*l[oa]\\b)"; + + public static final String EachUnitRegex = "(?({EachExpression})\\s*({UnitRegex}|(?fin(es)?\\s+de\\s+semana|finde)\\b))" + .replace("{EachExpression}", EachExpression) + .replace("{UnitRegex}", UnitRegex); + + public static final String EachPrefixRegex = "(?({EachExpression})\\s*$)" + .replace("{EachExpression}", EachExpression); + + public static final String EachDayRegex = "\\s*({EachExpression})\\s*d[ií]as\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String BeforeEachDayRegex = "({EachExpression})\\s*d[ií]as(\\s+a\\s+las?)?\\s*\\b" + .replace("{EachExpression}", EachExpression); + + public static final String SetEachRegex = "(?({EachExpression})\\s*)" + .replace("{EachExpression}", EachExpression); + + public static final String LaterEarlyPeriodRegex = "\\b(({PrefixPeriodRegex})\\s+(?{OneWordPeriodRegex}|(?{BaseDateTime.FourDigitYearRegex}))|({UnspecificEndOfRangeRegex}))\\b" + .replace("{OneWordPeriodRegex}", OneWordPeriodRegex) + .replace("{UnspecificEndOfRangeRegex}", UnspecificEndOfRangeRegex) + .replace("{PrefixPeriodRegex}", PrefixPeriodRegex) + .replace("{BaseDateTime.FourDigitYearRegex}", BaseDateTime.FourDigitYearRegex); + + public static final String RelativeWeekRegex = "(((la|el)\\s+)?(((est[ae]|pr[oó]xim[oa]|[uú]ltim(o|as|os))\\s+semanas?)|(semanas?\\s+(que\\s+viene|pasad[oa]))))"; + + public static final String WeekWithWeekDayRangeRegex = "\\b((({RelativeWeekRegex})((\\s+entre\\s+{WeekDayRegex}\\s+y\\s+{WeekDayRegex})|(\\s+de\\s+{WeekDayRegex}\\s+a\\s+{WeekDayRegex})))|((entre\\s+{WeekDayRegex}\\s+y\\s+{WeekDayRegex})|(de\\s+{WeekDayRegex}\\s+a\\s+{WeekDayRegex})){OfPrepositionRegex}\\s+{RelativeWeekRegex})\\b" + .replace("{RelativeWeekRegex}", RelativeWeekRegex) + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{OfPrepositionRegex}", OfPrepositionRegex); + + public static final String GeneralEndingRegex = "^\\s*((\\.,)|\\.|,|!|\\?)?\\s*$"; + + public static final String MiddlePauseRegex = "^[.]"; + + public static final String PrefixArticleRegex = "\\b(e[ln]\\s+(d[ií]a\\s+)?)"; + + public static final String OrRegex = "^[.]"; + + public static final String SpecialYearTermsRegex = "\\b(años?\\s+({SpecialYearPrefixes}\\s+)?(de\\s+)?)" + .replace("{SpecialYearPrefixes}", SpecialYearPrefixes); + + public static final String YearPlusNumberRegex = "\\b({SpecialYearTermsRegex}((?(\\d{2,4}))|{FullTextYearRegex}))\\b" + .replace("{FullTextYearRegex}", FullTextYearRegex) + .replace("{SpecialYearTermsRegex}", SpecialYearTermsRegex); + + public static final String NumberAsTimeRegex = "^[.]"; + + public static final String TimeBeforeAfterRegex = "\\b((?<=\\b(antes|no\\s+m[aá]s\\s+tard(e|ar)\\s+(de|a\\s+las?)|por| después)\\s+)({WrittenTimeRegex}|{HourNumRegex}|{BaseDateTime.HourRegex}|{MidTimeRegex}))\\b" + .replace("{WrittenTimeRegex}", WrittenTimeRegex) + .replace("{HourNumRegex}", HourNumRegex) + .replace("{BaseDateTime.HourRegex}", BaseDateTime.HourRegex) + .replace("{MidTimeRegex}", MidTimeRegex); + + public static final String DateNumberConnectorRegex = "^[.]"; + + public static final String CenturyRegex = "^[.]"; + + public static final String DecadeRegex = "(?diez|veinte|treinta|cuarenta|cincuenta|se[st]enta|ochenta|noventa)"; + + public static final String DecadeWithCenturyRegex = "(los\\s+)?((((d[ée]cada(\\s+de)?)\\s+)(((?\\d|1\\d|2\\d)?(?\\d0))))|a[ñn]os\\s+((((dos\\s+)?mil\\s+)?({WrittenOneHundredToNineHundredRegex}\\s+)?{DecadeRegex})|((dos\\s+)?mil\\s+)?({WrittenOneHundredToNineHundredRegex})(\\s+{DecadeRegex}?)|((dos\\s+)?mil)(\\s+{WrittenOneHundredToNineHundredRegex}\\s+)?{DecadeRegex}?))" + .replace("{WrittenOneHundredToNineHundredRegex}", WrittenOneHundredToNineHundredRegex) + .replace("{DecadeRegex}", DecadeRegex); + + public static final String RelativeDecadeRegex = "\\b(((el|las?)\\s+)?{RelativeRegex}\\s+(((?[\\d]+)|{WrittenOneToNineRegex})\\s+)?d[eé]cadas?)\\b" + .replace("{RelativeRegex}", RelativeRegex) + .replace("{WrittenOneToNineRegex}", WrittenOneToNineRegex); + + public static final String ComplexDatePeriodRegex = "(?:((de(sde)?)\\s+)?(?.+)\\s*({StrictTillRegex})\\s*(?.+)|((entre)\\s+)(?.+)\\s*({RangeConnectorRegex})\\s*(?.+))" + .replace("{StrictTillRegex}", StrictTillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String AmbiguousPointRangeRegex = "^(mar\\.?)$"; + + public static final String YearSuffix = "((,|\\sde)?\\s*({YearRegex}|{FullTextYearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{FullTextYearRegex}", FullTextYearRegex); + + public static final String AgoRegex = "\\b(antes\\s+de\\s+(?hoy|ayer|mañana)|antes)\\b"; + + public static final String LaterRegex = "\\b(despu[eé]s(?!\\s+de\\b)|desde\\s+ahora|a\\s+partir\\s+de\\s+(?hoy|ayer|mañana))\\b"; + + public static final String Tomorrow = "mañana"; + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("años", "Y") + .put("año", "Y") + .put("meses", "MON") + .put("mes", "MON") + .put("trimestre", "3MON") + .put("trimestres", "3MON") + .put("cuatrimestre", "4MON") + .put("cuatrimestres", "4MON") + .put("semestre", "6MON") + .put("semestres", "6MON") + .put("bimestre", "2MON") + .put("bimestres", "2MON") + .put("semanas", "W") + .put("semana", "W") + .put("fin de semana", "WE") + .put("fines de semana", "WE") + .put("finde", "WE") + .put("dias", "D") + .put("dia", "D") + .put("días", "D") + .put("día", "D") + .put("jornada", "D") + .put("noche", "D") + .put("noches", "D") + .put("horas", "H") + .put("hora", "H") + .put("hrs", "H") + .put("hras", "H") + .put("hra", "H") + .put("hr", "H") + .put("h", "H") + .put("minutos", "M") + .put("minuto", "M") + .put("mins", "M") + .put("min", "M") + .put("segundos", "S") + .put("segundo", "S") + .put("segs", "S") + .put("seg", "S") + .build(); + + public static final ImmutableMap UnitValueMap = ImmutableMap.builder() + .put("años", 31536000L) + .put("año", 31536000L) + .put("meses", 2592000L) + .put("mes", 2592000L) + .put("semanas", 604800L) + .put("semana", 604800L) + .put("fin de semana", 172800L) + .put("fines de semana", 172800L) + .put("finde", 172800L) + .put("dias", 86400L) + .put("dia", 86400L) + .put("días", 86400L) + .put("día", 86400L) + .put("noche", 86400L) + .put("noches", 86400L) + .put("horas", 3600L) + .put("hora", 3600L) + .put("hrs", 3600L) + .put("hras", 3600L) + .put("hra", 3600L) + .put("hr", 3600L) + .put("h", 3600L) + .put("minutos", 60L) + .put("minuto", 60L) + .put("mins", 60L) + .put("min", 60L) + .put("segundos", 1L) + .put("segundo", 1L) + .put("segs", 1L) + .put("seg", 1L) + .build(); + + public static final ImmutableMap SpecialYearPrefixesMap = ImmutableMap.builder() + .put("fiscal", "FY") + .put("escolar", "SY") + .build(); + + public static final ImmutableMap SeasonMap = ImmutableMap.builder() + .put("primavera", "SP") + .put("verano", "SU") + .put("otoño", "FA") + .put("invierno", "WI") + .build(); + + public static final ImmutableMap SeasonValueMap = ImmutableMap.builder() + .put("SP", 3) + .put("SU", 6) + .put("FA", 9) + .put("WI", 12) + .build(); + + public static final ImmutableMap CardinalMap = ImmutableMap.builder() + .put("primer", 1) + .put("primero", 1) + .put("primera", 1) + .put("1er", 1) + .put("1ro", 1) + .put("1ra", 1) + .put("1.º", 1) + .put("1º", 1) + .put("1ª", 1) + .put("segundo", 2) + .put("segunda", 2) + .put("2do", 2) + .put("2da", 2) + .put("2.º", 2) + .put("2º", 2) + .put("2ª", 2) + .put("tercer", 3) + .put("tercero", 3) + .put("tercera", 3) + .put("3er", 3) + .put("3ro", 3) + .put("3ra", 3) + .put("3.º", 3) + .put("3º", 3) + .put("3ª", 3) + .put("cuarto", 4) + .put("cuarta", 4) + .put("4to", 4) + .put("4ta", 4) + .put("4.º", 4) + .put("4º", 4) + .put("4ª", 4) + .put("quinto", 5) + .put("quinta", 5) + .put("5to", 5) + .put("5ta", 5) + .put("5.º", 5) + .put("5º", 5) + .put("5ª", 5) + .build(); + + public static final ImmutableMap DayOfWeek = ImmutableMap.builder() + .put("lunes", 1) + .put("martes", 2) + .put("miercoles", 3) + .put("miércoles", 3) + .put("jueves", 4) + .put("viernes", 5) + .put("sabado", 6) + .put("sábado", 6) + .put("domingo", 0) + .put("dom", 0) + .put("lun", 1) + .put("mar", 2) + .put("mie", 3) + .put("mié", 3) + .put("jue", 4) + .put("vie", 5) + .put("sab", 6) + .put("sáb", 6) + .put("dom.", 0) + .put("lun.", 1) + .put("mar.", 2) + .put("mie.", 3) + .put("mié.", 3) + .put("jue.", 4) + .put("vie.", 5) + .put("sab.", 6) + .put("sáb.", 6) + .put("do", 0) + .put("lu", 1) + .put("ma", 2) + .put("mi", 3) + .put("ju", 4) + .put("vi", 5) + .put("sa", 6) + .build(); + + public static final ImmutableMap MonthOfYear = ImmutableMap.builder() + .put("enero", 1) + .put("febrero", 2) + .put("marzo", 3) + .put("abril", 4) + .put("mayo", 5) + .put("junio", 6) + .put("julio", 7) + .put("agosto", 8) + .put("septiembre", 9) + .put("setiembre", 9) + .put("octubre", 10) + .put("noviembre", 11) + .put("diciembre", 12) + .put("ene", 1) + .put("feb", 2) + .put("mar", 3) + .put("abr", 4) + .put("may", 5) + .put("jun", 6) + .put("jul", 7) + .put("ago", 8) + .put("sept", 9) + .put("sep", 9) + .put("set", 9) + .put("oct", 10) + .put("nov", 11) + .put("dic", 12) + .put("ene.", 1) + .put("feb.", 2) + .put("mar.", 3) + .put("abr.", 4) + .put("may.", 5) + .put("jun.", 6) + .put("jul.", 7) + .put("ago.", 8) + .put("sept.", 9) + .put("sep.", 9) + .put("set.", 9) + .put("oct.", 10) + .put("nov.", 11) + .put("dic.", 12) + .put("1", 1) + .put("2", 2) + .put("3", 3) + .put("4", 4) + .put("5", 5) + .put("6", 6) + .put("7", 7) + .put("8", 8) + .put("9", 9) + .put("10", 10) + .put("11", 11) + .put("12", 12) + .put("01", 1) + .put("02", 2) + .put("03", 3) + .put("04", 4) + .put("05", 5) + .put("06", 6) + .put("07", 7) + .put("08", 8) + .put("09", 9) + .build(); + + public static final ImmutableMap Numbers = ImmutableMap.builder() + .put("cero", 0) + .put("un", 1) + .put("una", 1) + .put("uno", 1) + .put("dos", 2) + .put("dós", 2) + .put("tres", 3) + .put("trés", 3) + .put("cuatro", 4) + .put("cinco", 5) + .put("seis", 6) + .put("séis", 6) + .put("siete", 7) + .put("ocho", 8) + .put("nueve", 9) + .put("diez", 10) + .put("once", 11) + .put("doce", 12) + .put("docena", 12) + .put("docenas", 12) + .put("trece", 13) + .put("catorce", 14) + .put("quince", 15) + .put("dieciseis", 16) + .put("dieciséis", 16) + .put("diecisiete", 17) + .put("dieciocho", 18) + .put("diecinueve", 19) + .put("veinte", 20) + .put("veinti", 20) + .put("ventiuna", 21) + .put("ventiuno", 21) + .put("veintiun", 21) + .put("veintiún", 21) + .put("veintiuno", 21) + .put("veintiuna", 21) + .put("veintidos", 22) + .put("veintidós", 22) + .put("veintitres", 23) + .put("veintitrés", 23) + .put("veinticuatro", 24) + .put("veinticinco", 25) + .put("veintiseis", 26) + .put("veintiséis", 26) + .put("veintisiete", 27) + .put("veintiocho", 28) + .put("veintinueve", 29) + .put("treinta", 30) + .put("cuarenta", 40) + .put("cincuenta", 50) + .build(); + + public static final ImmutableMap HolidayNames = ImmutableMap.builder() + .put("padres", new String[]{"diadelpadre"}) + .put("madres", new String[]{"diadelamadre"}) + .put("acciondegracias", new String[]{"diadegracias", "diadeacciondegracias", "acciondegracias"}) + .put("trabajador", new String[]{"diadeltrabajador", "diainternacionaldelostrabajadores"}) + .put("delaraza", new String[]{"diadelaraza", "diadeladiversidadcultural"}) + .put("memoria", new String[]{"diadelamemoria"}) + .put("pascuas", new String[]{"diadepascuas", "pascuas"}) + .put("navidad", new String[]{"navidad", "diadenavidad"}) + .put("nochebuena", new String[]{"diadenochebuena", "nochebuena"}) + .put("añonuevo", new String[]{"añonuevo", "diadeañonuevo"}) + .put("nochevieja", new String[]{"nochevieja", "diadenochevieja"}) + .put("yuandan", new String[]{"yuandan"}) + .put("earthday", new String[]{"diadelatierra"}) + .put("maestro", new String[]{"diadelmaestro"}) + .put("todoslossantos", new String[]{"todoslossantos"}) + .put("niño", new String[]{"diadelniño"}) + .put("mujer", new String[]{"diadelamujer"}) + .put("independencia", new String[]{"diadelaindependencia", "diadeindependencia", "independencia"}) + .build(); + + public static final ImmutableMap VariableHolidaysTimexDictionary = ImmutableMap.builder() + .put("padres", "-06-WXX-7-3") + .put("madres", "-05-WXX-7-2") + .put("acciondegracias", "-11-WXX-4-4") + .put("delaraza", "-10-WXX-1-2") + .put("memoria", "-03-WXX-2-4") + .build(); + + public static final ImmutableMap DoubleNumbers = ImmutableMap.builder() + .put("mitad", 0.5D) + .put("cuarto", 0.25D) + .build(); + + public static final String UpcomingPrefixRegex = "((este\\s+))"; + + public static final String NextPrefixRegex = "\\b({UpcomingPrefixRegex}?pr[oó]xim[oa]s?|siguiente|que\\s+viene)\\b" + .replace("{UpcomingPrefixRegex}", UpcomingPrefixRegex); + + public static final String PastPrefixRegex = "((este\\s+))"; + + public static final String PreviousPrefixRegex = "\\b({PastPrefixRegex}?pasad[oa](?!(\\s+el)?\\s+medio\\s*d[ií]a)|[uú]ltim[oa]|anterior)\\b" + .replace("{PastPrefixRegex}", PastPrefixRegex); + + public static final String ThisPrefixRegex = "(est?[ea]|actual)\\b"; + + public static final String PrefixWeekDayRegex = "(\\s*((,?\\s*el)|[-—–]))"; + + public static final String ThisRegex = "\\b((est[ae]\\s*)(semana{PrefixWeekDayRegex}?)?\\s*{WeekDayRegex})|({WeekDayRegex}\\s*((de\\s+)?esta\\s+semana))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String LastDateRegex = "\\b(({PreviousPrefixRegex}\\s+(semana{PrefixWeekDayRegex}?)?|(la\\s+)?semana\\s+{PreviousPrefixRegex}{PrefixWeekDayRegex})\\s*{WeekDayRegex})|(este\\s+)?({WeekDayRegex}\\s+([uú]ltimo|pasado|anterior))|({WeekDayRegex}(\\s+((de\\s+)?((esta|la)\\s+([uú]ltima\\s+)?semana)|(de\\s+)?(la\\s+)?semana\\s+(pasada|anterior))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String NextDateRegex = "\\b((({NextPrefixRegex}\\s+)(semana{PrefixWeekDayRegex}?)?|(la\\s+)?semana\\s+{NextPrefixRegex}{PrefixWeekDayRegex})\\s*{WeekDayRegex})|(este\\s+)?({WeekDayRegex}\\s+(pr[oó]ximo|siguiente|que\\s+viene))|({WeekDayRegex}(\\s+(de\\s+)?(la\\s+)?((pr[oó]xima|siguiente)\\s+semana|semana\\s+(pr[oó]xima|siguiente))))\\b" + .replace("{WeekDayRegex}", WeekDayRegex) + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PrefixWeekDayRegex}", PrefixWeekDayRegex); + + public static final String RelativeDayRegex = "(?((este|pr[oó]ximo|([uú]ltim(o|as|os)))\\s+días)|(días\\s+((que\\s+viene)|pasado)))\\b"; + + public static final String RestOfDateRegex = "\\bresto\\s+((del|de)\\s+)?((la|el|est?[ae])\\s+)?(?semana|mes|año|decada)(\\s+actual)?\\b"; + + public static final String DurationUnitRegex = "(?{DateUnitRegex}|horas?|hra?s?|hs?|minutos?|mins?|segundos?|segs?|noches?)\\b" + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String DurationConnectorRegex = "^[.]"; + + public static final String RelativeDurationUnitRegex = "(?:(?<=({NextPrefixRegex}|{PreviousPrefixRegex}|{ThisPrefixRegex})\\s+)({DurationUnitRegex}))" + .replace("{NextPrefixRegex}", NextPrefixRegex) + .replace("{PreviousPrefixRegex}", PreviousPrefixRegex) + .replace("{ThisPrefixRegex}", ThisPrefixRegex) + .replace("{DurationUnitRegex}", DurationUnitRegex); + + public static final String ReferencePrefixRegex = "(mism[ao]|aquel|est?e)\\b"; + + public static final String ReferenceDatePeriodRegex = "\\b{ReferencePrefixRegex}\\s+({DateUnitRegex}|fin\\s+de\\s+semana)\\b" + .replace("{ReferencePrefixRegex}", ReferencePrefixRegex) + .replace("{DateUnitRegex}", DateUnitRegex); + + public static final String FromToRegex = "\\b(from).+(to)\\b.+"; + + public static final String SingleAmbiguousMonthRegex = "^(the\\s+)?(may|march)$"; + + public static final String UnspecificDatePeriodRegex = "^[\\.]"; + + public static final String PrepositionSuffixRegex = "\\b(en|el|la|cerca|desde|durante|hasta|hacia)$"; + + public static final String RestOfDateTimeRegex = "\\bresto\\s+((del?)\\s+)?((la|el|est[ae])\\s+)?(?(día|jornada))(\\s+de\\s+hoy)?\\b"; + + public static final String SetWeekDayRegex = "^[\\.]"; + + public static final String NightRegex = "\\b(medionoche|noche)\\b"; + + public static final String CommonDatePrefixRegex = "^[\\.]"; + + public static final String SuffixAfterRegex = "\\b((a\\s+)?(o|y)\\s+(arriba|despu[eé]s|posterior|mayor|m[aá]s\\s+tarde)(?!\\s+(que|de)))\\b"; + + public static final String YearPeriodRegex = "((((de(sde)?|durante|en)\\s+)?{YearRegex}\\s*({TillRegex})\\s*{YearRegex})|(((entre)\\s+){YearRegex}\\s*({RangeConnectorRegex})\\s*{YearRegex}))" + .replace("{YearRegex}", YearRegex) + .replace("{TillRegex}", TillRegex) + .replace("{RangeConnectorRegex}", RangeConnectorRegex); + + public static final String FutureSuffixRegex = "\\b(siguiente(s)?|pr[oó]xim[oa](s)?|(en\\s+el\\s+)?futuro|a\\s+partir\\s+de\\s+ahora)\\b"; + + public static final ImmutableMap WrittenDecades = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final ImmutableMap SpecialDecadeCases = ImmutableMap.builder() + .put("", 0) + .build(); + + public static final String DefaultLanguageFallback = "DMY"; + + public static final List DurationDateRestrictions = Arrays.asList("hoy"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^mi$", "\\bmi\\b") + .put("^a[nñ]o$", "(? EarlyMorningTermList = Arrays.asList("madrugada"); + + public static final List MorningTermList = Arrays.asList("mañana", "la mañana"); + + public static final List AfternoonTermList = Arrays.asList("pasado mediodia", "pasado el mediodia", "pasado mediodía", "pasado el mediodía", "pasado medio dia", "pasado el medio dia", "pasado medio día", "pasado el medio día"); + + public static final List EveningTermList = Arrays.asList("tarde"); + + public static final List NightTermList = Arrays.asList("noche"); + + public static final List SameDayTerms = Arrays.asList("hoy", "el dia"); + + public static final List PlusOneDayTerms = Arrays.asList("mañana", "dia siguiente", "el dia de mañana", "proximo dia"); + + public static final List MinusOneDayTerms = Arrays.asList("ayer", "ultimo dia", "dia anterior"); + + public static final List PlusTwoDayTerms = Arrays.asList("pasado mañana", "dia despues de mañana"); + + public static final List MinusTwoDayTerms = Arrays.asList("anteayer", "dia antes de ayer"); + + public static final List MonthTerms = Arrays.asList("mes", "meses"); + + public static final List MonthToDateTerms = Arrays.asList("mes a la fecha", "meses a la fecha"); + + public static final List WeekendTerms = Arrays.asList("finde", "fin de semana", "fines de semana"); + + public static final List WeekTerms = Arrays.asList("semana"); + + public static final List YearTerms = Arrays.asList("año", "años"); + + public static final List YearToDateTerms = Arrays.asList("año a la fecha", "años a la fecha"); + + public static final ImmutableMap SpecialCharactersEquivalent = ImmutableMap.builder() + .put('á', 'a') + .put('é', 'e') + .put('í', 'i') + .put('ó', 'o') + .put('ú', 'u') + .build(); + + public static final String DoubleMultiplierRegex = "^(bi)(-|\\s)?"; + + public static final String DayTypeRegex = "(d[ií]as?|diari(o|as|amente))$"; + + public static final String WeekTypeRegex = "(semanas?|semanalmente)$"; + + public static final String BiWeekTypeRegex = "(quincenalmente)$"; + + public static final String WeekendTypeRegex = "(fin(es)?\\s+de\\s+semana|finde)$"; + + public static final String MonthTypeRegex = "(mes(es)?|mensual(es|mente)?)$"; + + public static final String YearTypeRegex = "(años?|anualmente)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java new file mode 100644 index 000000000..e8735c5bb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateExtractorConfiguration.java @@ -0,0 +1,245 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + + +public class SpanishDateExtractorConfiguration extends BaseOptionsConfiguration implements IDateExtractorConfiguration { + + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearRegex); + public static final Pattern WeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayRegex); + public static final Pattern OnRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OnRegex); + public static final Pattern RelaxedOnRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelaxedOnRegex); + public static final Pattern ThisRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisRegex); + public static final Pattern LastDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LastDateRegex); + public static final Pattern NextDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextDateRegex); + public static final Pattern SpecialDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDayRegex); + public static final Pattern SpecialDayWithNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDayWithNumRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern WeekDayOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayOfMonthRegex); + public static final Pattern SpecialDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecialDateRegex); + public static final Pattern RelativeWeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeWeekDayRegex); + public static final Pattern ForTheRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ForTheRegex); + public static final Pattern WeekDayAndDayOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayAndDayOfMonthRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeMonthRegex); + public static final Pattern StrictRelativeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.StrictRelativeRegex); + public static final Pattern PrefixArticleRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrefixArticleRegex); + public static final Pattern RangeConnectorSymbolRegex = RegExpUtility.getSafeRegExp(BaseDateTime.RangeConnectorSymbolRegex); + public static final List ImplicitDateList = new ArrayList() { + { + add(OnRegex); + add(RelaxedOnRegex); + add(SpecialDayRegex); + add(ThisRegex); + add(LastDateRegex); + add(NextDateRegex); + add(WeekDayRegex); + add(WeekDayOfMonthRegex); + add(SpecialDateRegex); + } + }; + + public static final Pattern OfMonth = RegExpUtility.getSafeRegExp(SpanishDateTime.OfMonthRegex); + public static final Pattern MonthEnd = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthEndRegex); + public static final Pattern WeekDayEnd = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayEnd); + public static final Pattern YearSuffix = RegExpUtility.getSafeRegExp(SpanishDateTime.YearSuffix); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + public static final ImmutableMap DayOfWeek = SpanishDateTime.DayOfWeek; + public static final ImmutableMap MonthOfYear = SpanishDateTime.MonthOfYear; + + public static List DateRegexList; + + public SpanishDateExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + integerExtractor = new IntegerExtractor(); // in other languages (english) has a method named get instance + ordinalExtractor = new OrdinalExtractor(); // in other languages (english) has a method named get instance + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + + DateRegexList = new ArrayList() { + { + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor1)); + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor2)); + add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor3)); + } + }; + + boolean enableDmy = getDmyDateFormat() || SpanishDateTime.DefaultLanguageFallback == Constants.DefaultLanguageFallback_DMY; + + if (enableDmy) { + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor5)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor8)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor9)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor4)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor6)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor7)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor10)); + } else { + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor4)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor6)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor7)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor5)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor8)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor9)); + DateRegexList.add(RegExpUtility.getSafeRegExp(SpanishDateTime.DateExtractor10)); + } + } + + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + @Override + public Pattern getOfMonth() { + return OfMonth; + } + + @Override + public Pattern getMonthEnd() { + return MonthEnd; + } + + @Override + public Pattern getWeekDayEnd() { + return WeekDayEnd; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getForTheRegex() { + return ForTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return WeekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return RelativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return StrictRelativeRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return WeekDayRegex; + } + + @Override + public Pattern getPrefixArticleRegex() { + return PrefixArticleRegex; + } + + @Override + public Pattern getYearSuffix() { + return YearSuffix; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getRangeConnectorSymbolRegex() { + return RangeConnectorSymbolRegex; + } + + @Override + public Iterable getDateRegexList() { + return DateRegexList; + } + + @Override + public Iterable getImplicitDateList() { + return ImplicitDateList; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public ImmutableMap getDayOfWeek() { + return DayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return MonthOfYear; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java new file mode 100644 index 000000000..9af664696 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDatePeriodExtractorConfiguration.java @@ -0,0 +1,320 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.BaseDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDatePeriodExtractorConfiguration extends BaseOptionsConfiguration implements IDatePeriodExtractorConfiguration { + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TillRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + public static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + public static final Pattern MonthNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumRegex); + public static final Pattern IllegalYearRegex = RegExpUtility.getSafeRegExp(BaseDateTime.IllegalYearRegex); + public static final Pattern YearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearRegex); + public static final Pattern RelativeMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeMonthRegex); + public static final Pattern MonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthRegex); + public static final Pattern MonthSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthSuffixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern PastRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PastRegex); + public static final Pattern FutureRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FutureRegex); + public static final Pattern FutureSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FutureSuffixRegex); + + // composite regexes + public static final Pattern SimpleCasesRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleCasesRegex); + public static final Pattern MonthFrontSimpleCasesRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthFrontSimpleCasesRegex); + public static final Pattern MonthFrontBetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthFrontBetweenRegex); + public static final Pattern DayBetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayBetweenRegex); + public static final Pattern OneWordPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OneWordPeriodRegex); + public static final Pattern MonthWithYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthWithYearRegex); + public static final Pattern MonthNumWithYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthNumWithYearRegex); + public static final Pattern WeekOfMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfMonthRegex); + public static final Pattern WeekOfYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfYearRegex); + public static final Pattern FollowedDateUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedDateUnit); + public static final Pattern NumberCombinedWithDateUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberCombinedWithDateUnit); + public static final Pattern QuarterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.QuarterRegex); + public static final Pattern QuarterRegexYearFront = RegExpUtility.getSafeRegExp(SpanishDateTime.QuarterRegexYearFront); + public static final Pattern AllHalfYearRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AllHalfYearRegex); + public static final Pattern SeasonRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SeasonRegex); + public static final Pattern WhichWeekRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WhichWeekRegex); + public static final Pattern WeekOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekOfRegex); + public static final Pattern MonthOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MonthOfRegex); + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + public static final Pattern LaterEarlyPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterEarlyPeriodRegex); + public static final Pattern RestOfDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RestOfDateRegex); + public static final Pattern WeekWithWeekDayRangeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekWithWeekDayRangeRegex); + public static final Pattern YearPlusNumberRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearPlusNumberRegex); + public static final Pattern DecadeWithCenturyRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DecadeWithCenturyRegex); + public static final Pattern YearPeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.YearPeriodRegex); + public static final Pattern ComplexDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ComplexDatePeriodRegex); + public static final Pattern RelativeDecadeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDecadeRegex); + public static final Pattern ReferenceDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ReferenceDatePeriodRegex); + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AgoRegex); + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern CenturySuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.CenturySuffixRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NowRegex); + + public static final Iterable SimpleCasesRegexes = new ArrayList() { + { + add(SimpleCasesRegex); + add(DayBetweenRegex); + add(OneWordPeriodRegex); + add(MonthWithYearRegex); + add(MonthNumWithYearRegex); + add(YearRegex); + add(YearPeriodRegex); + add(WeekOfMonthRegex); + add(WeekOfYearRegex); + add(MonthFrontBetweenRegex); + add(MonthFrontSimpleCasesRegex); + add(QuarterRegex); + add(QuarterRegexYearFront); + add(SeasonRegex); + add(RestOfDateRegex); + add(LaterEarlyPeriodRegex); + add(WeekWithWeekDayRangeRegex); + add(YearPlusNumberRegex); + add(DecadeWithCenturyRegex); + add(RelativeDecadeRegex); + add(MonthOfRegex); + } + }; + + private static final Pattern fromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + private static final Pattern betweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + + private final IDateTimeExtractor datePointExtractor; + private final IExtractor cardinalExtractor; + private final IExtractor ordinalExtractor; + private final IDateTimeExtractor durationExtractor; + private final IParser numberParser; + private final String[] durationDateRestrictions; + + public SpanishDatePeriodExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + + datePointExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + cardinalExtractor = CardinalExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + + durationDateRestrictions = SpanishDateTime.DurationDateRestrictions.toArray(new String[0]); + } + + @Override + public Iterable getSimpleCasesRegexes() { + return SimpleCasesRegexes; + } + + @Override + public Pattern getIllegalYearRegex() { + return IllegalYearRegex; + } + + @Override + public Pattern getYearRegex() { + return YearRegex; + } + + @Override + public Pattern getTillRegex() { + return TillRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getFollowedDateUnit() { + return FollowedDateUnit; + } + + @Override + public Pattern getNumberCombinedWithDateUnit() { + return NumberCombinedWithDateUnit; + } + + @Override + public Pattern getPastRegex() { + return PastRegex; + } + + @Override + public Pattern getFutureRegex() { + return FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return FutureSuffixRegex; + } + + @Override + public Pattern getWeekOfRegex() { + return WeekOfRegex; + } + + @Override + public Pattern getMonthOfRegex() { + return MonthOfRegex; + } + + @Override + public Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getYearPeriodRegex() { + return YearPeriodRegex; + } + + @Override + public Pattern getRelativeDecadeRegex() { + return RelativeDecadeRegex; + } + + @Override + public Pattern getReferenceDatePeriodRegex() { + return ReferenceDatePeriodRegex; + } + + @Override + public Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public Pattern getCenturySuffixRegex() { + return CenturySuffixRegex; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public String[] getDurationDateRestrictions() { + return durationDateRestrictions; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = fromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = betweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } + + @Override + public Pattern getComplexDatePeriodRegex() { + return ComplexDatePeriodRegex; + } + + @Override + public IDateTimeExtractor getDatePointExtractor() { + return datePointExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java new file mode 100644 index 000000000..36ba13b3c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeAltExtractorConfiguration.java @@ -0,0 +1,90 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.config.IOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeAltExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishDateTimeAltExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeAltExtractorConfiguration { + + private static final Pattern OrRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OrRegex); + private static final Pattern DayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DayRegex); + + public static final Pattern ThisPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisPrefixRegex); + public static final Pattern PreviousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + public static final Pattern NextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmRegex); + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + public static final Pattern RangePrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangePrefixRegex); + + public static final Iterable RelativePrefixList = new ArrayList() { + { + add(ThisPrefixRegex); + add(PreviousPrefixRegex); + add(NextPrefixRegex); + } + }; + + public static final Iterable AmPmRegexList = new ArrayList() { + { + add(AmRegex); + add(PmRegex); + } + }; + + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor datePeriodExtractor; + + public SpanishDateTimeAltExtractorConfiguration(IOptionsConfiguration config) { + super(config.getOptions()); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public Iterable getRelativePrefixList() { + return RelativePrefixList; + } + + @Override + public Iterable getAmPmRegexList() { + return AmPmRegexList; + } + + @Override + public Pattern getOrRegex() { + return OrRegex; + } + + @Override + public Pattern getThisPrefixRegex() { + return ThisPrefixRegex; + } + + @Override + public Pattern getDayRegex() { + return DayRegex; + } + + @Override public Pattern getRangePrefixRegex() { + return RangePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java new file mode 100644 index 000000000..f0fda81a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimeExtractorConfiguration.java @@ -0,0 +1,170 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.english.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.regex.Pattern; + +public class SpanishDateTimeExtractorConfiguration extends BaseOptionsConfiguration implements IDateTimeExtractorConfiguration { + + public static final Pattern PrepositionRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrepositionRegex); + public static final Pattern NowRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NowRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixRegex); + + //TODO: modify it according to the corresponding English regex + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern SpecificTimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeOfDayRegex); + public static final Pattern TimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfTodayAfterRegex); + public static final Pattern TimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfTodayBeforeRegex); + public static final Pattern SimpleTimeOfTodayAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleTimeOfTodayAfterRegex); + public static final Pattern SimpleTimeOfTodayBeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SimpleTimeOfTodayBeforeRegex); + public static final Pattern SpecificEndOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificEndOfRegex); + public static final Pattern UnspecificEndOfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificEndOfRegex); + + //TODO: add this for Spanish + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern ConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConnectorRegex); + public static final Pattern NumberAsTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberAsTimeRegex); + public static final Pattern DateNumberConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateNumberConnectorRegex); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAfterRegex); + + public SpanishDateTimeExtractorConfiguration(DateTimeOptions options) { + + super(options); + + integerExtractor = IntegerExtractor.getInstance(); + datePointExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timePointExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + } + + public SpanishDateTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + private IExtractor integerExtractor; + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + private IDateExtractor datePointExtractor; + + @Override + public IDateExtractor getDatePointExtractor() { + return datePointExtractor; + } + + private IDateTimeExtractor timePointExtractor; + + @Override + public IDateTimeExtractor getTimePointExtractor() { + return timePointExtractor; + } + + private IDateTimeExtractor durationExtractor; + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Pattern getNowRegex() { + return NowRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getTimeOfTodayAfterRegex() { + return TimeOfTodayAfterRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayAfterRegex() { + return SimpleTimeOfTodayAfterRegex; + } + + @Override + public Pattern getTimeOfTodayBeforeRegex() { + return TimeOfTodayBeforeRegex; + } + + @Override + public Pattern getSimpleTimeOfTodayBeforeRegex() { + return SimpleTimeOfTodayBeforeRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + @Override + public Pattern getSpecificEndOfRegex() { + return SpecificEndOfRegex; + } + + @Override + public Pattern getUnspecificEndOfRegex() { + return UnspecificEndOfRegex; + } + + @Override + public Pattern getUnitRegex() { + return UnitRegex; + } + + @Override + public Pattern getNumberAsTimeRegex() { + return NumberAsTimeRegex; + } + + @Override + public Pattern getDateNumberConnectorRegex() { + return DateNumberConnectorRegex; + } + + @Override + public Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public boolean isConnector(String text) { + + text = text.trim(); + + boolean isPreposition = Arrays.stream(RegExpUtility.getMatches(PrepositionRegex, text)).findFirst().isPresent(); + boolean isConnector = Arrays.stream(RegExpUtility.getMatches(ConnectorRegex, text)).findFirst().isPresent(); + return (StringUtility.isNullOrEmpty(text) || isPreposition || isConnector); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..8ba69bc8b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDateTimePeriodExtractorConfiguration.java @@ -0,0 +1,283 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimePeriodExtractorConfiguration extends BaseOptionsConfiguration + implements IDateTimePeriodExtractorConfiguration { + + public static final Pattern weekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WeekDayRegex); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.DateTimePeriodNumberCombinedWithUnit); + public static final Pattern RestOfDateTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RestOfDateTimeRegex); + public static final Pattern PeriodTimeOfDayWithDateRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PeriodTimeOfDayWithDateRegex); + public static final Pattern RelativeTimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeTimeUnitRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.GeneralEndingRegex); + public static final Pattern MiddlePauseRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MiddlePauseRegex); + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmDescRegex); + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmDescRegex); + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + public static final Pattern PrefixDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrefixDayRegex); + public static final Pattern SuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixRegex); + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterRegex); + public static final Pattern FromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + public static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + public static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + public static final Pattern TimeFollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeFollowedUnit); + + private final String tokenBeforeDate; + + private final IExtractor cardinalExtractor; + private final IDateTimeExtractor singleDateExtractor; + private final IDateTimeExtractor singleTimeExtractor; + private final IDateTimeExtractor singleDateTimeExtractor; + private final IDateTimeExtractor durationExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor timeZoneExtractor; + + public static final Iterable SimpleCases = new ArrayList() { + { + add(SpanishTimePeriodExtractorConfiguration.PureNumFromTo); + add(SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd); + } + }; + + public SpanishDateTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishDateTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + + cardinalExtractor = CardinalExtractor.getInstance(); + + singleDateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + singleTimeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + singleDateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateExtractor() { + return singleDateExtractor; + } + + @Override + public IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + @Override + public IDateTimeExtractor getSingleDateTimeExtractor() { + return singleDateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + @Override + public Iterable getSimpleCasesRegex() { + return SimpleCases; + } + + @Override + public Pattern getPrepositionRegex() { + return SpanishDateTimeExtractorConfiguration.PrepositionRegex; + } + + @Override + public Pattern getTillRegex() { + return SpanishTimePeriodExtractorConfiguration.TillRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return SpanishDateTimeExtractorConfiguration.TimeOfDayRegex; + } + + @Override + public Pattern getFollowedUnit() { + return TimeFollowedUnit; + } + + @Override + public Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return SpanishDatePeriodExtractorConfiguration.PastRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return SpanishDatePeriodExtractorConfiguration.FutureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return PrefixDayRegex; + } + + @Override + public Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return PeriodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return RelativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return RestOfDateTimeRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public Pattern getMiddlePauseRegex() { + return MiddlePauseRegex; + } + + @Override + public Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public Pattern getSuffixRegex() { + return SuffixRegex; + } + + @Override + public Pattern getBeforeRegex() { + return BeforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return AfterRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java new file mode 100644 index 000000000..064ff06dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishDurationExtractorConfiguration.java @@ -0,0 +1,139 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishDurationExtractorConfiguration extends BaseOptionsConfiguration implements IDurationExtractorConfiguration { + + //public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnitRegex); + public static final Pattern SuffixAndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAndRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationNumberCombinedWithUnit); + public static final Pattern AnUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AnUnitRegex); + public static final Pattern DuringRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DuringRegex); + public static final Pattern AllRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AllRegex); + public static final Pattern HalfRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HalfRegex); + public static final Pattern ConjunctionRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConjunctionRegex); + public static final Pattern InexactNumberRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InexactNumberRegex); + public static final Pattern InexactNumberUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InexactNumberUnitRegex); + public static final Pattern RelativeDurationUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDurationUnitRegex); + public static final Pattern DurationUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationUnitRegex); + public static final Pattern DurationConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DurationConnectorRegex); + public static final Pattern MoreThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MoreThanRegex); + public static final Pattern LessThanRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanRegex); + + private final IExtractor cardinalExtractor; + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + + public SpanishDurationExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishDurationExtractorConfiguration(DateTimeOptions options) { + + super(options); + + cardinalExtractor = CardinalExtractor.getInstance(); + unitMap = SpanishDateTime.UnitMap; + unitValueMap = SpanishDateTime.UnitValueMap; + } + + @Override + public Pattern getFollowedUnit() { + return FollowedUnit; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return NumberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return AnUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return DuringRegex; + } + + @Override + public Pattern getAllRegex() { + return AllRegex; + } + + @Override + public Pattern getHalfRegex() { + return HalfRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return SuffixAndRegex; + } + + @Override + public Pattern getConjunctionRegex() { + return ConjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return InexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return InexactNumberUnitRegex; + } + + @Override + public Pattern getRelativeDurationUnitRegex() { + return RelativeDurationUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return DurationUnitRegex; + } + + @Override + public Pattern getDurationConnectorRegex() { + return DurationConnectorRegex; + } + + @Override + public Pattern getLessThanRegex() { + return LessThanRegex; + } + + @Override + public Pattern getMoreThanRegex() { + return MoreThanRegex; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java new file mode 100644 index 000000000..3a3e6fb8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishHolidayExtractorConfiguration.java @@ -0,0 +1,36 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.IHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishHolidayExtractorConfiguration extends BaseOptionsConfiguration implements IHolidayExtractorConfiguration { + + public static final Pattern H1 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex1); + + public static final Pattern H2 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex2); + + public static final Pattern H3 = RegExpUtility.getSafeRegExp(SpanishDateTime.HolidayRegex3); + + public static final Iterable HolidayRegexList = new ArrayList() { + { + add(H1); + add(H2); + add(H3); + } + }; + + public SpanishHolidayExtractorConfiguration() { + super(DateTimeOptions.None); + } + + @Override + public Iterable getHolidayRegexes() { + return HolidayRegexList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java new file mode 100644 index 000000000..db952e02b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishMergedExtractorConfiguration.java @@ -0,0 +1,198 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeAltExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseHolidayExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseSetExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeListExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.IMergedExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +import org.javatuples.Pair; + +public class SpanishMergedExtractorConfiguration extends BaseOptionsConfiguration implements IMergedExtractorConfiguration { + + public static final Pattern BeforeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeRegex); + public static final Pattern AfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterRegex); + public static final Pattern SinceRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SinceRegex); + public static final Pattern AroundRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AroundRegex); + public static final Pattern FromToRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromToRegex); + public static final Pattern SingleAmbiguousMonthRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SingleAmbiguousMonthRegex); + public static final Pattern PrepositionSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PrepositionSuffixRegex); + public static final Pattern AmbiguousRangeModifierPrefix = RegExpUtility.getSafeRegExp(SpanishDateTime.AmbiguousRangeModifierPrefix); + public static final Pattern NumberEndingPattern = RegExpUtility.getSafeRegExp(SpanishDateTime.NumberEndingPattern); + public static final Pattern SuffixAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SuffixAfterRegex); + public static final Pattern UnspecificDatePeriodRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificDatePeriodRegex); + public final Iterable> ambiguityFiltersDict = null; + + public static final StringMatcher SuperfluousWordMatcher = new StringMatcher(); + + public SpanishMergedExtractorConfiguration(DateTimeOptions options) { + super(options); + + setExtractor = new BaseSetExtractor(new SpanishSetExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + holidayExtractor = new BaseHolidayExtractor(new SpanishHolidayExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(options)); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + dateTimeAltExtractor = new BaseDateTimeAltExtractor(new SpanishDateTimeAltExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + integerExtractor = IntegerExtractor.getInstance(); + } + + public final StringMatcher getSuperfluousWordMatcher() { + return SuperfluousWordMatcher; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor setExtractor; + + public final IDateTimeExtractor getSetExtractor() { + return setExtractor; + } + + private IDateTimeExtractor holidayExtractor; + + public final IDateTimeExtractor getHolidayExtractor() { + return holidayExtractor; + } + + private IDateTimeZoneExtractor timeZoneExtractor; + + public final IDateTimeZoneExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + private IDateTimeListExtractor dateTimeAltExtractor; + + public final IDateTimeListExtractor getDateTimeAltExtractor() { + return dateTimeAltExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final Iterable> getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + @Override + public Iterable getFilterWordRegexList() { + return null; + } + + public final Pattern getAfterRegex() { + return AfterRegex; + } + + public final Pattern getBeforeRegex() { + return BeforeRegex; + } + + public final Pattern getSinceRegex() { + return SinceRegex; + } + + public final Pattern getAroundRegex() { + return AroundRegex; + } + + public final Pattern getFromToRegex() { + return FromToRegex; + } + + public final Pattern getSingleAmbiguousMonthRegex() { + return SingleAmbiguousMonthRegex; + } + + public final Pattern getPrepositionSuffixRegex() { + return PrepositionSuffixRegex; + } + + public final Pattern getAmbiguousRangeModifierPrefix() { + return null; + } + + public final Pattern getPotentialAmbiguousRangeRegex() { + return null; + } + + public final Pattern getNumberEndingPattern() { + return NumberEndingPattern; + } + + public final Pattern getSuffixAfterRegex() { + return SuffixAfterRegex; + } + + public final Pattern getUnspecificDatePeriodRegex() { + return UnspecificDatePeriodRegex; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java new file mode 100644 index 000000000..023ecb728 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishSetExtractorConfiguration.java @@ -0,0 +1,119 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ISetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishSetExtractorConfiguration extends BaseOptionsConfiguration implements ISetExtractorConfiguration { + + public static final Pattern PeriodicRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PeriodicRegex); + public static final Pattern EachUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachUnitRegex); + public static final Pattern EachPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachPrefixRegex); + public static final Pattern EachDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EachDayRegex); + public static final Pattern BeforeEachDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BeforeEachDayRegex); + public static final Pattern SetWeekDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SetWeekDayRegex); + public static final Pattern SetEachRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.SetEachRegex); + + public SpanishSetExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishSetExtractorConfiguration(DateTimeOptions options) { + super(options); + + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateExtractor dateExtractor; + + public final IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + public final Pattern getLastRegex() { + return SpanishDateExtractorConfiguration.LastDateRegex; + } + + public final Pattern getEachPrefixRegex() { + return EachPrefixRegex; + } + + public final Pattern getPeriodicRegex() { + return PeriodicRegex; + } + + public final Pattern getEachUnitRegex() { + return EachUnitRegex; + } + + public final Pattern getEachDayRegex() { + return EachDayRegex; + } + + public final Pattern getBeforeEachDayRegex() { + return BeforeEachDayRegex; + } + + public final Pattern getSetWeekDayRegex() { + return SetWeekDayRegex; + } + + public final Pattern getSetEachRegex() { + return SetEachRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java new file mode 100644 index 000000000..d92bb5e73 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeExtractorConfiguration.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishTimeExtractorConfiguration extends BaseOptionsConfiguration + implements ITimeExtractorConfiguration { + + // part 1: smallest component + // -------------------------------------- + public static final Pattern DescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DescRegex); + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HourNumRegex); + public static final Pattern MinuteNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MinuteNumRegex); + + // part 2: middle level component + // -------------------------------------- + // handle "... en punto" + public static final Pattern OclockRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.OclockRegex); + + // handle "... tarde" + public static final Pattern PmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + + // handle "... de la mañana" + public static final Pattern AmRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmRegex); + + // handle "y media ..." "menos cuarto ..." + public static final Pattern LessThanOneHour = RegExpUtility.getSafeRegExp(SpanishDateTime.LessThanOneHour); + public static final Pattern TensTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TensTimeRegex); + + // handle "seis treinta", "seis veintiuno", "seis menos diez" + public static final Pattern WrittenTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WrittenTimeRegex); + public static final Pattern TimePrefix = RegExpUtility.getSafeRegExp(SpanishDateTime.TimePrefix); + public static final Pattern TimeSuffix = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeSuffix); + public static final Pattern BasicTime = RegExpUtility.getSafeRegExp(SpanishDateTime.BasicTime); + + // part 3: regex for time + // -------------------------------------- + // handle "a las cuatro" "a las 3" + //TODO: add some new regex which have used in AtRegex + //TODO: modify according to corresponding English regex + public static final Pattern AtRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AtRegex); + public static final Pattern ConnectNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ConnectNumRegex); + public static final Pattern TimeBeforeAfterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeBeforeAfterRegex); + public static final Iterable TimeRegexList = new ArrayList() { + { + // (tres min pasadas las)? siete|7|(siete treinta) pm + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex1)); + + // (tres min pasadas las)? 3:00(:00)? (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex2)); + + // (tres min pasadas las)? 3.00 (pm) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex3)); + + // (tres min pasadas las) (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex4)); + + // (tres min pasadas las) (cinco treinta|siete|7|7:00(:00)?) (pm)? (de la noche) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex5)); + + // (cinco treinta|siete|7|7:00(:00)?) (pm)? (de la noche) + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex6)); + + // (En la noche) a las (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex7)); + + // (En la noche) (cinco treinta|siete|7|7:00(:00)?) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex8)); + + // once (y)? veinticinco + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex9)); + + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex10)); + + // (tres menos veinte) (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex11)); + + // (tres min pasadas las)? 3h00 (pm)? + add(RegExpUtility.getSafeRegExp(SpanishDateTime.TimeRegex12)); + + // 340pm + add(ConnectNumRegex); + } + }; + + public final Pattern getIshRegex() { + return null; + } + + public final Iterable getTimeRegexList() { + return TimeRegexList; + } + + public final Pattern getAtRegex() { + return AtRegex; + } + + public final Pattern getTimeBeforeAfterRegex() { + return TimeBeforeAfterRegex; + } + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeExtractor timeZoneExtractor; + + public final IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + public SpanishTimeExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishTimeExtractorConfiguration(DateTimeOptions options) { + super(options); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java new file mode 100644 index 000000000..7a03533f6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimePeriodExtractorConfiguration.java @@ -0,0 +1,154 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeZoneExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultIndex; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishTimePeriodExtractorConfiguration extends BaseOptionsConfiguration implements ITimePeriodExtractorConfiguration { + + private String tokenBeforeDate; + + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + public static final Pattern HourNumRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.HourNumRegex); + public static final Pattern PureNumFromTo = RegExpUtility.getSafeRegExp(SpanishDateTime.PureNumFromTo); + public static final Pattern PureNumBetweenAnd = RegExpUtility.getSafeRegExp(SpanishDateTime.PureNumBetweenAnd); + public static final Pattern SpecificTimeFromTo = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeFromTo); + public static final Pattern SpecificTimeBetweenAnd = RegExpUtility.getSafeRegExp(SpanishDateTime.SpecificTimeBetweenAnd); + public static final Pattern UnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnitRegex); + public static final Pattern FollowedUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.FollowedUnit); + public static final Pattern NumberCombinedWithUnit = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeNumberCombinedWithUnit); + + private static final Pattern FromRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.FromRegex); + private static final Pattern RangeConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeConnectorRegex); + private static final Pattern BetweenRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.BetweenRegex); + + public static final Pattern TimeOfDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeOfDayRegex); + public static final Pattern GeneralEndingRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.GeneralEndingRegex); + public static final Pattern TillRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TillRegex); + + public SpanishTimePeriodExtractorConfiguration() { + this(DateTimeOptions.None); + } + + public SpanishTimePeriodExtractorConfiguration(DateTimeOptions options) { + + super(options); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + singleTimeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + integerExtractor = IntegerExtractor.getInstance(); + timeZoneExtractor = new BaseTimeZoneExtractor(new SpanishTimeZoneExtractorConfiguration(options)); + } + + private IDateTimeUtilityConfiguration utilityConfiguration; + + public final IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + private IDateTimeExtractor singleTimeExtractor; + + public final IDateTimeExtractor getSingleTimeExtractor() { + return singleTimeExtractor; + } + + private IExtractor integerExtractor; + + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + public final IDateTimeExtractor timeZoneExtractor; + + public IDateTimeExtractor getTimeZoneExtractor() { + return timeZoneExtractor; + } + + + public Iterable getSimpleCasesRegex() { + return getSimpleCasesRegex; + } + + public final Iterable getSimpleCasesRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + + public Iterable getPureNumberRegex() { + return getPureNumberRegex; + } + + public final Iterable getPureNumberRegex = new ArrayList() { + { + add(PureNumFromTo); + add(PureNumBetweenAnd); + } + }; + + public final Pattern getTillRegex() { + return TillRegex; + } + + public final Pattern getTimeOfDayRegex() { + return TimeOfDayRegex; + } + + public final Pattern getGeneralEndingRegex() { + return GeneralEndingRegex; + } + + @Override + public ResultIndex getFromTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = FromRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public ResultIndex getBetweenTokenIndex(String text) { + int index = -1; + boolean result = false; + Matcher matcher = BetweenRegex.matcher(text); + if (matcher.find()) { + result = true; + index = matcher.start(); + } + + return new ResultIndex(result, index); + } + + @Override + public boolean hasConnectorToken(String text) { + Optional match = Arrays.stream(RegExpUtility.getMatches(RangeConnectorRegex, text)).findFirst(); + return match.isPresent() && match.get().length == text.trim().length(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java new file mode 100644 index 000000000..09ede1427 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/extractors/SpanishTimeZoneExtractorConfiguration.java @@ -0,0 +1,44 @@ +package com.microsoft.recognizers.text.datetime.spanish.extractors; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.config.ITimeZoneExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.ArrayList; +import java.util.regex.Pattern; + +public class SpanishTimeZoneExtractorConfiguration extends BaseOptionsConfiguration implements ITimeZoneExtractorConfiguration { + public SpanishTimeZoneExtractorConfiguration(DateTimeOptions options) { + super(options); + + } + + private Pattern directUtcRegex; + + public final Pattern getDirectUtcRegex() { + return directUtcRegex; + } + + private Pattern locationTimeSuffixRegex; + + public final Pattern getLocationTimeSuffixRegex() { + return locationTimeSuffixRegex; + } + + private StringMatcher locationMatcher; + + public final StringMatcher getLocationMatcher() { + return locationMatcher; + } + + private StringMatcher timeZoneMatcher; + + public final StringMatcher getTimeZoneMatcher() { + return timeZoneMatcher; + } + + public final ArrayList getAmbiguousTimezoneList() { + return new ArrayList<>(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java new file mode 100644 index 000000000..527a1e934 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/DateTimePeriodParser.java @@ -0,0 +1,133 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeFormatUtil; +import com.microsoft.recognizers.text.datetime.utilities.DateTimeResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.Optional; + +import org.javatuples.Pair; + +public class DateTimePeriodParser extends BaseDateTimePeriodParser { + + public DateTimePeriodParser(IDateTimePeriodParserConfiguration configuration) { + + super(configuration); + } + + @Override + protected DateTimeResolutionResult parseSpecificTimeOfDay(String text, LocalDateTime referenceTime) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + // handle morning, afternoon.. + MatchedTimeRangeResult matchedTimeRangeResult = this.config.getMatchedTimeRange(trimmedText, "-1", -1, -1, -1); + if (!matchedTimeRangeResult.getMatched()) { + return ret; + } + + boolean exactMatch = RegexExtension.isExactMatch(this.config.getSpecificTimeOfDayRegex(),trimmedText, true); + + if (exactMatch) { + int swift = this.config.getSwiftPrefix(trimmedText); + LocalDateTime date = referenceTime.plusDays(swift); + int day = date.getDayOfMonth(); + int month = date.getMonthValue(); + int year = date.getYear(); + + ret.setTimex(DateTimeFormatUtil.formatDate(date) + matchedTimeRangeResult.getTimeStr()); + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getEndHour(), matchedTimeRangeResult.getEndMin(), matchedTimeRangeResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getBeginHour(), 0, 0), + DateUtil.safeCreateFromValue( + date, year, month, day, matchedTimeRangeResult.getEndHour(), matchedTimeRangeResult.getEndMin(), matchedTimeRangeResult.getEndMin()))); + ret.setSuccess(true); + + return ret; + } + + int startIndex = trimmedText.indexOf(SpanishDateTime.Tomorrow); + if (startIndex == 0) { + startIndex = SpanishDateTime.Tomorrow.length(); + } else { + startIndex = 0; + } + + // handle Date followed by morning, afternoon + // Add handling code to handle morning, afternoon followed by Date + // Add handling code to handle early/late morning, afternoon + Optional match = Arrays.stream(RegExpUtility.getMatches(this.config .getTimeOfDayRegex(), trimmedText.substring(startIndex))).findFirst(); + if (match.isPresent()) { + String beforeStr = trimmedText.substring(0, match.get().index).trim(); + List ers = this.config.getDateExtractor().extract(beforeStr, referenceTime); + + if (ers.size() == 0) { + return ret; + } + + DateTimeParseResult pr = this.config.getDateParser().parse(ers.get(0), referenceTime); + LocalDateTime futureDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getFutureValue(); + LocalDateTime pastDate = (LocalDateTime)((DateTimeResolutionResult)pr.getValue()).getPastValue(); + + ret.setTimex(pr.getTimexStr() + matchedTimeRangeResult.getTimeStr()); + + ret.setFutureValue(new Pair<>( + DateUtil.safeCreateFromValue( + futureDate, + futureDate.getYear(), + futureDate.getMonthValue(), + futureDate.getDayOfMonth(), + matchedTimeRangeResult.getBeginHour(), + 0, + 0), + DateUtil.safeCreateFromValue( + futureDate, + futureDate.getYear(), + futureDate.getMonthValue(), + futureDate.getDayOfMonth(), + matchedTimeRangeResult.getEndHour(), + matchedTimeRangeResult.getEndMin(), + matchedTimeRangeResult.getEndMin()))); + ret.setPastValue(new Pair<>( + DateUtil.safeCreateFromValue(pastDate, + pastDate.getYear(), + pastDate.getMonthValue(), + pastDate.getDayOfMonth(), + matchedTimeRangeResult.getBeginHour(), + 0, + 0), + DateUtil.safeCreateFromValue( + pastDate, + pastDate.getYear(), + pastDate.getMonthValue(), + pastDate.getDayOfMonth(), + matchedTimeRangeResult.getEndHour(), + matchedTimeRangeResult.getEndMin(), + matchedTimeRangeResult.getEndMin()))); + ret.setSuccess(true); + + return ret; + } + + return ret; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java new file mode 100644 index 000000000..ec5a90061 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishCommonDateTimeParserConfiguration.java @@ -0,0 +1,287 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.english.parsers.TimeParser; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDatePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDateTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.BaseTimePeriodExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDatePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeAltParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDateTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseDurationParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimePeriodParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.BaseDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.utilities.SpanishDatetimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.spanish.extractors.CardinalExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.IntegerExtractor; +import com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishCommonDateTimeParserConfiguration extends BaseDateParserConfiguration implements ICommonDateTimeParserConfiguration { + + private final IDateTimeUtilityConfiguration utilityConfiguration; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap numbers; + private final ImmutableMap doubleNumbers; + private final ImmutableMap writtenDecades; + private final ImmutableMap specialDecadeCases; + + private final IExtractor cardinalExtractor; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor datePeriodExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor dateTimePeriodExtractor; + + private final IDateTimeParser timeZoneParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser datePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser dateTimeAltParser; + + public SpanishCommonDateTimeParserConfiguration(DateTimeOptions options) { + + super(options); + + utilityConfiguration = new SpanishDatetimeUtilityConfiguration(); + + unitMap = SpanishDateTime.UnitMap; + unitValueMap = SpanishDateTime.UnitValueMap; + seasonMap = SpanishDateTime.SeasonMap; + specialYearPrefixesMap = SpanishDateTime.SpecialYearPrefixesMap; + cardinalMap = SpanishDateTime.CardinalMap; + dayOfWeek = SpanishDateTime.DayOfWeek; + monthOfYear = SpanishDateTime.MonthOfYear; + numbers = SpanishDateTime.Numbers; + doubleNumbers = SpanishDateTime.DoubleNumbers; + writtenDecades = SpanishDateTime.WrittenDecades; + specialDecadeCases = SpanishDateTime.SpecialDecadeCases; + + cardinalExtractor = CardinalExtractor.getInstance(); + integerExtractor = IntegerExtractor.getInstance(); + ordinalExtractor = OrdinalExtractor.getInstance(); + + numberParser = new BaseNumberParser(new SpanishNumberParserConfiguration()); + + dateExtractor = new BaseDateExtractor(new SpanishDateExtractorConfiguration(this)); + timeExtractor = new BaseTimeExtractor(new SpanishTimeExtractorConfiguration(options)); + dateTimeExtractor = new BaseDateTimeExtractor(new SpanishDateTimeExtractorConfiguration(options)); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration()); + datePeriodExtractor = new BaseDatePeriodExtractor(new SpanishDatePeriodExtractorConfiguration(this)); + timePeriodExtractor = new BaseTimePeriodExtractor(new SpanishTimePeriodExtractorConfiguration(options)); + dateTimePeriodExtractor = new BaseDateTimePeriodExtractor(new SpanishDateTimePeriodExtractorConfiguration(options)); + + timeZoneParser = new BaseTimeZoneParser(); + dateParser = new BaseDateParser(new SpanishDateParserConfiguration(this)); + timeParser = new TimeParser(new SpanishTimeParserConfiguration(this)); + dateTimeParser = new BaseDateTimeParser(new SpanishDateTimeParserConfiguration(this)); + durationParser = new BaseDurationParser(new SpanishDurationParserConfiguration(this)); + datePeriodParser = new BaseDatePeriodParser(new SpanishDatePeriodParserConfiguration(this)); + timePeriodParser = new BaseTimePeriodParser(new SpanishTimePeriodParserConfiguration(this)); + dateTimePeriodParser = new BaseDateTimePeriodParser(new SpanishDateTimePeriodParserConfiguration(this)); + dateTimeAltParser = new BaseDateTimeAltParser(new SpanishDateTimeAltParserConfiguration(this)); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + @Override + public IDateTimeParser getDateTimeAltParser() { + return dateTimeAltParser; + } + + @Override public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java new file mode 100644 index 000000000..858415171 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateParserConfiguration.java @@ -0,0 +1,336 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.StringExtension; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateParserConfiguration extends BaseOptionsConfiguration implements IDateParserConfiguration { + + private final String dateTokenPrefix; + private final IExtractor integerExtractor; + private final IExtractor ordinalExtractor; + private final IExtractor cardinalExtractor; + private final IParser numberParser; + private final IDateTimeExtractor durationExtractor; + private final IDateExtractor dateExtractor; + private final IDateTimeParser durationParser; + private final ImmutableMap unitMap; + private final Iterable dateRegexes; + private final Pattern onRegex; + private final Pattern specialDayRegex; + private final Pattern specialDayWithNumRegex; + private final Pattern nextRegex; + private final Pattern thisRegex; + private final Pattern lastRegex; + private final Pattern unitRegex; + private final Pattern weekDayRegex; + private final Pattern monthRegex; + private final Pattern weekDayOfMonthRegex; + private final Pattern forTheRegex; + private final Pattern weekDayAndDayOfMonthRegex; + private final Pattern relativeMonthRegex; + private final Pattern strictRelativeRegex; + private final Pattern yearSuffix; + private final Pattern relativeWeekDayRegex; + private final Pattern relativeDayRegex; + private final Pattern nextPrefixRegex; + private final Pattern previousPrefixRegex; + + private final ImmutableMap dayOfMonth; + private final ImmutableMap dayOfWeek; + private final ImmutableMap monthOfYear; + private final ImmutableMap cardinalMap; + private final List sameDayTerms; + private final List plusOneDayTerms; + private final List plusTwoDayTerms; + private final List minusOneDayTerms; + private final List minusTwoDayTerms; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishDateParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + dateTokenPrefix = SpanishDateTime.DateTokenPrefix; + integerExtractor = config.getIntegerExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateRegexes = Collections.unmodifiableList(SpanishDateExtractorConfiguration.DateRegexList); + onRegex = SpanishDateExtractorConfiguration.OnRegex; + specialDayRegex = SpanishDateExtractorConfiguration.SpecialDayRegex; + specialDayWithNumRegex = SpanishDateExtractorConfiguration.SpecialDayWithNumRegex; + nextRegex = SpanishDateExtractorConfiguration.NextDateRegex; + thisRegex = SpanishDateExtractorConfiguration.ThisRegex; + lastRegex = SpanishDateExtractorConfiguration.LastDateRegex; + unitRegex = SpanishDateExtractorConfiguration.DateUnitRegex; + weekDayRegex = SpanishDateExtractorConfiguration.WeekDayRegex; + monthRegex = SpanishDateExtractorConfiguration.MonthRegex; + weekDayOfMonthRegex = SpanishDateExtractorConfiguration.WeekDayOfMonthRegex; + forTheRegex = SpanishDateExtractorConfiguration.ForTheRegex; + weekDayAndDayOfMonthRegex = SpanishDateExtractorConfiguration.WeekDayAndDayOfMonthRegex; + relativeMonthRegex = SpanishDateExtractorConfiguration.RelativeMonthRegex; + strictRelativeRegex = SpanishDateExtractorConfiguration.StrictRelativeRegex; + yearSuffix = SpanishDateExtractorConfiguration.YearSuffix; + relativeWeekDayRegex = SpanishDateExtractorConfiguration.RelativeWeekDayRegex; + relativeDayRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeDayRegex); + nextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + previousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + dayOfMonth = config.getDayOfMonth(); + dayOfWeek = config.getDayOfWeek(); + monthOfYear = config.getMonthOfYear(); + cardinalMap = config.getCardinalMap(); + unitMap = config.getUnitMap(); + utilityConfiguration = config.getUtilityConfiguration(); + sameDayTerms = Collections.unmodifiableList(SpanishDateTime.SameDayTerms); + plusOneDayTerms = Collections.unmodifiableList(SpanishDateTime.PlusOneDayTerms); + plusTwoDayTerms = Collections.unmodifiableList(SpanishDateTime.PlusTwoDayTerms); + minusOneDayTerms = Collections.unmodifiableList(SpanishDateTime.MinusOneDayTerms); + minusTwoDayTerms = Collections.unmodifiableList(SpanishDateTime.MinusTwoDayTerms); + } + + @Override + public String getDateTokenPrefix() { + return dateTokenPrefix; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public Iterable getDateRegexes() { + return dateRegexes; + } + + @Override + public Pattern getOnRegex() { + return onRegex; + } + + @Override + public Pattern getSpecialDayRegex() { + return specialDayRegex; + } + + @Override + public Pattern getSpecialDayWithNumRegex() { + return specialDayWithNumRegex; + } + + @Override + public Pattern getNextRegex() { + return nextRegex; + } + + @Override + public Pattern getThisRegex() { + return thisRegex; + } + + @Override + public Pattern getLastRegex() { + return lastRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getWeekDayRegex() { + return weekDayRegex; + } + + @Override + public Pattern getMonthRegex() { + return monthRegex; + } + + @Override + public Pattern getWeekDayOfMonthRegex() { + return weekDayOfMonthRegex; + } + + @Override + public Pattern getForTheRegex() { + return forTheRegex; + } + + @Override + public Pattern getWeekDayAndDayOfMonthRegex() { + return weekDayAndDayOfMonthRegex; + } + + @Override + public Pattern getRelativeMonthRegex() { + return relativeMonthRegex; + } + + @Override + public Pattern getStrictRelativeRegex() { + return strictRelativeRegex; + } + + @Override + public Pattern getYearSuffix() { + return yearSuffix; + } + + @Override + public Pattern getRelativeWeekDayRegex() { + return relativeWeekDayRegex; + } + + @Override + public Pattern getRelativeDayRegex() { + return relativeDayRegex; + } + + @Override + public Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getDayOfWeek() { + return dayOfWeek; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public List getSameDayTerms() { + return sameDayTerms; + } + + @Override + public List getPlusOneDayTerms() { + return plusOneDayTerms; + } + + @Override + public List getMinusOneDayTerms() { + return minusOneDayTerms; + } + + @Override + public List getPlusTwoDayTerms() { + return plusTwoDayTerms; + } + + @Override + public List getMinusTwoDayTerms() { + return minusTwoDayTerms; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public Integer getSwiftMonthOrYear(String text) { + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + int swift = 0; + + Matcher regexMatcher = nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = 1; + } + + regexMatcher = previousPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + + return swift; + } + + @Override + public Boolean isCardinalLast(String text) { + String trimmedText = text.trim().toLowerCase(); + return trimmedText.equals("last"); + } + + @Override + public String normalize(String text) { + return StringExtension.normalize(text, SpanishDateTime.SpecialCharactersEquivalent); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java new file mode 100644 index 000000000..6cd0032f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDatePeriodParserConfiguration.java @@ -0,0 +1,621 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDatePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.EnglishDateTime; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class SpanishDatePeriodParserConfiguration extends BaseOptionsConfiguration implements IDatePeriodParserConfiguration { + + public static final Pattern nextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextPrefixRegex); + public static final Pattern nextSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NextSuffixRegex); + public static final Pattern previousPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousPrefixRegex); + public static final Pattern previousSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PreviousSuffixRegex); + public static final Pattern thisPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.ThisPrefixRegex); + public static final Pattern relativeSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeSuffixRegex); + public static final Pattern relativeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RelativeRegex); + public static final Pattern unspecificEndOfRangeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.UnspecificEndOfRangeRegex); + + public SpanishDatePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + cardinalExtractor = config.getCardinalExtractor(); + ordinalExtractor = config.getOrdinalExtractor(); + integerExtractor = config.getIntegerExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = config.getDurationExtractor(); + dateExtractor = config.getDateExtractor(); + durationParser = config.getDurationParser(); + dateParser = config.getDateParser(); + monthFrontBetweenRegex = SpanishDatePeriodExtractorConfiguration.MonthFrontBetweenRegex; + betweenRegex = SpanishDatePeriodExtractorConfiguration.DayBetweenRegex; + monthFrontSimpleCasesRegex = SpanishDatePeriodExtractorConfiguration.MonthFrontSimpleCasesRegex; + simpleCasesRegex = SpanishDatePeriodExtractorConfiguration.SimpleCasesRegex; + oneWordPeriodRegex = SpanishDatePeriodExtractorConfiguration.OneWordPeriodRegex; + monthWithYear = SpanishDatePeriodExtractorConfiguration.MonthWithYearRegex; + monthNumWithYear = SpanishDatePeriodExtractorConfiguration.MonthNumWithYearRegex; + yearRegex = SpanishDatePeriodExtractorConfiguration.YearRegex; + pastRegex = SpanishDatePeriodExtractorConfiguration.PastRegex; + futureRegex = SpanishDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnit = SpanishDurationExtractorConfiguration.NumberCombinedWithUnit; + weekOfMonthRegex = SpanishDatePeriodExtractorConfiguration.WeekOfMonthRegex; + weekOfYearRegex = SpanishDatePeriodExtractorConfiguration.WeekOfYearRegex; + quarterRegex = SpanishDatePeriodExtractorConfiguration.QuarterRegex; + quarterRegexYearFront = SpanishDatePeriodExtractorConfiguration.QuarterRegexYearFront; + allHalfYearRegex = SpanishDatePeriodExtractorConfiguration.AllHalfYearRegex; + seasonRegex = SpanishDatePeriodExtractorConfiguration.SeasonRegex; + whichWeekRegex = SpanishDatePeriodExtractorConfiguration.WhichWeekRegex; + weekOfRegex = SpanishDatePeriodExtractorConfiguration.WeekOfRegex; + monthOfRegex = SpanishDatePeriodExtractorConfiguration.MonthOfRegex; + restOfDateRegex = SpanishDatePeriodExtractorConfiguration.RestOfDateRegex; + laterEarlyPeriodRegex = SpanishDatePeriodExtractorConfiguration.LaterEarlyPeriodRegex; + weekWithWeekDayRangeRegex = SpanishDatePeriodExtractorConfiguration.WeekWithWeekDayRangeRegex; + yearPlusNumberRegex = SpanishDatePeriodExtractorConfiguration.YearPlusNumberRegex; + decadeWithCenturyRegex = SpanishDatePeriodExtractorConfiguration.DecadeWithCenturyRegex; + yearPeriodRegex = SpanishDatePeriodExtractorConfiguration.YearPeriodRegex; + complexDatePeriodRegex = SpanishDatePeriodExtractorConfiguration.ComplexDatePeriodRegex; + relativeDecadeRegex = SpanishDatePeriodExtractorConfiguration.RelativeDecadeRegex; + inConnectorRegex = config.getUtilityConfiguration().getInConnectorRegex(); + withinNextPrefixRegex = SpanishDatePeriodExtractorConfiguration.WithinNextPrefixRegex; + referenceDatePeriodRegex = SpanishDatePeriodExtractorConfiguration.ReferenceDatePeriodRegex; + agoRegex = SpanishDatePeriodExtractorConfiguration.AgoRegex; + laterRegex = SpanishDatePeriodExtractorConfiguration.LaterRegex; + lessThanRegex = SpanishDatePeriodExtractorConfiguration.LessThanRegex; + moreThanRegex = SpanishDatePeriodExtractorConfiguration.MoreThanRegex; + centurySuffixRegex = SpanishDatePeriodExtractorConfiguration.CenturySuffixRegex; + nowRegex = SpanishDatePeriodExtractorConfiguration.NowRegex; + + unitMap = config.getUnitMap(); + cardinalMap = config.getCardinalMap(); + dayOfMonth = config.getDayOfMonth(); + monthOfYear = config.getMonthOfYear(); + seasonMap = config.getSeasonMap(); + specialYearPrefixesMap = config.getSpecialYearPrefixesMap(); + numbers = config.getNumbers(); + writtenDecades = config.getWrittenDecades(); + specialDecadeCases = config.getSpecialDecadeCases(); + + afterNextSuffixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfterNextSuffixRegex); + } + + // Regex + + private final String tokenBeforeDate; + + private final IDateExtractor dateExtractor; + + private final IExtractor cardinalExtractor; + + private final IExtractor ordinalExtractor; + + private final IDateTimeExtractor durationExtractor; + + private final IExtractor integerExtractor; + + private final IParser numberParser; + + private final IDateTimeParser dateParser; + + private final IDateTimeParser durationParser; + + private final Pattern monthFrontBetweenRegex; + + private final Pattern betweenRegex; + + private final Pattern monthFrontSimpleCasesRegex; + + private final Pattern simpleCasesRegex; + + private final Pattern oneWordPeriodRegex; + + private final Pattern monthWithYear; + + private final Pattern monthNumWithYear; + + private final Pattern yearRegex; + + private final Pattern pastRegex; + + private final Pattern futureRegex; + + private final Pattern futureSuffixRegex; + + private final Pattern numberCombinedWithUnit; + + private final Pattern weekOfMonthRegex; + + private final Pattern weekOfYearRegex; + + private final Pattern quarterRegex; + + private final Pattern quarterRegexYearFront; + + private final Pattern allHalfYearRegex; + + private final Pattern seasonRegex; + + private final Pattern whichWeekRegex; + + private final Pattern weekOfRegex; + + private final Pattern monthOfRegex; + + private final Pattern inConnectorRegex; + + private final Pattern withinNextPrefixRegex; + + private final Pattern restOfDateRegex; + + private final Pattern laterEarlyPeriodRegex; + + private final Pattern weekWithWeekDayRangeRegex; + + private final Pattern yearPlusNumberRegex; + + private final Pattern decadeWithCenturyRegex; + + private final Pattern yearPeriodRegex; + + private final Pattern complexDatePeriodRegex; + + private final Pattern relativeDecadeRegex; + + private final Pattern referenceDatePeriodRegex; + + private final Pattern agoRegex; + + private final Pattern laterRegex; + + private final Pattern lessThanRegex; + + private final Pattern moreThanRegex; + + private final Pattern centurySuffixRegex; + + private final Pattern afterNextSuffixRegex; + + private final Pattern nowRegex; + + // Dictionaries + private final ImmutableMap unitMap; + private final ImmutableMap cardinalMap; + private final ImmutableMap dayOfMonth; + private final ImmutableMap monthOfYear; + private final ImmutableMap seasonMap; + private final ImmutableMap specialYearPrefixesMap; + private final ImmutableMap writtenDecades; + private final ImmutableMap numbers; + private final ImmutableMap specialDecadeCases; + + @Override + public final String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public final IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public final IExtractor getOrdinalExtractor() { + return ordinalExtractor; + } + + @Override + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public final IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public final IParser getNumberParser() { + return numberParser; + } + + @Override + public final IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public final Pattern getMonthFrontBetweenRegex() { + return monthFrontBetweenRegex; + } + + @Override + public final Pattern getBetweenRegex() { + return betweenRegex; + } + + @Override + public final Pattern getMonthFrontSimpleCasesRegex() { + return monthFrontSimpleCasesRegex; + } + + @Override + public final Pattern getSimpleCasesRegex() { + return simpleCasesRegex; + } + + @Override + public final Pattern getOneWordPeriodRegex() { + return oneWordPeriodRegex; + } + + @Override + public final Pattern getMonthWithYear() { + return monthWithYear; + } + + @Override + public final Pattern getMonthNumWithYear() { + return monthNumWithYear; + } + + @Override + public final Pattern getYearRegex() { + return yearRegex; + } + + @Override + public final Pattern getPastRegex() { + return pastRegex; + } + + @Override + public final Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public final Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public final Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public final Pattern getWeekOfMonthRegex() { + return weekOfMonthRegex; + } + + @Override + public final Pattern getWeekOfYearRegex() { + return weekOfYearRegex; + } + + @Override + public final Pattern getQuarterRegex() { + return quarterRegex; + } + + @Override + public final Pattern getQuarterRegexYearFront() { + return quarterRegexYearFront; + } + + @Override + public final Pattern getAllHalfYearRegex() { + return allHalfYearRegex; + } + + @Override + public final Pattern getSeasonRegex() { + return seasonRegex; + } + + @Override + public final Pattern getWhichWeekRegex() { + return whichWeekRegex; + } + + @Override + public final Pattern getWeekOfRegex() { + return weekOfRegex; + } + + @Override + public final Pattern getMonthOfRegex() { + return monthOfRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return inConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public final Pattern getRestOfDateRegex() { + return restOfDateRegex; + } + + @Override + public final Pattern getLaterEarlyPeriodRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getWeekWithWeekDayRangeRegex() { + return laterEarlyPeriodRegex; + } + + @Override + public final Pattern getYearPlusNumberRegex() { + return yearPlusNumberRegex; + } + + @Override + public final Pattern getDecadeWithCenturyRegex() { + return decadeWithCenturyRegex; + } + + @Override + public final Pattern getYearPeriodRegex() { + return yearPeriodRegex; + } + + @Override + public final Pattern getComplexDatePeriodRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getRelativeDecadeRegex() { + return complexDatePeriodRegex; + } + + @Override + public final Pattern getReferenceDatePeriodRegex() { + return referenceDatePeriodRegex; + } + + @Override + public final Pattern getAgoRegex() { + return agoRegex; + } + + @Override + public final Pattern getLaterRegex() { + return laterRegex; + } + + @Override + public final Pattern getLessThanRegex() { + return lessThanRegex; + } + + @Override + public final Pattern getMoreThanRegex() { + return moreThanRegex; + } + + @Override + public final Pattern getCenturySuffixRegex() { + return centurySuffixRegex; + } + + @Override + public final Pattern getNextPrefixRegex() { + return nextPrefixRegex; + } + + @Override + public final Pattern getPastPrefixRegex() { + return previousPrefixRegex; + } + + @Override + public final Pattern getThisPrefixRegex() { + return thisPrefixRegex; + } + + @Override + public final Pattern getRelativeRegex() { + return relativeRegex; + } + + @Override + public final Pattern getUnspecificEndOfRangeRegex() { + return unspecificEndOfRangeRegex; + } + + @Override + public Pattern getNowRegex() { + return nowRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getCardinalMap() { + return cardinalMap; + } + + @Override + public ImmutableMap getDayOfMonth() { + return dayOfMonth; + } + + @Override + public ImmutableMap getMonthOfYear() { + return monthOfYear; + } + + @Override + public ImmutableMap getSeasonMap() { + return seasonMap; + } + + @Override + public ImmutableMap getSpecialYearPrefixesMap() { + return specialYearPrefixesMap; + } + + @Override + public ImmutableMap getWrittenDecades() { + return writtenDecades; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public ImmutableMap getSpecialDecadeCases() { + return specialDecadeCases; + } + + @Override + public int getSwiftDayOrMonth(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = 0; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNextPrefix = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchNextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)).findFirst(); + Optional matchPastPrefix = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchPastSuffix = Arrays.stream(RegExpUtility.getMatches(previousSuffixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNextPrefix.isPresent() || matchNextSuffix.isPresent()) { + swift = 1; + } else if (matchPastPrefix.isPresent() || matchPastSuffix.isPresent()) { + swift = -1; + } + + return swift; + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = text.trim().toLowerCase(); + int swift = -10; + + Optional matchAfterNext = Arrays.stream(RegExpUtility.getMatches(afterNextSuffixRegex, trimmedText)).findFirst(); + Optional matchNextPrefix = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + Optional matchNextSuffix = Arrays.stream(RegExpUtility.getMatches(nextSuffixRegex, trimmedText)).findFirst(); + Optional matchPastPrefix = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + Optional matchPastSuffix = Arrays.stream(RegExpUtility.getMatches(previousSuffixRegex, trimmedText)).findFirst(); + Optional matchThisPresent = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + + if (matchAfterNext.isPresent()) { + swift = 2; + } else if (matchNextPrefix.isPresent() || matchNextSuffix.isPresent()) { + swift = 1; + } else if (matchPastPrefix.isPresent() || matchPastSuffix.isPresent()) { + swift = -1; + } else if (matchThisPresent.isPresent()) { + swift = 0; + } + + return swift; + } + + @Override + public boolean isFuture(String text) { + String trimmedText = text.trim().toLowerCase(); + + Optional matchThis = Arrays.stream(RegExpUtility.getMatches(thisPrefixRegex, trimmedText)).findFirst(); + Optional matchNext = Arrays.stream(RegExpUtility.getMatches(nextPrefixRegex, trimmedText)).findFirst(); + return matchThis.isPresent() || matchNext.isPresent(); + } + + @Override + public boolean isLastCardinal(String text) { + String trimmedText = text.trim().toLowerCase(); + + Optional matchLast = Arrays.stream(RegExpUtility.getMatches(previousPrefixRegex, trimmedText)).findFirst(); + return matchLast.isPresent(); + } + + @Override + public boolean isMonthOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return SpanishDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.MonthTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent(); + } + + @Override + public boolean isMonthToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + return SpanishDateTime.MonthToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isWeekend(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent(); + } + + @Override + public boolean isWeekOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + Optional matchRelative = Arrays.stream(RegExpUtility.getMatches(relativeSuffixRegex, trimmedText)).findFirst(); + return (SpanishDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.endsWith(o)) || + SpanishDateTime.WeekTerms.stream().anyMatch(o -> trimmedText.contains(o)) && matchRelative.isPresent()) && + !SpanishDateTime.WeekendTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearOnly(String text) { + String trimmedText = text.trim().toLowerCase(); + return SpanishDateTime.YearTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } + + @Override + public boolean isYearToDate(String text) { + String trimmedText = text.trim().toLowerCase(); + + return SpanishDateTime.YearToDateTerms.stream().anyMatch(o -> trimmedText.endsWith(o)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java new file mode 100644 index 000000000..38020f75f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeAltParserConfiguration.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeAltParserConfiguration; + +public class SpanishDateTimeAltParserConfiguration implements IDateTimeAltParserConfiguration { + + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimePeriodParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser datePeriodParser; + + public SpanishDateTimeAltParserConfiguration(ICommonDateTimeParserConfiguration config) { + dateTimeParser = config.getDateTimeParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + datePeriodParser = config.getDatePeriodParser(); + } + + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + public IDateTimeParser getDateParser() { + return dateParser; + } + + public IDateTimeParser getTimeParser() { + return timeParser; + } + + public IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + public IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java new file mode 100644 index 000000000..8ad340630 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java @@ -0,0 +1,237 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.extractors.config.ResultTimex; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimeParserConfiguration extends BaseOptionsConfiguration implements IDateTimeParserConfiguration { + + public final String tokenBeforeDate; + public final String tokenBeforeTime; + + public final IDateTimeExtractor dateExtractor; + public final IDateTimeExtractor timeExtractor; + public final IDateTimeParser dateParser; + public final IDateTimeParser timeParser; + public final IExtractor cardinalExtractor; + public final IExtractor integerExtractor; + public final IParser numberParser; + public final IDateTimeExtractor durationExtractor; + public final IDateTimeParser durationParser; + + public final ImmutableMap unitMap; + public final ImmutableMap numbers; + + public final Pattern nowRegex; + public final Pattern amTimeRegex; + public final Pattern pmTimeRegex; + public final Pattern simpleTimeOfTodayAfterRegex; + public final Pattern simpleTimeOfTodayBeforeRegex; + public final Pattern specificTimeOfDayRegex; + public final Pattern specificEndOfRegex; + public final Pattern unspecificEndOfRegex; + public final Pattern unitRegex; + public final Pattern dateNumberConnectorRegex; + + public final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishDateTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + super(config.getOptions()); + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + numberParser = config.getNumberParser(); + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + durationParser = config.getDurationParser(); + integerExtractor = config.getIntegerExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + utilityConfiguration = config.getUtilityConfiguration(); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + tokenBeforeTime = SpanishDateTime.TokenBeforeTime; + + nowRegex = SpanishDateTimeExtractorConfiguration.NowRegex; + unitRegex = SpanishDateTimeExtractorConfiguration.UnitRegex; + specificEndOfRegex = SpanishDateTimeExtractorConfiguration.SpecificEndOfRegex; + unspecificEndOfRegex = SpanishDateTimeExtractorConfiguration.UnspecificEndOfRegex; + specificTimeOfDayRegex = SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + dateNumberConnectorRegex = SpanishDateTimeExtractorConfiguration.DateNumberConnectorRegex; + simpleTimeOfTodayAfterRegex = SpanishDateTimeExtractorConfiguration.SimpleTimeOfTodayAfterRegex; + simpleTimeOfTodayBeforeRegex = SpanishDateTimeExtractorConfiguration.SimpleTimeOfTodayBeforeRegex; + + pmTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); + amTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmTimeRegex); + } + + @Override + public int getHour(String text, int hour) { + + String trimmedText = text.trim().toLowerCase(); + int result = hour; + + //TODO: Replace with a regex + if ((trimmedText.endsWith("mañana") || trimmedText.endsWith("madrugada")) && hour >= Constants.HalfDayHourCount) { + result -= Constants.HalfDayHourCount; + } else if (!(trimmedText.endsWith("mañana") || trimmedText.endsWith("madrugada")) && hour < Constants.HalfDayHourCount) { + result += Constants.HalfDayHourCount; + } + + return result; + } + + @Override + public ResultTimex getMatchedNowTimex(String text) { + + String trimmedText = text.trim().toLowerCase(); + + if (trimmedText.endsWith("ahora") || trimmedText.endsWith("mismo") || trimmedText.endsWith("momento")) { + return new ResultTimex(true, "PRESENT_REF"); + } else if (trimmedText.endsWith("posible") || trimmedText.endsWith("pueda") || trimmedText.endsWith("puedas") || + trimmedText.endsWith("podamos") || trimmedText.endsWith("puedan")) { + return new ResultTimex(true, "FUTURE_REF"); + } else if (trimmedText.endsWith("mente")) { + return new ResultTimex(true, "PAST_REF"); + } + + return new ResultTimex(false, null); + } + + @Override + public int getSwiftDay(String text) { + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + Matcher regexMatcher = SpanishDatePeriodParserConfiguration.previousPrefixRegex.matcher(trimmedText); + + int swift = 0; + if (regexMatcher.find()) { + swift = 1; + } else { + regexMatcher = SpanishDatePeriodParserConfiguration.nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = -1; + } + } + + return swift; + } + + @Override + public boolean containsAmbiguousToken(String text, String matchedText) { + return text.contains("esta mañana") && matchedText.contains("mañana"); + } + + @Override public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override public String getTokenBeforeTime() { + return tokenBeforeTime; + } + + @Override public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override public IParser getNumberParser() { + return numberParser; + } + + @Override public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override public ImmutableMap getNumbers() { + return numbers; + } + + @Override public Pattern getNowRegex() { + return nowRegex; + } + + public Pattern getAMTimeRegex() { + return amTimeRegex; + } + + public Pattern getPMTimeRegex() { + return pmTimeRegex; + } + + @Override public Pattern getSimpleTimeOfTodayAfterRegex() { + return simpleTimeOfTodayAfterRegex; + } + + @Override public Pattern getSimpleTimeOfTodayBeforeRegex() { + return simpleTimeOfTodayBeforeRegex; + } + + @Override public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override public Pattern getSpecificEndOfRegex() { + return specificEndOfRegex; + } + + @Override public Pattern getUnspecificEndOfRegex() { + return unspecificEndOfRegex; + } + + @Override public Pattern getUnitRegex() { + return unitRegex; + } + + @Override public Pattern getDateNumberConnectorRegex() { + return dateNumberConnectorRegex; + } + + @Override public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java new file mode 100644 index 000000000..4626f201a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java @@ -0,0 +1,347 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDateTimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDateTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Locale; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class SpanishDateTimePeriodParserConfiguration extends BaseOptionsConfiguration implements IDateTimePeriodParserConfiguration { + + private final String tokenBeforeDate; + + private final IDateTimeExtractor dateExtractor; + private final IDateTimeExtractor timeExtractor; + private final IDateTimeExtractor dateTimeExtractor; + private final IDateTimeExtractor timePeriodExtractor; + private final IDateTimeExtractor durationExtractor; + private final IExtractor cardinalExtractor; + + private final IParser numberParser; + private final IDateTimeParser dateParser; + private final IDateTimeParser timeParser; + private final IDateTimeParser dateTimeParser; + private final IDateTimeParser timePeriodParser; + private final IDateTimeParser durationParser; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeOfDayRegex; + private final Pattern timeOfDayRegex; + private final Pattern pastRegex; + private final Pattern futureRegex; + private final Pattern futureSuffixRegex; + private final Pattern numberCombinedWithUnitRegex; + private final Pattern unitRegex; + private final Pattern periodTimeOfDayWithDateRegex; + private final Pattern relativeTimeUnitRegex; + private final Pattern restOfDateTimeRegex; + private final Pattern amDescRegex; + private final Pattern pmDescRegex; + private final Pattern withinNextPrefixRegex; + private final Pattern prefixDayRegex; + private final Pattern beforeRegex; + private final Pattern afterRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap numbers; + + /*public static final Pattern MorningStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.MorningStartEndRegex); + public static final Pattern AfternoonStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AfternoonStartEndRegex); + public static final Pattern EveningStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.EveningStartEndRegex); + public static final Pattern NightStartEndRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.NightStartEndRegex);*/ + + public SpanishDateTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + tokenBeforeDate = SpanishDateTime.TokenBeforeDate; + + dateExtractor = config.getDateExtractor(); + timeExtractor = config.getTimeExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + cardinalExtractor = config.getCardinalExtractor(); + durationExtractor = config.getDurationExtractor(); + numberParser = config.getNumberParser(); + dateParser = config.getDateParser(); + timeParser = config.getTimeParser(); + timePeriodParser = config.getTimePeriodParser(); + durationParser = config.getDurationParser(); + dateTimeParser = config.getDateTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + + pureNumberFromToRegex = SpanishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeOfDayRegex = SpanishDateTimeExtractorConfiguration.SpecificTimeOfDayRegex; + timeOfDayRegex = SpanishDateTimeExtractorConfiguration.TimeOfDayRegex; + pastRegex = SpanishDatePeriodExtractorConfiguration.PastRegex; + futureRegex = SpanishDatePeriodExtractorConfiguration.FutureRegex; + futureSuffixRegex = SpanishDatePeriodExtractorConfiguration.FutureSuffixRegex; + numberCombinedWithUnitRegex = SpanishDateTimePeriodExtractorConfiguration.NumberCombinedWithUnit; + unitRegex = SpanishTimePeriodExtractorConfiguration.UnitRegex; + periodTimeOfDayWithDateRegex = SpanishDateTimePeriodExtractorConfiguration.PeriodTimeOfDayWithDateRegex; + relativeTimeUnitRegex = SpanishDateTimePeriodExtractorConfiguration.RelativeTimeUnitRegex; + restOfDateTimeRegex = SpanishDateTimePeriodExtractorConfiguration.RestOfDateTimeRegex; + amDescRegex = SpanishDateTimePeriodExtractorConfiguration.AmDescRegex; + pmDescRegex = SpanishDateTimePeriodExtractorConfiguration.PmDescRegex; + withinNextPrefixRegex = SpanishDateTimePeriodExtractorConfiguration.WithinNextPrefixRegex; + prefixDayRegex = SpanishDateTimePeriodExtractorConfiguration.PrefixDayRegex; + beforeRegex = SpanishDateTimePeriodExtractorConfiguration.BeforeRegex; + afterRegex = SpanishDateTimePeriodExtractorConfiguration.AfterRegex; + + unitMap = config.getUnitMap(); + numbers = config.getNumbers(); + } + + @Override + public String getTokenBeforeDate() { + return tokenBeforeDate; + } + + @Override + public IDateTimeExtractor getDateExtractor() { + return dateExtractor; + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + @Override + public IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + @Override + public IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public IDateTimeParser getDateParser() { + return dateParser; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + @Override + public IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + @Override + public IDateTimeParser getDurationParser() { + return durationParser; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeOfDayRegex() { + return specificTimeOfDayRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getPastRegex() { + return pastRegex; + } + + @Override + public Pattern getFutureRegex() { + return futureRegex; + } + + @Override + public Pattern getFutureSuffixRegex() { + return futureSuffixRegex; + } + + @Override + public Pattern getNumberCombinedWithUnitRegex() { + return numberCombinedWithUnitRegex; + } + + @Override + public Pattern getUnitRegex() { + return unitRegex; + } + + @Override + public Pattern getPeriodTimeOfDayWithDateRegex() { + return periodTimeOfDayWithDateRegex; + } + + @Override + public Pattern getRelativeTimeUnitRegex() { + return relativeTimeUnitRegex; + } + + @Override + public Pattern getRestOfDateTimeRegex() { + return restOfDateTimeRegex; + } + + @Override + public Pattern getAmDescRegex() { + return amDescRegex; + } + + @Override + public Pattern getPmDescRegex() { + return pmDescRegex; + } + + @Override + public Pattern getWithinNextPrefixRegex() { + return withinNextPrefixRegex; + } + + @Override + public Pattern getPrefixDayRegex() { + return prefixDayRegex; + } + + @Override + public Pattern getBeforeRegex() { + return beforeRegex; + } + + @Override + public Pattern getAfterRegex() { + return afterRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public MatchedTimeRangeResult getMatchedTimeRange(String text, String timeStr, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + beginHour = 0; + endHour = 0; + endMin = 0; + boolean result = false; + + // TODO: modify it according to the coresponding function in English part + if (trimmedText.endsWith("madrugada")) { + timeStr = "TDA"; + beginHour = 4; + endHour = 8; + result = true; + } else if (trimmedText.endsWith("mañana")) { + timeStr = "TMO"; + beginHour = 8; + endHour = Constants.HalfDayHourCount; + result = true; + } else if (trimmedText.contains("pasado mediodia") || trimmedText.contains("pasado el mediodia")) { + timeStr = "TAF"; + beginHour = Constants.HalfDayHourCount; + endHour = 16; + result = true; + } else if (trimmedText.endsWith("tarde")) { + timeStr = "TEV"; + beginHour = 16; + endHour = 20; + result = true; + } else if (trimmedText.endsWith("noche")) { + timeStr = "TNI"; + beginHour = 20; + endHour = 23; + endMin = 59; + result = true; + } else { + timeStr = null; + } + + return new MatchedTimeRangeResult(result, timeStr, beginHour, endHour, endMin); + } + + @Override + public int getSwiftPrefix(String text) { + + String trimmedText = text.trim().toLowerCase(); + + Pattern regex = Pattern.compile(SpanishDateTime.PreviousPrefixRegex); + Matcher regexMatcher = regex.matcher(trimmedText); + + int swift = 0; + if (regexMatcher.find() || trimmedText.equals("anoche")) { + swift = -1; + } else { + regex = Pattern.compile(SpanishDateTime.NextPrefixRegex); + regexMatcher = regex.matcher(text); + if (regexMatcher.find()) { + swift = 1; + } + } + + return swift; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java new file mode 100644 index 000000000..0c7cd1487 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDurationParserConfiguration.java @@ -0,0 +1,145 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.BaseDurationExtractor; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.IDurationParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDurationExtractorConfiguration; + +import java.util.regex.Pattern; + +public class SpanishDurationParserConfiguration extends BaseOptionsConfiguration implements IDurationParserConfiguration { + + private final IExtractor cardinalExtractor; + private final IExtractor durationExtractor; + private final IParser numberParser; + + private final Pattern numberCombinedWithUnit; + private final Pattern anUnitRegex; + private final Pattern duringRegex; + private final Pattern allDateUnitRegex; + private final Pattern halfDateUnitRegex; + private final Pattern suffixAndRegex; + private final Pattern followedUnit; + private final Pattern conjunctionRegex; + private final Pattern inexactNumberRegex; + private final Pattern inexactNumberUnitRegex; + private final Pattern durationUnitRegex; + + private final ImmutableMap unitMap; + private final ImmutableMap unitValueMap; + private final ImmutableMap doubleNumbers; + + public SpanishDurationParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + cardinalExtractor = config.getCardinalExtractor(); + numberParser = config.getNumberParser(); + durationExtractor = new BaseDurationExtractor(new SpanishDurationExtractorConfiguration(), false); + numberCombinedWithUnit = SpanishDurationExtractorConfiguration.NumberCombinedWithUnit; + + anUnitRegex = SpanishDurationExtractorConfiguration.AnUnitRegex; + duringRegex = SpanishDurationExtractorConfiguration.DuringRegex; + allDateUnitRegex = SpanishDurationExtractorConfiguration.AllRegex; + halfDateUnitRegex = SpanishDurationExtractorConfiguration.HalfRegex; + suffixAndRegex = SpanishDurationExtractorConfiguration.SuffixAndRegex; + followedUnit = SpanishDurationExtractorConfiguration.FollowedUnit; + conjunctionRegex = SpanishDurationExtractorConfiguration.ConjunctionRegex; + inexactNumberRegex = SpanishDurationExtractorConfiguration.InexactNumberRegex; + inexactNumberUnitRegex = SpanishDurationExtractorConfiguration.InexactNumberUnitRegex; + durationUnitRegex = SpanishDurationExtractorConfiguration.DurationUnitRegex; + + unitMap = config.getUnitMap(); + unitValueMap = config.getUnitValueMap(); + doubleNumbers = config.getDoubleNumbers(); + } + + @Override + public IExtractor getCardinalExtractor() { + return cardinalExtractor; + } + + @Override + public IExtractor getDurationExtractor() { + return durationExtractor; + } + + @Override + public IParser getNumberParser() { + return numberParser; + } + + @Override + public Pattern getNumberCombinedWithUnit() { + return numberCombinedWithUnit; + } + + @Override + public Pattern getAnUnitRegex() { + return anUnitRegex; + } + + @Override + public Pattern getDuringRegex() { + return duringRegex; + } + + @Override + public Pattern getAllDateUnitRegex() { + return allDateUnitRegex; + } + + @Override + public Pattern getHalfDateUnitRegex() { + return halfDateUnitRegex; + } + + @Override + public Pattern getSuffixAndRegex() { + return suffixAndRegex; + } + + @Override + public Pattern getFollowedUnit() { + return followedUnit; + } + + @Override + public Pattern getConjunctionRegex() { + return conjunctionRegex; + } + + @Override + public Pattern getInexactNumberRegex() { + return inexactNumberRegex; + } + + @Override + public Pattern getInexactNumberUnitRegex() { + return inexactNumberUnitRegex; + } + + @Override + public Pattern getDurationUnitRegex() { + return durationUnitRegex; + } + + @Override + public ImmutableMap getUnitMap() { + return unitMap; + } + + @Override + public ImmutableMap getUnitValueMap() { + return unitValueMap; + } + + @Override + public ImmutableMap getDoubleNumbers() { + return doubleNumbers; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java new file mode 100644 index 000000000..0eab930ff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishHolidayParserConfiguration.java @@ -0,0 +1,137 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParserConfiguration; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishHolidayExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.DateUtil; +import com.microsoft.recognizers.text.datetime.utilities.HolidayFunctions; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.function.IntFunction; + +public class SpanishHolidayParserConfiguration extends BaseHolidayParserConfiguration { + + public SpanishHolidayParserConfiguration() { + + super(); + + this.setHolidayRegexList(SpanishHolidayExtractorConfiguration.HolidayRegexList); + + HashMap> holidayNamesMap = new HashMap<>(); + for (Map.Entry entry : SpanishDateTime.HolidayNames.entrySet()) { + if (entry.getValue() instanceof String[]) { + holidayNamesMap.put(entry.getKey(), Arrays.asList(entry.getValue())); + } + } + this.setHolidayNames(ImmutableMap.copyOf(holidayNamesMap)); + + HashMap variableHolidaysTimexMap = new HashMap<>(); + for (Map.Entry entry : SpanishDateTime.VariableHolidaysTimexDictionary.entrySet()) { + if (entry.getValue() instanceof String) { + variableHolidaysTimexMap.put(entry.getKey(), entry.getValue()); + } + } + this.setVariableHolidaysTimexDictionary(ImmutableMap.copyOf(variableHolidaysTimexMap)); + } + + @Override + public int getSwiftYear(String text) { + + String trimmedText = StringUtility + .trimStart(StringUtility.trimEnd(text)).toLowerCase(Locale.ROOT); + int swift = -10; + Optional matchNextPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.nextPrefixRegex, text)).findFirst(); + Optional matchPastPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.previousPrefixRegex, text)).findFirst(); + Optional matchThisPrefixRegex = Arrays.stream(RegExpUtility.getMatches( + SpanishDatePeriodParserConfiguration.thisPrefixRegex, text)).findFirst(); + if (matchNextPrefixRegex.isPresent() && matchNextPrefixRegex.get().length == text.trim().length()) { + swift = 1; + } else if (matchPastPrefixRegex.isPresent() && matchPastPrefixRegex.get().length == text.trim().length()) { + swift = -1; + } else if (matchThisPrefixRegex.isPresent() && matchThisPrefixRegex.get().length == text.trim().length()) { + swift = 0; + } + + return swift; + } + + public String sanitizeHolidayToken(String holiday) { + return holiday.replace(" ", "") + .replace("á", "a") + .replace("é", "e") + .replace("í", "i") + .replace("ó", "o") + .replace("ú", "u"); + } + + @Override + protected HashMap> initHolidayFuncs() { + + HashMap> holidays = new HashMap<>(super.initHolidayFuncs()); + holidays.put("padres", SpanishHolidayParserConfiguration::fathersDay); + holidays.put("madres", SpanishHolidayParserConfiguration::mothersDay); + holidays.put("acciondegracias", SpanishHolidayParserConfiguration::thanksgivingDay); + holidays.put("trabajador", SpanishHolidayParserConfiguration::internationalWorkersDay); + holidays.put("delaraza", SpanishHolidayParserConfiguration::columbusDay); + holidays.put("memoria", SpanishHolidayParserConfiguration::memorialDay); + holidays.put("pascuas", SpanishHolidayParserConfiguration::pascuas); + holidays.put("navidad", SpanishHolidayParserConfiguration::christmasDay); + holidays.put("nochebuena", SpanishHolidayParserConfiguration::christmasEve); + holidays.put("añonuevo", SpanishHolidayParserConfiguration::newYear); + holidays.put("nochevieja", SpanishHolidayParserConfiguration::newYearEve); + holidays.put("yuandan", SpanishHolidayParserConfiguration::newYear); + holidays.put("maestro", SpanishHolidayParserConfiguration::teacherDay); + holidays.put("todoslossantos", SpanishHolidayParserConfiguration::halloweenDay); + holidays.put("niño", SpanishHolidayParserConfiguration::childrenDay); + holidays.put("mujer", SpanishHolidayParserConfiguration::femaleDay); + + return holidays; + } + + private static LocalDateTime newYear(int year) { + return DateUtil.safeCreateFromMinValue(year, 1, 1); + } + + private static LocalDateTime newYearEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 31); + } + + private static LocalDateTime christmasDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 25); + } + + private static LocalDateTime christmasEve(int year) { + return DateUtil.safeCreateFromMinValue(year, 12, 24); + } + + private static LocalDateTime femaleDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 3, 8); + } + + private static LocalDateTime childrenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 6, 1); + } + + private static LocalDateTime halloweenDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 10, 31); + } + + private static LocalDateTime teacherDay(int year) { + return DateUtil.safeCreateFromMinValue(year, 9, 10); + } + + private static LocalDateTime pascuas(int year) { + return HolidayFunctions.calculateHolidayByEaster(year); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java new file mode 100644 index 000000000..3c5ea9e4e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishMergedParserConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.datetime.parsers.BaseHolidayParser; +import com.microsoft.recognizers.text.datetime.parsers.BaseSetParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.IMergedParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishDatePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishMergedExtractorConfiguration; +import com.microsoft.recognizers.text.matcher.StringMatcher; + +import java.util.regex.Pattern; + +public class SpanishMergedParserConfiguration extends SpanishCommonDateTimeParserConfiguration implements IMergedParserConfiguration { + + public SpanishMergedParserConfiguration(DateTimeOptions options) { + super(options); + + beforeRegex = SpanishMergedExtractorConfiguration.BeforeRegex; + afterRegex = SpanishMergedExtractorConfiguration.AfterRegex; + sinceRegex = SpanishMergedExtractorConfiguration.SinceRegex; + aroundRegex = SpanishMergedExtractorConfiguration.AroundRegex; + suffixAfterRegex = SpanishMergedExtractorConfiguration.SuffixAfterRegex; + yearRegex = SpanishDatePeriodExtractorConfiguration.YearRegex; + superfluousWordMatcher = SpanishMergedExtractorConfiguration.SuperfluousWordMatcher; + + getParser = new BaseSetParser(new SpanishSetParserConfiguration(this)); + holidayParser = new BaseHolidayParser(new SpanishHolidayParserConfiguration()); + } + + private final Pattern beforeRegex; + private final Pattern afterRegex; + private final Pattern sinceRegex; + private final Pattern aroundRegex; + private final Pattern suffixAfterRegex; + private final Pattern yearRegex; + private final IDateTimeParser getParser; + private final IDateTimeParser holidayParser; + private final StringMatcher superfluousWordMatcher; + + public Pattern getBeforeRegex() { + return beforeRegex; + } + + public Pattern getAfterRegex() { + return afterRegex; + } + + public Pattern getSinceRegex() { + return sinceRegex; + } + + public Pattern getAroundRegex() { + return aroundRegex; + } + + public Pattern getSuffixAfterRegex() { + return suffixAfterRegex; + } + + public Pattern getYearRegex() { + return yearRegex; + } + + public IDateTimeParser getGetParser() { + return getParser; + } + + public IDateTimeParser getHolidayParser() { + return holidayParser; + } + + public StringMatcher getSuperfluousWordMatcher() { + return superfluousWordMatcher; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java new file mode 100644 index 000000000..9cf688cb2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishSetParserConfiguration.java @@ -0,0 +1,218 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateExtractor; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ISetParserConfiguration; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishSetExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.MatchedTimexResult; + +import java.util.Locale; +import java.util.regex.Pattern; + +public class SpanishSetParserConfiguration extends BaseOptionsConfiguration implements ISetParserConfiguration { + + private IDateTimeExtractor durationExtractor; + + public final IDateTimeExtractor getDurationExtractor() { + return durationExtractor; + } + + private IDateTimeParser durationParser; + + public final IDateTimeParser getDurationParser() { + return durationParser; + } + + private IDateTimeExtractor timeExtractor; + + public final IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + private IDateTimeParser timeParser; + + public final IDateTimeParser getTimeParser() { + return timeParser; + } + + private IDateExtractor dateExtractor; + + public final IDateExtractor getDateExtractor() { + return dateExtractor; + } + + private IDateTimeParser dateParser; + + public final IDateTimeParser getDateParser() { + return dateParser; + } + + private IDateTimeExtractor dateTimeExtractor; + + public final IDateTimeExtractor getDateTimeExtractor() { + return dateTimeExtractor; + } + + private IDateTimeParser dateTimeParser; + + public final IDateTimeParser getDateTimeParser() { + return dateTimeParser; + } + + private IDateTimeExtractor datePeriodExtractor; + + public final IDateTimeExtractor getDatePeriodExtractor() { + return datePeriodExtractor; + } + + private IDateTimeParser datePeriodParser; + + public final IDateTimeParser getDatePeriodParser() { + return datePeriodParser; + } + + private IDateTimeExtractor timePeriodExtractor; + + public final IDateTimeExtractor getTimePeriodExtractor() { + return timePeriodExtractor; + } + + private IDateTimeParser timePeriodParser; + + public final IDateTimeParser getTimePeriodParser() { + return timePeriodParser; + } + + private IDateTimeExtractor dateTimePeriodExtractor; + + public final IDateTimeExtractor getDateTimePeriodExtractor() { + return dateTimePeriodExtractor; + } + + private IDateTimeParser dateTimePeriodParser; + + public final IDateTimeParser getDateTimePeriodParser() { + return dateTimePeriodParser; + } + + private ImmutableMap unitMap; + + public final ImmutableMap getUnitMap() { + return unitMap; + } + + private Pattern eachPrefixRegex; + + public final Pattern getEachPrefixRegex() { + return eachPrefixRegex; + } + + private Pattern periodicRegex; + + public final Pattern getPeriodicRegex() { + return periodicRegex; + } + + private Pattern eachUnitRegex; + + public final Pattern getEachUnitRegex() { + return eachUnitRegex; + } + + private Pattern eachDayRegex; + + public final Pattern getEachDayRegex() { + return eachDayRegex; + } + + private Pattern setWeekDayRegex; + + public final Pattern getSetWeekDayRegex() { + return setWeekDayRegex; + } + + private Pattern setEachRegex; + + public final Pattern getSetEachRegex() { + return setEachRegex; + } + + public SpanishSetParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + durationExtractor = config.getDurationExtractor(); + timeExtractor = config.getTimeExtractor(); + dateExtractor = config.getDateExtractor(); + dateTimeExtractor = config.getDateTimeExtractor(); + datePeriodExtractor = config.getDatePeriodExtractor(); + timePeriodExtractor = config.getTimePeriodExtractor(); + dateTimePeriodExtractor = config.getDateTimePeriodExtractor(); + + durationParser = config.getDurationParser(); + timeParser = config.getTimeParser(); + dateParser = config.getDateParser(); + dateTimeParser = config.getDateTimeParser(); + datePeriodParser = config.getDatePeriodParser(); + timePeriodParser = config.getTimePeriodParser(); + dateTimePeriodParser = config.getDateTimePeriodParser(); + unitMap = config.getUnitMap(); + + eachPrefixRegex = SpanishSetExtractorConfiguration.EachPrefixRegex; + periodicRegex = SpanishSetExtractorConfiguration.PeriodicRegex; + eachUnitRegex = SpanishSetExtractorConfiguration.EachUnitRegex; + eachDayRegex = SpanishSetExtractorConfiguration.EachDayRegex; + setWeekDayRegex = SpanishSetExtractorConfiguration.SetWeekDayRegex; + setEachRegex = SpanishSetExtractorConfiguration.SetEachRegex; + } + + public MatchedTimexResult getMatchedDailyTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.endsWith("diario") || trimmedText.endsWith("diariamente")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("semanalmente")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("quincenalmente")) { + result.setTimex("P2W"); + } else if (trimmedText.equals("mensualmente")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("anualmente")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } + + public MatchedTimexResult getMatchedUnitTimex(String text) { + + MatchedTimexResult result = new MatchedTimexResult(); + String trimmedText = text.trim().toLowerCase(Locale.ROOT); + + if (trimmedText.equals("día") || trimmedText.equals("dia") || trimmedText.equals("días") || trimmedText.equals("dias")) { + result.setTimex("P1D"); + } else if (trimmedText.equals("semana") || trimmedText.equals("semanas")) { + result.setTimex("P1W"); + } else if (trimmedText.equals("mes") || trimmedText.equals("meses")) { + result.setTimex("P1M"); + } else if (trimmedText.equals("año") || trimmedText.equals("años")) { + result.setTimex("P1Y"); + } + + if (result.getTimex() != "") { + result.setResult(true); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java new file mode 100644 index 000000000..31e7b56ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimeParserConfiguration.java @@ -0,0 +1,161 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.BaseTimeZoneParser; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.PrefixAdjustResult; +import com.microsoft.recognizers.text.datetime.parsers.config.SuffixAdjustResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimeExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.ConditionalMatch; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.RegexExtension; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public class SpanishTimeParserConfiguration extends BaseOptionsConfiguration implements ITimeParserConfiguration { + + public String timeTokenPrefix = SpanishDateTime.TimeTokenPrefix; + + public final Pattern atRegex; + public Pattern mealTimeRegex; + + private final Iterable timeRegexes; + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + private final IDateTimeParser timeZoneParser; + + public SpanishTimeParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + timeZoneParser = new BaseTimeZoneParser(); + + atRegex = SpanishTimeExtractorConfiguration.AtRegex; + timeRegexes = SpanishTimeExtractorConfiguration.TimeRegexList; + } + + @Override + public String getTimeTokenPrefix() { + return timeTokenPrefix; + } + + @Override + public Pattern getAtRegex() { + return atRegex; + } + + @Override + public Iterable getTimeRegexes() { + return timeRegexes; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public PrefixAdjustResult adjustByPrefix(String prefix, int hour, int min, boolean hasMin) { + + int deltaMin = 0; + String trimmedPrefix = prefix.trim().toLowerCase(); + + if (trimmedPrefix.startsWith("cuarto") || trimmedPrefix.startsWith("y cuarto")) { + deltaMin = 15; + } else if (trimmedPrefix.startsWith("menos cuarto")) { + deltaMin = -15; + } else if (trimmedPrefix.startsWith("media") || trimmedPrefix.startsWith("y media")) { + deltaMin = 30; + } else { + Optional match = Arrays.stream(RegExpUtility.getMatches(SpanishTimeExtractorConfiguration.LessThanOneHour, trimmedPrefix)).findFirst(); + if (match.isPresent()) { + String minStr = match.get().getGroup("deltamin").value; + if (!StringUtility.isNullOrWhiteSpace(minStr)) { + deltaMin = Integer.parseInt(minStr); + } else { + minStr = match.get().getGroup("deltaminnum").value.toLowerCase(); + deltaMin = numbers.getOrDefault(minStr, 0); + } + } + } + + if (trimmedPrefix.endsWith("pasadas") || trimmedPrefix.endsWith("pasados") || trimmedPrefix.endsWith("pasadas las") || + trimmedPrefix.endsWith("pasados las") || trimmedPrefix.endsWith("pasadas de las") || trimmedPrefix.endsWith("pasados de las")) { + //deltaMin is positive + } else if (trimmedPrefix.endsWith("para la") || trimmedPrefix.endsWith("para las") || + trimmedPrefix.endsWith("antes de la") || trimmedPrefix.endsWith("antes de las")) { + deltaMin = -deltaMin; + } + + min += deltaMin; + if (min < 0) { + min += 60; + hour -= 1; + } + + hasMin = hasMin || (min != 0); + + return new PrefixAdjustResult(hour, min, hasMin); + } + + @Override + public SuffixAdjustResult adjustBySuffix(String suffix, int hour, int min, boolean hasMin, boolean hasAm, boolean hasPm) { + + String trimmedSuffix = suffix.trim().toLowerCase(); + PrefixAdjustResult prefixAdjustResult = adjustByPrefix(trimmedSuffix,hour, min, hasMin); + hour = prefixAdjustResult.hour; + min = prefixAdjustResult.minute; + hasMin = prefixAdjustResult.hasMin; + + int deltaHour = 0; + ConditionalMatch match = RegexExtension.matchExact(SpanishTimeExtractorConfiguration.TimeSuffix, trimmedSuffix, true); + if (match.getSuccess()) { + + String oclockStr = match.getMatch().get().getGroup("oclock").value; + if (StringUtility.isNullOrEmpty(oclockStr)) { + + String amStr = match.getMatch().get().getGroup(Constants.AmGroupName).value; + if (!StringUtility.isNullOrEmpty(amStr)) { + if (hour >= Constants.HalfDayHourCount) { + deltaHour = -Constants.HalfDayHourCount; + } + hasAm = true; + } + + String pmStr = match.getMatch().get().getGroup(Constants.PmGroupName).value; + if (!StringUtility.isNullOrEmpty(pmStr)) { + if (hour < Constants.HalfDayHourCount) { + deltaHour = Constants.HalfDayHourCount; + } + hasPm = true; + } + } + } + + hour = (hour + deltaHour) % 24; + + return new SuffixAdjustResult(hour, min, hasMin, hasAm, hasPm); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java new file mode 100644 index 000000000..c4e8debcc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishTimePeriodParserConfiguration.java @@ -0,0 +1,152 @@ +package com.microsoft.recognizers.text.datetime.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.config.BaseOptionsConfiguration; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.datetime.parsers.config.ICommonDateTimeParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.ITimePeriodParserConfiguration; +import com.microsoft.recognizers.text.datetime.parsers.config.MatchedTimeRangeResult; +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.spanish.extractors.SpanishTimePeriodExtractorConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.datetime.utilities.TimeOfDayResolutionResult; +import com.microsoft.recognizers.text.datetime.utilities.TimexUtility; + +import java.util.regex.Pattern; + +public class SpanishTimePeriodParserConfiguration extends BaseOptionsConfiguration implements ITimePeriodParserConfiguration { + + private final IDateTimeExtractor timeExtractor; + private final IDateTimeParser timeParser; + private final IExtractor integerExtractor; + private final IDateTimeParser timeZoneParser; + + private final Pattern pureNumberFromToRegex; + private final Pattern pureNumberBetweenAndRegex; + private final Pattern specificTimeFromToRegex; + private final Pattern specificTimeBetweenAndRegex; + private final Pattern timeOfDayRegex; + private final Pattern generalEndingRegex; + private final Pattern tillRegex; + + private final ImmutableMap numbers; + private final IDateTimeUtilityConfiguration utilityConfiguration; + + public SpanishTimePeriodParserConfiguration(ICommonDateTimeParserConfiguration config) { + + super(config.getOptions()); + + timeExtractor = config.getTimeExtractor(); + integerExtractor = config.getIntegerExtractor(); + timeParser = config.getTimeParser(); + timeZoneParser = config.getTimeZoneParser(); + pureNumberFromToRegex = SpanishTimePeriodExtractorConfiguration.PureNumFromTo; + pureNumberBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.PureNumBetweenAnd; + specificTimeFromToRegex = SpanishTimePeriodExtractorConfiguration.SpecificTimeFromTo; + specificTimeBetweenAndRegex = SpanishTimePeriodExtractorConfiguration.SpecificTimeBetweenAnd; + timeOfDayRegex = SpanishTimePeriodExtractorConfiguration.TimeOfDayRegex; + generalEndingRegex = SpanishTimePeriodExtractorConfiguration.GeneralEndingRegex; + tillRegex = SpanishTimePeriodExtractorConfiguration.TillRegex; + numbers = config.getNumbers(); + utilityConfiguration = config.getUtilityConfiguration(); + } + + @Override + public IDateTimeExtractor getTimeExtractor() { + return timeExtractor; + } + + @Override + public IDateTimeParser getTimeParser() { + return timeParser; + } + + @Override + public IExtractor getIntegerExtractor() { + return integerExtractor; + } + + @Override + public IDateTimeParser getTimeZoneParser() { + return timeZoneParser; + } + + @Override + public Pattern getPureNumberFromToRegex() { + return pureNumberFromToRegex; + } + + @Override + public Pattern getPureNumberBetweenAndRegex() { + return pureNumberBetweenAndRegex; + } + + @Override + public Pattern getSpecificTimeFromToRegex() { + return specificTimeFromToRegex; + } + + @Override + public Pattern getSpecificTimeBetweenAndRegex() { + return specificTimeBetweenAndRegex; + } + + @Override + public Pattern getTimeOfDayRegex() { + return timeOfDayRegex; + } + + @Override + public Pattern getGeneralEndingRegex() { + return generalEndingRegex; + } + + @Override + public Pattern getTillRegex() { + return tillRegex; + } + + @Override + public ImmutableMap getNumbers() { + return numbers; + } + + @Override + public IDateTimeUtilityConfiguration getUtilityConfiguration() { + return utilityConfiguration; + } + + @Override + public MatchedTimeRangeResult getMatchedTimexRange(String text, String timex, int beginHour, int endHour, int endMin) { + + String trimmedText = text.trim().toLowerCase(); + + beginHour = 0; + endHour = 0; + endMin = 0; + + String timeOfDay = ""; + + if (SpanishDateTime.EarlyMorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.EarlyMorning; + } else if (SpanishDateTime.MorningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Morning; + } else if (SpanishDateTime.AfternoonTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Afternoon; + } else if (SpanishDateTime.EveningTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Evening; + } else if (SpanishDateTime.NightTermList.stream().anyMatch(trimmedText::endsWith)) { + timeOfDay = Constants.Night; + } else { + timex = null; + return new MatchedTimeRangeResult(false, timex, beginHour, endHour, endMin); + } + + TimeOfDayResolutionResult result = TimexUtility.parseTimeOfDay(timeOfDay); + + return new MatchedTimeRangeResult(true, result.getTimex(), result.getBeginHour(), result.getEndHour(), result.getEndMin()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java new file mode 100644 index 000000000..874d5e904 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/utilities/SpanishDatetimeUtilityConfiguration.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.datetime.spanish.utilities; + +import com.microsoft.recognizers.text.datetime.resources.SpanishDateTime; +import com.microsoft.recognizers.text.datetime.utilities.IDateTimeUtilityConfiguration; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class SpanishDatetimeUtilityConfiguration implements IDateTimeUtilityConfiguration { + public static final Pattern AgoRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AgoRegex); + + public static final Pattern LaterRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LaterRegex); + + public static final Pattern InConnectorRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.InConnectorRegex); + + public static final Pattern WithinNextPrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.WithinNextPrefixRegex); + + public static final Pattern AmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmDescRegex); + + public static final Pattern PmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmDescRegex); + + public static final Pattern AmPmDescRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmPmDescRegex); + + public static final Pattern RangeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.RangeUnitRegex); + + public static final Pattern TimeUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.TimeUnitRegex); + + public static final Pattern DateUnitRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.DateUnitRegex); + + public static final Pattern CommonDatePrefixRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.CommonDatePrefixRegex); + + @Override + public final Pattern getLaterRegex() { + return LaterRegex; + } + + @Override + public final Pattern getAgoRegex() { + return AgoRegex; + } + + @Override + public final Pattern getInConnectorRegex() { + return InConnectorRegex; + } + + @Override + public final Pattern getWithinNextPrefixRegex() { + return WithinNextPrefixRegex; + } + + @Override + public final Pattern getAmDescRegex() { + return AmDescRegex; + } + + @Override + public final Pattern getPmDescRegex() { + return PmDescRegex; + } + + @Override + public final Pattern getAmPmDescRegex() { + return AmPmDescRegex; + } + + @Override + public final Pattern getRangeUnitRegex() { + return RangeUnitRegex; + } + + @Override + public final Pattern getTimeUnitRegex() { + return TimeUnitRegex; + } + + @Override + public final Pattern getDateUnitRegex() { + return DateUnitRegex; + } + + @Override + public final Pattern getCommonDatePrefixRegex() { + return CommonDatePrefixRegex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java new file mode 100644 index 000000000..2cff56be6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/AgoLaterUtil.java @@ -0,0 +1,197 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.extractors.IDateTimeExtractor; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import com.microsoft.recognizers.text.datetime.parsers.IDateTimeParser; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.MatchGroup; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.regex.Pattern; + +public class AgoLaterUtil { + public static DateTimeResolutionResult parseDurationWithAgoAndLater(String text, LocalDateTime referenceTime, + IDateTimeExtractor durationExtractor, IDateTimeParser durationParser, ImmutableMap unitMap, + Pattern unitRegex, IDateTimeUtilityConfiguration utilityConfiguration, + Function getSwiftDay) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + List durationRes = durationExtractor.extract(text, referenceTime); + if (durationRes.size() > 0) { + DateTimeParseResult pr = durationParser.parse(durationRes.get(0), referenceTime); + Match[] matches = RegExpUtility.getMatches(unitRegex, text); + if (matches.length > 0) { + String afterStr = text.substring(durationRes.get(0).getStart() + durationRes.get(0).getLength()).trim() + .toLowerCase(); + + String beforeStr = text.substring(0, durationRes.get(0).getStart()).trim().toLowerCase(); + + AgoLaterMode mode = AgoLaterMode.DATE; + if (pr.getTimexStr().contains("T")) { + mode = AgoLaterMode.DATETIME; + } + + if (pr.getValue() != null) { + return getAgoLaterResult(pr, afterStr, beforeStr, referenceTime, utilityConfiguration, mode, + getSwiftDay); + } + } + } + return ret; + } + + private static DateTimeResolutionResult getAgoLaterResult(DateTimeParseResult durationParseResult, String afterStr, + String beforeStr, LocalDateTime referenceTime, IDateTimeUtilityConfiguration utilityConfiguration, + AgoLaterMode mode, Function getSwiftDay) { + DateTimeResolutionResult ret = new DateTimeResolutionResult(); + LocalDateTime resultDateTime = referenceTime; + String timex = durationParseResult.getTimexStr(); + + if (((DateTimeResolutionResult)durationParseResult.getValue()).getMod() == Constants.MORE_THAN_MOD) { + ret.setMod(Constants.MORE_THAN_MOD); + } else if (((DateTimeResolutionResult)durationParseResult.getValue()).getMod() == Constants.LESS_THAN_MOD) { + ret.setMod(Constants.LESS_THAN_MOD); + } + + if (MatchingUtil.containsAgoLaterIndex(afterStr, utilityConfiguration.getAgoRegex())) { + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getAgoRegex(), afterStr)).findFirst(); + int swift = 0; + + // Handle cases like "3 days before yesterday" + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup("day").value)) { + swift = getSwiftDay.apply(match.get().getGroup("day").value); + } + + resultDateTime = DurationParsingUtil.shiftDateTime(timex, referenceTime.plusDays(swift), false); + + ((DateTimeResolutionResult)durationParseResult.getValue()).setMod(Constants.BEFORE_MOD); + } else if (MatchingUtil.containsAgoLaterIndex(afterStr, utilityConfiguration.getLaterRegex()) || + MatchingUtil.containsTermIndex(beforeStr, utilityConfiguration.getInConnectorRegex())) { + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getLaterRegex(), afterStr)).findFirst(); + int swift = 0; + + // Handle cases like "3 days after tomorrow" + if (match.isPresent() && !StringUtility.isNullOrEmpty(match.get().getGroup("day").value)) { + swift = getSwiftDay.apply(match.get().getGroup("day").value); + } + + resultDateTime = DurationParsingUtil.shiftDateTime(timex, referenceTime.plusDays(swift), true); + + ((DateTimeResolutionResult)durationParseResult.getValue()).setMod(Constants.AFTER_MOD); + } + + if (resultDateTime != referenceTime) { + if (mode.equals(AgoLaterMode.DATE)) { + ret.setTimex(DateTimeFormatUtil.luisDate(resultDateTime)); + } else if (mode.equals(AgoLaterMode.DATETIME)) { + ret.setTimex(DateTimeFormatUtil.luisDateTime(resultDateTime)); + } + + ret.setFutureValue(resultDateTime); + ret.setPastValue(resultDateTime); + + List subDateTimeEntities = new ArrayList<>(); + subDateTimeEntities.add(durationParseResult); + + ret.setSubDateTimeEntities(subDateTimeEntities); + ret.setSuccess(true); + } + + return ret; + } + + public static List extractorDurationWithBeforeAndAfter(String text, ExtractResult er, List result, + IDateTimeUtilityConfiguration utilityConfiguration) { + int pos = er.getStart() + er.getLength(); + if (pos <= text.length()) { + String afterString = text.substring(pos); + String beforeString = text.substring(0, er.getStart()); + boolean isTimeDuration = RegExpUtility.getMatches(utilityConfiguration.getTimeUnitRegex(), + er.getText()).length != 0; + + MatchingUtilResult resultIndex = MatchingUtil.getAgoLaterIndex(afterString, + utilityConfiguration.getAgoRegex()); + if (resultIndex.result) { + // We don't support cases like "5 minutes from today" for now + // Cases like "5 minutes ago" or "5 minutes from now" are supported + // Cases like "2 days before today" or "2 weeks from today" are also supported + Optional match = Arrays.stream(RegExpUtility.getMatches(utilityConfiguration.getAgoRegex(), afterString)).findFirst(); + boolean isDayMatchInAfterString = match.isPresent() && !match.get().getGroup("day").value.equals(""); + + if (!(isTimeDuration && isDayMatchInAfterString)) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + resultIndex.index)); + } + } else { + resultIndex = MatchingUtil.getAgoLaterIndex(afterString, utilityConfiguration.getLaterRegex()); + if (resultIndex.result) { + Optional match = Arrays.stream(RegExpUtility.getMatches(utilityConfiguration.getLaterRegex(), afterString)).findFirst(); + boolean isDayMatchInAfterString = match.isPresent() && !match.get().getGroup("day").value.equals(""); + + if (!(isTimeDuration && isDayMatchInAfterString)) { + result.add(new Token(er.getStart(), er.getStart() + er.getLength() + resultIndex.index)); + } + } else { + resultIndex = MatchingUtil.getTermIndex(beforeString, utilityConfiguration.getInConnectorRegex()); + if (resultIndex.result) { + // For range unit like "week, month, year", it should output dateRange or + // datetimeRange + Optional match = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getRangeUnitRegex(), er.getText())) + .findFirst(); + if (!match.isPresent()) { + if (er.getStart() >= resultIndex.index) { + result.add(new Token(er.getStart() - resultIndex.index, er.getStart() + er.getLength())); + } + } + } else { + resultIndex = MatchingUtil.getTermIndex(beforeString, + utilityConfiguration.getWithinNextPrefixRegex()); + if (resultIndex.result) { + // For range unit like "week, month, year, day, second, minute, hour", it should + // output dateRange or datetimeRange + Optional matchDateUnitRegex = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getDateUnitRegex(), er.getText())) + .findFirst(); + Optional matchTimeUnitRegex = Arrays + .stream(RegExpUtility.getMatches(utilityConfiguration.getTimeUnitRegex(), er.getText())) + .findFirst(); + if (!matchDateUnitRegex.isPresent() && !matchTimeUnitRegex.isPresent()) { + if (er.getStart() >= resultIndex.index) { + result.add(new Token(er.getStart() - resultIndex.index, er.getStart() + er.getLength())); + } + } + } + } + } + } + } + + return result; + } + + private static boolean isDayMatchInAfterString(String text, Pattern pattern, String group) { + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, text)).findFirst(); + + if (match.isPresent()) { + MatchGroup matchGroup = match.get().getGroup(group); + return !StringUtility.isNullOrEmpty(matchGroup.value); + } + + return false; + } + + public enum AgoLaterMode { + DATE, DATETIME + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java new file mode 100644 index 000000000..2fe1304c5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/ConditionalMatch.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.utilities.Match; + +import java.util.Optional; + +public class ConditionalMatch { + + private final Optional match; + private final boolean success; + + public ConditionalMatch(Optional match, boolean success) { + this.match = match; + this.success = success; + } + + public Optional getMatch() { + + return match; + } + + public boolean getSuccess() { + + return success; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java new file mode 100644 index 000000000..56e1a9ed6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateContext.java @@ -0,0 +1,165 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.parsers.DateTimeParseResult; +import java.time.LocalDateTime; +import java.util.HashMap; + +import org.javatuples.Pair; + +// Currently only Year is enabled as context, we may support Month or Week in the future +public class DateContext { + private int year = Constants.InvalidYear; + + public int getYear() { + return year; + } + + public void setYear(int year) { + this.year = year; + } + + public DateTimeParseResult processDateEntityParsingResult(DateTimeParseResult originalResult) { + if (!isEmpty()) { + originalResult.setTimexStr(TimexUtility.setTimexWithContext(originalResult.getTimexStr(), this)); + originalResult.setValue(processDateEntityResolution((DateTimeResolutionResult)originalResult.getValue())); + } + + return originalResult; + } + + public DateTimeResolutionResult processDateEntityResolution(DateTimeResolutionResult resolutionResult) { + if (!isEmpty()) { + resolutionResult.setTimex(TimexUtility.setTimexWithContext(resolutionResult.getTimex(), this)); + resolutionResult.setFutureValue(setDateWithContext((LocalDateTime)resolutionResult.getFutureValue())); + resolutionResult.setPastValue(setDateWithContext((LocalDateTime)resolutionResult.getPastValue())); + } + + return resolutionResult; + } + + public DateTimeResolutionResult processDatePeriodEntityResolution(DateTimeResolutionResult resolutionResult) { + if (!isEmpty()) { + resolutionResult.setTimex(TimexUtility.setTimexWithContext(resolutionResult.getTimex(), this)); + resolutionResult.setFutureValue(setDateRangeWithContext((Pair)resolutionResult.getFutureValue())); + resolutionResult.setPastValue(setDateRangeWithContext((Pair)resolutionResult.getPastValue())); + } + + return resolutionResult; + } + + // Generate future/past date for cases without specific year like "Feb 29th" + public static HashMap generateDates(boolean noYear, LocalDateTime referenceDate, int year, int month, int day) { + HashMap result = new HashMap<>(); + LocalDateTime futureDate = DateUtil.safeCreateFromMinValue(year, month, day); + LocalDateTime pastDate = DateUtil.safeCreateFromMinValue(year, month, day); + int futureYear = year; + int pastYear = year; + if (noYear) { + if (isFeb29th(year, month, day)) { + if (isLeapYear(year)) { + if (futureDate.compareTo(referenceDate) < 0) { + futureDate = DateUtil.safeCreateFromMinValue(futureYear + 4, month, day); + } else { + pastDate = DateUtil.safeCreateFromMinValue(pastYear - 4, month, day); + } + } else { + pastYear = pastYear >> 2 << 2; + if (!isLeapYear(pastYear)) { + pastYear -= 4; + } + + futureYear = pastYear + 4; + if (!isLeapYear(futureYear)) { + futureYear += 4; + } + futureDate = DateUtil.safeCreateFromMinValue(futureYear, month, day); + pastDate = DateUtil.safeCreateFromMinValue(pastYear, month, day); + } + } else { + if (futureDate.compareTo(referenceDate) < 0 && DateUtil.isValidDate(year, month, day)) { + futureDate = DateUtil.safeCreateFromMinValue(year + 1, month, day); + } + + if (pastDate.compareTo(referenceDate) >= 0 && DateUtil.isValidDate(year, month, day)) { + pastDate = DateUtil.safeCreateFromMinValue(year - 1, month, day); + } + } + } + result.put(Constants.FutureDate, futureDate); + result.put(Constants.PastDate, pastDate); + return result; + } + + private static boolean isLeapYear(int year) { + return (((year % 4) == 0) && (((year % 100) != 0) || ((year % 400) == 0))); + } + + private static boolean isFeb29th(int year, int month, int day) { + return month == 2 && day == 29; + } + + public static boolean isFeb29th(LocalDateTime date) { + return date.getMonthValue() == 2 && date.getDayOfMonth() == 29; + } + + // This method is to ensure the year of begin date is same with the end date in no year situation. + public HashMap syncYear(DateTimeParseResult pr1, DateTimeParseResult pr2) { + if (this.isEmpty()) { + int futureYear; + int pastYear; + if (isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue())) { + futureYear = ((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getFutureValue()).getYear(); + pastYear = ((LocalDateTime)((DateTimeResolutionResult)pr1.getValue()).getPastValue()).getYear(); + pr2.setValue(syncYearResolution((DateTimeResolutionResult)pr2.getValue(), futureYear, pastYear)); + } else if (isFeb29th((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue())) { + futureYear = ((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getFutureValue()).getYear(); + pastYear = ((LocalDateTime)((DateTimeResolutionResult)pr2.getValue()).getPastValue()).getYear(); + pr1.setValue(syncYearResolution((DateTimeResolutionResult)pr1.getValue(), futureYear, pastYear)); + } + } + + HashMap result = new HashMap<>(); + result.put(Constants.ParseResult1, pr1); + result.put(Constants.ParseResult2, pr2); + return result; + } + + public DateTimeResolutionResult syncYearResolution(DateTimeResolutionResult resolutionResult, int futureYear, int pastYear) { + resolutionResult.setFutureValue(setDateWithContext((LocalDateTime)resolutionResult.getFutureValue(), futureYear)); + resolutionResult.setPastValue(setDateWithContext((LocalDateTime)resolutionResult.getPastValue(), pastYear)); + return resolutionResult; + } + + public boolean isEmpty() { + return this.year == Constants.InvalidYear; + } + + // This method is to ensure the begin date is less than the end date + // As DateContext only support common Year as context, so decrease year part of begin date to ensure the begin date is less than end date + public LocalDateTime swiftDateObject(LocalDateTime beginDate, LocalDateTime endDate) { + if (beginDate.isAfter(endDate)) { + beginDate = beginDate.plusYears(-1); + } + + return beginDate; + } + + private LocalDateTime setDateWithContext(LocalDateTime originalDate) { + return setDateWithContext(originalDate, this.year); + } + + private LocalDateTime setDateWithContext(LocalDateTime originalDate, int year) { + if (!DateUtil.isDefaultValue(originalDate)) { + return DateUtil.safeCreateFromMinValue(year, originalDate.getMonthValue(), originalDate.getDayOfMonth()); + } + return originalDate; + } + + private Pair setDateRangeWithContext(Pair originalDateRange) { + LocalDateTime startDate = setDateWithContext(originalDateRange.getValue0()); + LocalDateTime endDate = setDateWithContext(originalDateRange.getValue1()); + + return new Pair<>(startDate, endDate); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java new file mode 100644 index 000000000..f6b194402 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeFormatUtil.java @@ -0,0 +1,248 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.utilities.IntegerUtility; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.time.Duration; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.time.temporal.IsoFields; +import java.util.ArrayList; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DateTimeFormatUtil { + + private static final Pattern HourTimexRegex = Pattern.compile("(? 0) { + result = String.format("%s%sH", result, timeSpan.toHours() % 24); + } + + if (timeSpan.toMinutes() % 60 > 0) { + result = String.format("%s%sM", result, timeSpan.toMinutes() % 60); + } + + if (timeSpan.get(ChronoUnit.SECONDS) % 60 > 0) { + result = String.format("%s%sS", result, timeSpan.get(ChronoUnit.SECONDS) % 60); + } + + if (timeSpan.toMinutes() % 60 > 0) { + result = String.format("%s%sM", result, timeSpan.toMinutes() % 60); + } + + if (timeSpan.get(ChronoUnit.SECONDS) % 60 > 0) { + result = String.format("%s%sS", result, timeSpan.get(ChronoUnit.SECONDS) % 60); + } + + return timeSpan.toString(); + } + + public static String toPm(String timeStr) { + boolean hasT = false; + if (timeStr.startsWith("T")) { + hasT = true; + timeStr = timeStr.substring(1); + } + + String[] splited = timeStr.split(":"); + int hour = Integer.parseInt(splited[0]); + hour = hour >= Constants.HalfDayHourCount ? hour - Constants.HalfDayHourCount : hour + Constants.HalfDayHourCount; + splited[0] = String.format("%02d", hour); + timeStr = String.join(":", splited); + + return hasT ? "T" + timeStr : timeStr; + } + + public static String allStringToPm(String timeStr) { + Match[] matches = RegExpUtility.getMatches(HourTimexRegex, timeStr); + ArrayList splited = new ArrayList<>(); + + int lastPos = 0; + for (Match match : matches) { + if (lastPos != match.index) { + splited.add(timeStr.substring(lastPos, match.index)); + } + splited.add(timeStr.substring(match.index, match.index + match.length)); + lastPos = match.index + match.length; + } + + if (!StringUtility.isNullOrEmpty(timeStr.substring(lastPos))) { + splited.add(timeStr.substring(lastPos)); + } + + for (int i = 0; i < splited.size(); i++) { + if (HourTimexRegex.matcher(splited.get(i)).lookingAt()) { + splited.set(i, toPm(splited.get(i))); + } + } + + // Modify weekDay timex for the cases which cross day boundary + if (splited.size() >= 4) { + Matcher weekDayStartMatch = WeekDayTimexRegex.matcher(splited.get(0)); + Matcher weekDayEndMatch = WeekDayTimexRegex.matcher(splited.get(2)); + Matcher hourStartMatch = HourTimexRegex.matcher(splited.get(1)); + Matcher hourEndMatch = HourTimexRegex.matcher(splited.get(3)); + + String weekDayStartStr = weekDayStartMatch.find() ? weekDayStartMatch.group(1) : ""; + String weekDayEndStr = weekDayEndMatch.find() ? weekDayEndMatch.group(1) : ""; + String hourStartStr = hourStartMatch.find() ? hourStartMatch.group(1) : ""; + String hourEndStr = hourEndMatch.find() ? hourEndMatch.group(1) : ""; + + if (IntegerUtility.canParse(weekDayStartStr) && + IntegerUtility.canParse(weekDayEndStr) && + IntegerUtility.canParse(hourStartStr) && + IntegerUtility.canParse(hourEndStr)) { + int weekDayStart = Integer.parseInt(weekDayStartStr); + int weekDayEnd = Integer.parseInt(weekDayEndStr); + int hourStart = Integer.parseInt(hourStartStr); + int hourEnd = Integer.parseInt(hourEndStr); + + if (hourEnd < hourStart && weekDayStart == weekDayEnd) { + weekDayEnd = weekDayEnd == Constants.WeekDayCount ? 1 : weekDayEnd + 1; + splited.set(2, splited.get(2).substring(0, weekDayEndMatch.start(1)) + weekDayEnd); + } + } + } + + return String.join("", splited); + } + + public static String toIsoWeekTimex(LocalDateTime date) { + int weekNum = LocalDate.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth()).get(IsoFields.WEEK_OF_WEEK_BASED_YEAR); + return String.format("%04d-W%02d", date.getYear(), weekNum); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java new file mode 100644 index 000000000..943ecf02e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateTimeResolutionResult.java @@ -0,0 +1,134 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.util.List; +import java.util.Map; + +public class DateTimeResolutionResult { + + private Boolean success; + private String timex; + private Boolean isLunar; + private String mod; + private Boolean hasRangeChangingMod; + private String comment; + + private Map futureResolution; + private Map pastResolution; + + private Object futureValue; + private Object pastValue; + + private List subDateTimeEntities; + + private TimeZoneResolutionResult timeZoneResolution; + + private List list; + + public DateTimeResolutionResult() { + success = hasRangeChangingMod = false; + } + + public Boolean getSuccess() { + return this.success; + } + + public void setSuccess(Boolean success) { + this.success = success; + } + + public String getTimex() { + return this.timex; + } + + public void setTimex(String timex) { + this.timex = timex; + } + + public Boolean getIsLunar() { + return this.isLunar; + } + + public void setIsLunar(Boolean isLunar) { + this.isLunar = isLunar; + } + + public String getMod() { + return this.mod; + } + + public void setMod(String mod) { + this.mod = mod; + } + + public Boolean getHasRangeChangingMod() { + return this.hasRangeChangingMod; + } + + public void setHasRangeChangingMod(Boolean hasRangeChangingMod) { + this.hasRangeChangingMod = hasRangeChangingMod; + } + + public String getComment() { + return this.comment; + } + + public void setComment(String comment) { + this.comment = comment; + } + + public Map getFutureResolution() { + return this.futureResolution; + } + + public void setFutureResolution(Map futureResolution) { + this.futureResolution = futureResolution; + } + + public Map getPastResolution() { + return this.pastResolution; + } + + public void setPastResolution(Map pastResolution) { + this.pastResolution = pastResolution; + } + + public Object getFutureValue() { + return this.futureValue; + } + + public void setFutureValue(Object futureValue) { + this.futureValue = futureValue; + } + + public Object getPastValue() { + return this.pastValue; + } + + public void setPastValue(Object pastValue) { + this.pastValue = pastValue; + } + + public List getSubDateTimeEntities() { + return this.subDateTimeEntities; + } + + public void setSubDateTimeEntities(List subDateTimeEntities) { + this.subDateTimeEntities = subDateTimeEntities; + } + + public TimeZoneResolutionResult getTimeZoneResolution() { + return this.timeZoneResolution; + } + + public void setTimeZoneResolution(TimeZoneResolutionResult timeZoneResolution) { + this.timeZoneResolution = timeZoneResolution; + } + + public List getList() { + return this.list; + } + + public void setList(List list) { + this.list = list; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java new file mode 100644 index 000000000..0e1a26c19 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DateUtil.java @@ -0,0 +1,156 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeFormatterBuilder; +import java.time.format.DateTimeParseException; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalField; +import java.time.temporal.WeekFields; +import java.util.Locale; + +public class DateUtil { + + public static LocalDateTime safeCreateFromValue(LocalDateTime datetime, int year, int month, int day, int hour, int minute, int second) { + if (isValidDate(year, month, day) && isValidTime(hour, minute, second)) { + datetime = safeCreateFromValue(datetime, year, month, day); + datetime = datetime.plusHours(hour - datetime.getHour()); + datetime = datetime.plusMinutes((minute - datetime.getMinute())); + datetime = datetime.plusSeconds(second - datetime.getSecond()); + } + + return datetime; + } + + public static LocalDateTime safeCreateFromValue(LocalDateTime datetime, int year, int month, int day) { + if (isValidDate(year, month, day)) { + datetime = datetime.plusYears(year - datetime.getYear()); + datetime = datetime.plusMonths((month - datetime.getMonthValue())); + datetime = datetime.plusDays(day - datetime.getDayOfMonth()); + } + + return datetime; + } + + public static LocalDateTime safeCreateFromMinValue(int year, int month, int day) { + return safeCreateFromValue(minValue(), year, month, day, 0, 0, 0); + } + + public static LocalDateTime safeCreateFromMinValue(int year, int month, int day, int hour, int minute, int second) { + return safeCreateFromValue(minValue(), year, month, day, hour, minute, second); + } + + public static LocalDateTime safeCreateFromMinValue(LocalDate date, LocalTime time) { + return safeCreateFromValue(minValue(), + date.getYear(), date.getMonthValue(), date.getDayOfMonth(), + time.getHour(), time.getMinute(), time.getSecond() + ); + } + + public static LocalDateTime minValue() { + return LocalDateTime.of(1, 1, 1, 0, 0, 0, 0); + } + + public static Boolean isValidDate(int year, int month, int day) { + if (year < 1 || year > 9999) { + return false; + } + + Integer[] validDays = { + 31, + year % 4 == 0 && year % 100 != 0 || year % 400 == 0 ? 29 : 28, + 31, + 30, + 31, + 30, + 31, + 31, + 30, + 31, + 30, + 31 + }; + + return month >= 1 && month <= 12 && day >= 1 && day <= validDays[month - 1]; + } + + public static boolean isValidTime(int hour, int minute, int second) { + return 0 <= hour && hour <= 23 && + 0 <= minute && minute <= 59 && + 0 <= second && second <= 59; + } + + public static boolean isDefaultValue(LocalDateTime date) { + return date.equals(DateUtil.minValue()); + } + + private static final DateTimeFormatter DATE_TIME_FORMATTER = new DateTimeFormatterBuilder() + .append(DateTimeFormatter.ofPattern("yyyy-MM-dd")) + .parseDefaulting(ChronoField.HOUR_OF_DAY, 0) + .parseDefaulting(ChronoField.MINUTE_OF_HOUR, 0) + .parseDefaulting(ChronoField.SECOND_OF_MINUTE, 0) + .toFormatter(); + + public static LocalDateTime tryParse(String date) { + try { + return LocalDateTime.parse(date, DATE_TIME_FORMATTER); + } catch (DateTimeParseException ex) { + return null; + } + } + + public static LocalDateTime next(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start + 7); + } + + public static LocalDateTime thisDate(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start); + } + + public static LocalDateTime last(LocalDateTime from, int dayOfWeek) { + int start = from.getDayOfWeek().getValue(); + + if (start == 0) { + start = 7; + } + + if (dayOfWeek == 0) { + dayOfWeek = 7; + } + + return from.plusDays(dayOfWeek - start - 7); + } + + public static LocalDateTime plusPeriodInNanos(LocalDateTime reference, double period, ChronoUnit unit) { + long nanos = unit.getDuration().toNanos(); + return reference.plusNanos(Math.round(nanos * period)); + } + + public static int weekOfYear(LocalDateTime date) { + TemporalField woy = WeekFields.ISO.weekOfYear(); + return date.get(woy); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java new file mode 100644 index 000000000..0b2a9e40a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/DurationParsingUtil.java @@ -0,0 +1,187 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableMap.Builder; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.utilities.DoubleUtility; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; + + +public class DurationParsingUtil { + public static boolean isTimeDurationUnit(String unitStr) { + boolean result = false; + switch (unitStr) { + case "H": + result = true; + break; + case "M": + result = true; + break; + case "S": + result = true; + break; + default: + break; + } + return result; + } + + public static boolean isMultipleDuration(String timex) { + ImmutableMap map = resolveDurationTimex(timex); + return map.size() > 1; + } + + public static boolean isDateDuration(String timex) { + ImmutableMap map = resolveDurationTimex(timex); + + for (String unit : map.keySet()) { + if (isTimeDurationUnit(unit)) { + return false; + } + } + + return true; + } + + public static LocalDateTime shiftDateTime(String timex, LocalDateTime reference, boolean future) { + ImmutableMap timexUnitMap = resolveDurationTimex(timex); + + return getShiftResult(timexUnitMap, reference, future); + } + + public static LocalDateTime getShiftResult(ImmutableMap timexUnitMap, LocalDateTime reference, boolean future) { + LocalDateTime result = reference; + int futureOrPast = future ? 1 : -1; + for (Map.Entry pair : timexUnitMap.entrySet()) { + String unit = pair.getKey(); + ChronoUnit chronoUnit; + Double number = pair.getValue(); + + switch (unit) { + case "H": + chronoUnit = ChronoUnit.HOURS; + break; + case "M": + chronoUnit = ChronoUnit.MINUTES; + break; + case "S": + chronoUnit = ChronoUnit.SECONDS; + break; + case Constants.TimexDay: + chronoUnit = ChronoUnit.DAYS; + break; + case Constants.TimexWeek: + chronoUnit = ChronoUnit.WEEKS; + break; + case Constants.TimexMonthFull: + chronoUnit = null; + result = result.plusMonths(Math.round(number * futureOrPast)); + break; + case Constants.TimexYear: + chronoUnit = null; + result = result.plusYears(Math.round(number * futureOrPast)); + break; + case Constants.TimexBusinessDay: + chronoUnit = null; + result = getNthBusinessDay(result, Math.round(number.floatValue()), future).result; + break; + + default: + return result; + } + if (chronoUnit != null) { + result = DateUtil.plusPeriodInNanos(result, number * futureOrPast, chronoUnit); + } + } + return result; + } + + public static NthBusinessDayResult getNthBusinessDay(LocalDateTime startDate, int number, boolean isFuture) { + LocalDateTime date = startDate; + List dateList = new ArrayList<>(); + dateList.add(date); + + for (int i = 0; i < number; i++) { + date = getNextBusinessDay(date, isFuture); + dateList.add(date); + } + + if (!isFuture) { + Collections.reverse(dateList); + } + + return new NthBusinessDayResult(date, dateList); + + } + + public static LocalDateTime getNextBusinessDay(LocalDateTime startDate) { + return getNextBusinessDay(startDate, true); + } + + // By design it currently does not take holidays into account + public static LocalDateTime getNextBusinessDay(LocalDateTime startDate, boolean isFuture) { + int dateIncrement = isFuture ? 1 : -1; + LocalDateTime date = startDate.plusDays(dateIncrement); + + while (date.getDayOfWeek().equals(DayOfWeek.SATURDAY) || date.getDayOfWeek().equals(DayOfWeek.SUNDAY)) { + date = date.plusDays(dateIncrement); + } + + return date; + } + + private static ImmutableMap resolveDurationTimex(String timex) { + Builder resultBuilder = ImmutableMap.builder(); + + // resolve duration timex, such as P21DT2H(21 days 2 hours) + String durationStr = timex.replace('P', '\0'); + int numberStart = 0; + boolean isTime = false; + + // Resolve business days + if (durationStr.endsWith(Constants.TimexBusinessDay)) { + if (DoubleUtility.canParse(durationStr.substring(0, durationStr.length() - 2))) { + + double numVal = Double.parseDouble(durationStr.substring(0, durationStr.length() - 2)); + resultBuilder.put(Constants.TimexBusinessDay, numVal); + } + + return resultBuilder.build(); + } + + for (int i = 0; i < durationStr.length(); i++) { + if (Character.isLetter(durationStr.charAt(i))) { + if (durationStr.charAt(i) == 'T') { + isTime = true; + } else { + String numStr = durationStr.substring(numberStart, i); + + try { + Double number = Double.parseDouble(numStr); + String srcTimexUnit = durationStr.substring(i, i + 1); + + if (!isTime && srcTimexUnit.equals("M")) { + srcTimexUnit = "MON"; + } + + resultBuilder.put(srcTimexUnit, number); + + } catch (NumberFormatException e) { + return resultBuilder.build(); + } + + } + numberStart = i + 1; + } + } + + return resultBuilder.build(); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java new file mode 100644 index 000000000..2e424c492 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/GetModAndDateResult.java @@ -0,0 +1,22 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; +import java.util.List; + +public class GetModAndDateResult { + public final LocalDateTime beginDate; + public final LocalDateTime endDate; + public final String mod; + public final List dateList; + + public GetModAndDateResult(LocalDateTime beginDate, LocalDateTime endDate, String mod, List dateList) { + this.beginDate = beginDate; + this.endDate = endDate; + this.mod = mod; + this.dateList = dateList; + } + + public GetModAndDateResult() { + this(null, null, "", null); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java new file mode 100644 index 000000000..260d816e4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/HolidayFunctions.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; + +public class HolidayFunctions { + + public static LocalDateTime calculateHolidayByEaster(int year) { + return calculateHolidayByEaster(year, 0); + } + + public static LocalDateTime calculateHolidayByEaster(int year, int days) { + + int day = 0; + int month = 3; + + int g = year % 19; + int c = year / 100; + int h = (c - (int)(c / 4) - (int)(((8 * c) + 13) / 25) + (19 * g) + 15) % 30; + int i = h - ((int)(h / 28) * (1 - ((int)(h / 28) * (int)(29 / (h + 1)) * (int)((21 - g) / 11)))); + + day = i - ((year + (int)(year / 4) + i + 2 - c + (int)(c / 4)) % 7) + 28; + + if (day > 31) { + month++; + day -= 31; + } + + return DateUtil.safeCreateFromMinValue(year, month, day).plusDays(days); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java new file mode 100644 index 000000000..5ea05f65b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/IDateTimeUtilityConfiguration.java @@ -0,0 +1,28 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.util.regex.Pattern; + +public interface IDateTimeUtilityConfiguration { + + Pattern getAgoRegex(); + + Pattern getLaterRegex(); + + Pattern getInConnectorRegex(); + + Pattern getWithinNextPrefixRegex(); + + Pattern getRangeUnitRegex(); + + Pattern getTimeUnitRegex(); + + Pattern getDateUnitRegex(); + + Pattern getAmDescRegex(); + + Pattern getPmDescRegex(); + + Pattern getAmPmDescRegex(); + + Pattern getCommonDatePrefixRegex(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java new file mode 100644 index 000000000..6f9c0bd39 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchedTimexResult.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class MatchedTimexResult { + private boolean result; + private String timex; + + public MatchedTimexResult(boolean result, String timex) { + this.result = result; + this.timex = timex; + } + + public MatchedTimexResult() { + this(false, ""); + } + + public boolean getResult() { + return result; + } + + public String getTimex() { + return timex; + } + + public void setResult(boolean result) { + this.result = result; + } + + public void setTimex(String timex) { + this.timex = timex; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java new file mode 100644 index 000000000..e700d2f1f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtil.java @@ -0,0 +1,106 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.extractors.config.ProcessedSuperfluousWords; +import com.microsoft.recognizers.text.matcher.MatchResult; +import com.microsoft.recognizers.text.matcher.StringMatcher; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class MatchingUtil { + + public static MatchingUtilResult getAgoLaterIndex(String text, Pattern pattern) { + int index = -1; + ConditionalMatch match = RegexExtension.matchBegin(pattern, text, true); + + if (match.getSuccess()) { + index = match.getMatch().get().index + match.getMatch().get().length; + return new MatchingUtilResult(true, index); + } + + return new MatchingUtilResult(); + } + + public static MatchingUtilResult getTermIndex(String text, Pattern pattern) { + String[] parts = text.trim().toLowerCase().split(" "); + String lastPart = parts[parts.length - 1]; + Optional match = Arrays.stream(RegExpUtility.getMatches(pattern, lastPart)).findFirst(); + + if (match.isPresent()) { + int index = text.length() - text.toLowerCase().lastIndexOf(match.get().value); + return new MatchingUtilResult(true, index); + } + + return new MatchingUtilResult(); + } + + public static Boolean containsAgoLaterIndex(String text, Pattern regex) { + MatchingUtilResult result = getAgoLaterIndex(text, regex); + return result.result; + } + + public static Boolean containsTermIndex(String text, Pattern regex) { + MatchingUtilResult result = getTermIndex(text, regex); + return result.result; + } + + // Temporary solution for remove superfluous words only under the Preview mode + public static ProcessedSuperfluousWords preProcessTextRemoveSuperfluousWords(String text, StringMatcher matcher) { + List> superfluousWordMatches = removeSubMatches(matcher.find(text)); + int bias = 0; + + for (MatchResult match : superfluousWordMatches) { + text = text.substring(0, match.getStart() - bias) + text.substring(match.getEnd() - bias); + bias += match.getLength(); + } + + return new ProcessedSuperfluousWords(text, superfluousWordMatches); + } + + // Temporary solution for recover superfluous words only under the Preview mode + public static List posProcessExtractionRecoverSuperfluousWords(List extractResults, + Iterable> superfluousWordMatches, String originText) { + for (MatchResult match : superfluousWordMatches) { + int index = 0; + for (ExtractResult extractResult : extractResults.toArray(new ExtractResult[0])) { + int extractResultEnd = extractResult.getStart() + extractResult.getLength(); + if (match.getStart() > extractResult.getStart() && extractResultEnd >= match.getStart()) { + extractResult.setLength(extractResult.getLength() + match.getLength()); + extractResults.set(index, extractResult); + } + + if (match.getStart() <= extractResult.getStart()) { + extractResult.setStart(extractResult.getStart() + match.getLength()); + extractResults.set(index, extractResult); + } + index++; + } + } + + int index = 0; + for (ExtractResult er : extractResults.toArray(new ExtractResult[0])) { + er.setText(originText.substring(er.getStart(), er.getStart() + er.getLength())); + extractResults.set(index, er); + index++; + } + + return extractResults; + } + + public static List> removeSubMatches(Iterable> matchResults) { + + return StreamSupport.stream(matchResults.spliterator(), false) + .filter(item -> !StreamSupport.stream(matchResults.spliterator(), false) + .anyMatch(ritem -> (ritem.getStart() < item.getStart() && ritem.getEnd() >= item.getEnd()) || + (ritem.getStart() <= item.getStart() && ritem.getEnd() > item.getEnd()))) + .collect(Collectors.toCollection(ArrayList::new)); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java new file mode 100644 index 000000000..b5d06180c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/MatchingUtilResult.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class MatchingUtilResult { + public final boolean result; + public final int index; + + public MatchingUtilResult(boolean result, int index) { + this.result = result; + this.index = index; + } + + public MatchingUtilResult() { + this(false, -1); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java new file mode 100644 index 000000000..155bc9b6d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/NthBusinessDayResult.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import java.time.LocalDateTime; +import java.util.List; + +public class NthBusinessDayResult { + public final LocalDateTime result; + public final List dateList; + + public NthBusinessDayResult(LocalDateTime result, List dateList) { + this.result = result; + this.dateList = dateList; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java new file mode 100644 index 000000000..35fc10d22 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RangeTimexComponents.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class RangeTimexComponents { + public String beginTimex; + + public String endTimex; + + public String durationTimex; + + public Boolean isValid = false; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java new file mode 100644 index 000000000..b4d4b8dc1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/RegexExtension.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Arrays; +import java.util.Optional; +import java.util.regex.Pattern; + +public abstract class RegexExtension { + // Regex match with match length equals to text length + public static boolean isExactMatch(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + int length = trim ? text.trim().length() : text.length(); + + return (match.isPresent() && match.get().length == length); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchExact(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + int length = trim ? text.trim().length() : text.length(); + + return new ConditionalMatch(match, (match.isPresent() && match.get().length == length)); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchEnd(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).reduce((f, s) -> s); + String strAfter = ""; + if (match.isPresent()) { + strAfter = text.substring(match.get().index + match.get().length); + + if (trim) { + strAfter = strAfter.trim(); + } + } + + return new ConditionalMatch(match, (match.isPresent() && StringUtility.isNullOrEmpty(strAfter))); + } + + // We can't trim before match as we may use the match index later + public static ConditionalMatch matchBegin(Pattern regex, String text, boolean trim) { + Optional match = Arrays.stream(RegExpUtility.getMatches(regex, text)).findFirst(); + String strBefore = ""; + + if (match.isPresent()) { + strBefore = text.substring(0, match.get().index); + + if (trim) { + strBefore = strBefore.trim(); + } + } + + return new ConditionalMatch(match, (match.isPresent() && StringUtility.isNullOrEmpty(strBefore))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java new file mode 100644 index 000000000..01607aea1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/StringExtension.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; + +public abstract class StringExtension { + public static String normalize(String text, ImmutableMap dic) { + for (ImmutableMap.Entry keyPair : dic.entrySet()) { + text = text.replace(keyPair.getKey(), keyPair.getValue()); + } + + return text; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java new file mode 100644 index 000000000..1447e81a3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeOfDayResolutionResult.java @@ -0,0 +1,50 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class TimeOfDayResolutionResult { + private String timex; + private int beginHour; + private int endHour; + private int endMin; + + public TimeOfDayResolutionResult(String timex, int beginHour, int endHour, int endMin) { + this.timex = timex; + this.beginHour = beginHour; + this.endHour = endHour; + this.endMin = endMin; + } + + public TimeOfDayResolutionResult() { + } + + public String getTimex() { + return timex; + } + + public void setTimex(String timex) { + this.timex = timex; + } + + public int getBeginHour() { + return beginHour; + } + + public void setBeginHour(int beginHour) { + this.beginHour = beginHour; + } + + public int getEndHour() { + return endHour; + } + + public void setEndHour(int endHour) { + this.endHour = endHour; + } + + public int getEndMin() { + return endMin; + } + + public void setEndMin(int endMin) { + this.endMin = endMin; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java new file mode 100644 index 000000000..7305ec7ff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneResolutionResult.java @@ -0,0 +1,26 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +public class TimeZoneResolutionResult { + + private final String value; + private final Integer utcOffsetMins; + private final String timeZoneText; + + public TimeZoneResolutionResult(String value, Integer utcOffsetMins, String timeZoneText) { + this.value = value; + this.utcOffsetMins = utcOffsetMins; + this.timeZoneText = timeZoneText; + } + + public String getValue() { + return this.value; + } + + public Integer getUtcOffsetMins() { + return this.utcOffsetMins; + } + + public String getTimeZoneText() { + return this.timeZoneText; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java new file mode 100644 index 000000000..7720f1dca --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimeZoneUtility.java @@ -0,0 +1,64 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DateTimeOptions; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public abstract class TimeZoneUtility { + public static List mergeTimeZones(List originalErs, List timeZoneErs, String text) { + + int index = 0; + for (ExtractResult er : originalErs.toArray(new ExtractResult[0])) { + for (ExtractResult timeZoneEr : timeZoneErs) { + int begin = er.getStart() + er.getLength(); + int end = timeZoneEr.getStart(); + + if (begin < end) { + String gapText = text.substring(begin, end); + + if (StringUtility.isNullOrWhiteSpace(gapText)) { + int length = timeZoneEr.getStart() + timeZoneEr.getLength() - er.getStart(); + Map data = new HashMap<>(); + data.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + + originalErs.set(index, new ExtractResult(er.getStart(), length, text.substring(er.getStart(), er.getStart() + length), er.getType(), data)); + } + } + + // Make sure timezone info propagates to longer span entity. + if (er.isOverlap(timeZoneEr)) { + Map data = new HashMap<>(); + data.put(Constants.SYS_DATETIME_TIMEZONE, timeZoneEr); + er.setData(data); + } + } + index++; + } + + return originalErs; + } + + public static boolean shouldResolveTimeZone(ExtractResult er, DateTimeOptions options) { + boolean enablePreview = options.match(DateTimeOptions.EnablePreview); + if (!enablePreview) { + return enablePreview; + } + + boolean hasTimeZoneData = false; + + if (er.getData() instanceof Map) { + Map metadata = (HashMap)er.getData(); + + if (metadata.containsKey(Constants.SYS_DATETIME_TIMEZONE)) { + hasTimeZoneData = true; + } + } + + return hasTimeZoneData; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java new file mode 100644 index 000000000..5c643317f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java @@ -0,0 +1,314 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.datetime.Constants; +import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; +import com.microsoft.recognizers.text.datetime.DateTimeResolutionKey; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class TimexUtility { + private static final HashMap DatePeriodTimexTypeToTimexSuffix = new HashMap() { + { + put(DatePeriodTimexType.ByDay, Constants.TimexDay); + put(DatePeriodTimexType.ByWeek, Constants.TimexWeek); + put(DatePeriodTimexType.ByMonth, Constants.TimexMonth); + put(DatePeriodTimexType.ByYear, Constants.TimexYear); + } + }; + + public static String generateCompoundDurationTimex(Map unitToTimexComponents, ImmutableMap unitValueMap) { + List unitList = new ArrayList<>(unitToTimexComponents.keySet()); + unitList.sort((x, y) -> unitValueMap.get(x) < unitValueMap.get(y) ? 1 : -1); + boolean isTimeDurationAlreadyExist = false; + StringBuilder timexBuilder = new StringBuilder(Constants.GeneralPeriodPrefix); + + for (String unitKey : unitList) { + String timexComponent = unitToTimexComponents.get(unitKey); + + // The Time Duration component occurs first time + if (!isTimeDurationAlreadyExist && isTimeDurationTimex(timexComponent)) { + timexBuilder.append(Constants.TimeTimexPrefix); + timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); + isTimeDurationAlreadyExist = true; + } else { + timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); + } + } + return timexBuilder.toString(); + } + + private static boolean isTimeDurationTimex(String timex) { + return timex.startsWith(Constants.GeneralPeriodPrefix + Constants.TimeTimexPrefix); + } + + private static String getDurationTimexWithoutPrefix(String timex) { + // Remove "PT" prefix for TimeDuration, Remove "P" prefix for DateDuration + return timex.substring(isTimeDurationTimex(timex) ? 2 : 1); + } + + public static String getDatePeriodTimexUnitCount(LocalDateTime begin, LocalDateTime end, + DatePeriodTimexType timexType, Boolean equalDurationLength) { + String unitCount = "XX"; + if (equalDurationLength) { + switch (timexType) { + case ByDay: + unitCount = StringUtility.format((double)ChronoUnit.HOURS.between(begin, end) / 24); + break; + case ByWeek: + unitCount = Long.toString(ChronoUnit.WEEKS.between(begin, end)); + break; + case ByMonth: + unitCount = Long.toString(ChronoUnit.MONTHS.between(begin, end)); + break; + default: + unitCount = new BigDecimal((end.getYear() - begin.getYear()) + (end.getMonthValue() - begin.getMonthValue()) / 12.0).stripTrailingZeros().toString(); + } + } + + return unitCount; + } + + public static String generateDatePeriodTimex(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType) { + + return generateDatePeriodTimex(begin, end, timexType, null, null); + } + + public static String generateDatePeriodTimex(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType, + LocalDateTime alternativeBegin, LocalDateTime alternativeEnd) { + Boolean equalDurationLength; + if (alternativeBegin == null || alternativeEnd == null) { + equalDurationLength = true; + } else { + equalDurationLength = Duration.between(begin, end).equals(Duration.between(alternativeBegin, alternativeEnd)); + } + + String unitCount = getDatePeriodTimexUnitCount(begin, end, timexType, equalDurationLength); + String datePeriodTimex = "P" + unitCount + DatePeriodTimexTypeToTimexSuffix.get(timexType); + return "(" + DateTimeFormatUtil.luisDate(begin, alternativeBegin) + "," + DateTimeFormatUtil.luisDate(end, alternativeEnd) + "," + datePeriodTimex + ")"; + } + + public static String generateDatePeriodTimexStr(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType, + String timex1, String timex2) { + boolean boundaryValid = !DateUtil.isDefaultValue(begin) && !DateUtil.isDefaultValue(end); + String unitCount = boundaryValid ? getDatePeriodTimexUnitCount(begin, end, timexType, true) : "X"; + String datePeriodTimex = "P" + unitCount + DatePeriodTimexTypeToTimexSuffix.get(timexType); + return String.format("(%s,%s,%s)", timex1, timex2, datePeriodTimex); + } + + public static String generateWeekTimex() { + return generateWeekTimex(null); + } + + public static String generateWeekTimex(LocalDateTime monday) { + + if (monday == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyWeek; + } else { + return DateTimeFormatUtil.toIsoWeekTimex(monday); + } + } + + public static String generateWeekTimex(int weekNum) { + return "W" + String.format("%02d", weekNum); + } + + public static String generateWeekendTimex() { + return generateWeekendTimex(null); + } + + public static String generateWeekendTimex(LocalDateTime date) { + if (date == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyWeek + Constants.DateTimexConnector + Constants.TimexWeekend; + } else { + return DateTimeFormatUtil.toIsoWeekTimex(date) + Constants.DateTimexConnector + Constants.TimexWeekend; + } + } + + public static String generateMonthTimex() { + return generateMonthTimex(null); + } + + public static String generateMonthTimex(LocalDateTime date) { + if (date == null) { + return Constants.TimexFuzzyYear + Constants.DateTimexConnector + Constants.TimexFuzzyMonth; + } else { + return String.format("%04d-%02d", date.getYear(), date.getMonthValue()); + } + } + + public static String generateYearTimex(int year) { + return DateTimeFormatUtil.luisDate(year); + } + + public static String generateYearTimex(int year, String specialYearPrefixes) { + String yearStr = DateTimeFormatUtil.luisDate(year); + return String.format("%s%s", specialYearPrefixes, yearStr); + } + + public static String generateDurationTimex(double number, String unitStr, boolean isLessThanDay) { + if (!Constants.TimexBusinessDay.equals(unitStr)) { + if (Constants.DECADE_UNIT.equals(unitStr)) { + number = number * 10; + unitStr = Constants.TimexYear; + } else if (Constants.FORTNIGHT_UNIT.equals(unitStr)) { + number = number * 2; + unitStr = Constants.TimexWeek; + } else { + unitStr = unitStr.substring(0, 1); + } + } + + return String.format("%s%s%s%s", + Constants.GeneralPeriodPrefix, + isLessThanDay ? Constants.TimeTimexPrefix : "", + StringUtility.format(number), + unitStr); + } + + public static DatePeriodTimexType getDatePeriodTimexType(String durationTimex) { + DatePeriodTimexType result; + + String minimumUnit = durationTimex.substring(durationTimex.length() - 1); + + switch (minimumUnit) { + case Constants.TimexYear: + result = DatePeriodTimexType.ByYear; + break; + case Constants.TimexMonth: + result = DatePeriodTimexType.ByMonth; + break; + case Constants.TimexWeek: + result = DatePeriodTimexType.ByWeek; + break; + default: + result = DatePeriodTimexType.ByDay; + break; + } + + return result; + } + + public static LocalDateTime offsetDateObject(LocalDateTime date, int offset, DatePeriodTimexType timexType) { + LocalDateTime result; + + switch (timexType) { + case ByYear: + result = date.plusYears(offset); + break; + case ByMonth: + result = date.plusMonths(offset); + break; + case ByWeek: + result = date.plusDays(7 * offset); + break; + case ByDay: + result = date.plusDays(offset); + break; + default: + result = date; + break; + } + + return result; + } + + public static TimeOfDayResolutionResult parseTimeOfDay(String tod) { + switch (tod) { + case Constants.EarlyMorning: + return new TimeOfDayResolutionResult(Constants.EarlyMorning, 4, 8, 0); + case Constants.Morning: + return new TimeOfDayResolutionResult(Constants.Morning, 8, 12, 0); + case Constants.Afternoon: + return new TimeOfDayResolutionResult(Constants.Afternoon, 12, 16, 0); + case Constants.Evening: + return new TimeOfDayResolutionResult(Constants.Evening, 16, 20, 0); + case Constants.Daytime: + return new TimeOfDayResolutionResult(Constants.Daytime, 8, 18, 0); + case Constants.BusinessHour: + return new TimeOfDayResolutionResult(Constants.BusinessHour, 8, 18, 0); + case Constants.Night: + return new TimeOfDayResolutionResult(Constants.Night, 20, 23, 59); + default: + return new TimeOfDayResolutionResult(); + } + } + + public static String combineDateAndTimeTimex(String dateTimex, String timeTimex) { + return dateTimex + timeTimex; + } + + public static String generateWeekOfYearTimex(int year, int weekNum) { + String weekTimex = generateWeekTimex(weekNum); + String yearTimex = DateTimeFormatUtil.luisDate(year); + + return yearTimex + "-" + weekTimex; + } + + public static String generateWeekOfMonthTimex(int year, int month, int weekNum) { + String weekTimex = generateWeekTimex(weekNum); + String monthTimex = DateTimeFormatUtil.luisDate(year, month); + + return monthTimex + "-" + weekTimex; + } + + public static String generateDateTimePeriodTimex(String beginTimex, String endTimex, String durationTimex) { + return "(" + beginTimex + "," + endTimex + "," + durationTimex + ")"; + } + + public static RangeTimexComponents getRangeTimexComponents(String rangeTimex) { + rangeTimex = rangeTimex.replace("(", "").replace(")", ""); + String[] components = rangeTimex.split(","); + RangeTimexComponents result = new RangeTimexComponents(); + + if (components.length == 3) { + result.beginTimex = components[0]; + result.endTimex = components[1]; + result.durationTimex = components[2]; + result.isValid = true; + } + + return result; + } + + public static boolean isRangeTimex(String timex) { + return !StringUtility.isNullOrEmpty(timex) && timex.startsWith("("); + } + + public static String setTimexWithContext(String timex, DateContext context) { + return timex.replace(Constants.TimexFuzzyYear, String.format("%04d", context.getYear())); + } + + public static boolean hasDoubleTimex(String comment) { + return comment.equals(Constants.Comment_DoubleTimex); + } + + public static String mergeTimexAlternatives(String timex1, String timex2) { + if (timex1.equals(timex2)) { + return timex1; + } + return timex1 + Constants.CompositeTimexDelimiter + timex2; + } + + public static Map processDoubleTimex(Map resolutionDic, String futureKey, String pastKey, String originTimex) { + String[] timexes = originTimex.split(Constants.CompositeTimexSplit); + + if (!resolutionDic.containsKey(futureKey) || !resolutionDic.containsKey(pastKey) || timexes.length != 2) { + return resolutionDic; + } + + HashMap futureResolution = (HashMap)resolutionDic.get(futureKey); + HashMap pastResolution = (HashMap)resolutionDic.get(pastKey); + futureResolution.put(DateTimeResolutionKey.Timex, timexes[0]); + pastResolution.put(DateTimeResolutionKey.Timex, timexes[1]); + return resolutionDic; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java new file mode 100644 index 000000000..e5a14f78e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/Token.java @@ -0,0 +1,87 @@ +package com.microsoft.recognizers.text.datetime.utilities; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.Metadata; + +import java.util.ArrayList; +import java.util.List; + +public class Token { + private final int start; + private final int end; + private final Metadata metadata; + + public Token(int start, int end, Metadata metadata) { + this.start = start; + this.end = end; + this.metadata = metadata; + } + + public Token(int start, int end) { + this.start = start; + this.end = end; + this.metadata = null; + } + + public int getStart() { + return start; + } + + public int getEnd() { + return end; + } + + public int getLength() { + return end < start ? 0 : end - start; + } + + public static List mergeAllTokens(List tokens, String text, String extractorName) { + List result = new ArrayList<>(); + List mergedTokens = new ArrayList<>(); + + tokens.sort((o1, o2) -> { + if (o1.start != o2.start) { + return o1.start - o2.start; + } + + return o2.getLength() - o1.getLength(); + }); + + for (Token token : tokens) { + if (token != null) { + boolean bAdd = true; + for (int i = 0; i < mergedTokens.size() && bAdd; i++) { + // It is included in one of the current tokens + if (token.start >= mergedTokens.get(i).start && token.end <= mergedTokens.get(i).end) { + bAdd = false; + } + + // If it contains overlaps + if (token.start > mergedTokens.get(i).start && token.start < mergedTokens.get(i).end) { + bAdd = false; + } + + // It includes one of the tokens and should replace the included one + if (token.start <= mergedTokens.get(i).start && token.end >= mergedTokens.get(i).end) { + bAdd = false; + mergedTokens.set(i, token); + } + } + + if (bAdd) { + mergedTokens.add(token); + } + } + } + + for (Token token : mergedTokens) { + String substring = text.substring(token.start, token.end); + + ExtractResult er = new ExtractResult(token.start, token.getLength(), substring, extractorName, null, token.metadata); + + result.add(er); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java new file mode 100644 index 000000000..5319445bd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AaNode.java @@ -0,0 +1,53 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashMap; +import java.util.Iterator; + +public class AaNode extends Node { + T word; + int depth; + AaNode parent; + AaNode fail; + + public AaNode(T word, int depth, AaNode parent) { + this.word = word; + this.depth = depth; + this.parent = parent; + this.fail = null; + } + + public AaNode(T word, int depth) { + this(word, depth, null); + } + + public AaNode() { + this(null, 0); + } + + AaNode get(T c) { + return children != null && children.containsKey(c) ? (AaNode)children.get(c) : null; + } + + void put(T c, AaNode value) { + if (children == null) { + children = new HashMap<>(); + } + + children.put(c, value); + } + + @Override + Iterator getEnumerator() { + return children != null ? children.values().stream().map(x -> (AaNode)x).iterator() : null; + } + + @Override + Iterable getIterable() { + return children != null ? (Iterable)children.values().stream().map(x -> (AaNode)x) : null; + } + + @Override + public String toString() { + return word.toString(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java new file mode 100644 index 000000000..6fb2267a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AbstractMatcher.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public abstract class AbstractMatcher implements IMatcher { + abstract void insert(Iterable value, String id); + + protected void batchInsert(List> values, String[] ids) { + if (values.size() != ids.length) { + throw new IllegalArgumentException(); + } + + for (int i = 0; i < values.size(); i++) { + insert(values.get(i), ids[i]); + } + } + + boolean isMatch(Iterable queryText) { + return find(queryText) != null; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java new file mode 100644 index 000000000..b1c0f054c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/AcAutomation.java @@ -0,0 +1,89 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.stream.Stream; + +public class AcAutomation extends AbstractMatcher { + protected final AaNode root; + + public AcAutomation() { + root = new AaNode<>(); + } + + @Override + void insert(Iterable value, String id) { + AaNode node = root; + int i = 0; + for (T item : value) { + AaNode child = node.get(item); + if (child == null) { + node.put(item, new AaNode<>(item, i, node)); + child = node.get(item); + } + + node = child; + i++; + } + + node.addValue(id); + } + + @Override + public void init(List> values, String[] ids) { + this.batchInsert(values, ids); + Queue> queue = new LinkedList<>(); + queue.offer(root); + + while (!queue.isEmpty()) { + AaNode node = queue.peek(); + + if (node.children != null) { + for (Object item : node.getIterable()) { + queue.offer((AaNode)item); + } + } + + if (node == root) { + root.fail = root; + continue; + } + + AaNode fail = node.parent.fail; + + while (fail.get(node.word) == null && fail != root) { + fail = fail.fail; + } + + node.fail = fail.get(node.word) != null ? fail.get(node.word) : root; + node.fail = node.fail == node ? root : node.fail; + } + } + + @Override + public Iterable> find(Iterable queryText) { + AaNode node = root; + int i = 0; + List> result = new ArrayList<>(); + + for (T c : queryText) { + while (node.get(c) == null && node != root) { + node = node.fail; + } + + node = node.get(c) == null ? node.get(c) : root; + + for (AaNode t = node; t != root ; t = t.fail) { + if (t.getEnd()) { + result.add(new MatchResult<>(i - t.depth, t.depth + 1, t.values)); + } + } + + i++; + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java new file mode 100644 index 000000000..76b0de58f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/IMatcher.java @@ -0,0 +1,10 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public interface IMatcher { + void init(List> values, String[] ids); + + Iterable> find(Iterable queryText); + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java new file mode 100644 index 000000000..cbdb5c553 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/ITokenizer.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.List; + +public interface ITokenizer { + List tokenize(String input); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java new file mode 100644 index 000000000..6d577691a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchResult.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashSet; +import java.util.Set; + +public class MatchResult { + private int start; + private int length; + private T text; + private Set canonicalValues; + + public MatchResult(int start, int lenght, Set canonicalValues, T text) { + this.start = start; + this.length = lenght; + this.canonicalValues = canonicalValues; + this.text = text; + } + + public MatchResult(int start, int length, Set canonicalValues) { + this(start, length, new HashSet<>(), null); + } + + public MatchResult(int start, int length) { + this(start, length, new HashSet<>()); + } + + public MatchResult() { + this(0, 0); + } + + public int getStart() { + return start; + } + + public void setStart(int start) { + this.start = start; + } + + public int getLength() { + return length; + } + + public void setLength(int length) { + this.length = length; + } + + public T getText() { + return text; + } + + public void setText(T text) { + this.text = text; + } + + public Set getCanonicalValues() { + return canonicalValues; + } + + public void setCanonicalValues(Set canonicalValues) { + this.canonicalValues = canonicalValues; + } + + public int getEnd() { + return getStart() + getLength(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java new file mode 100644 index 000000000..b6c3b2e11 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/MatchStrategy.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.matcher; + +public enum MatchStrategy { + AcAutomaton, + TrieTree +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java new file mode 100644 index 000000000..6be935156 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Node.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Map; + +public class Node { + Iterator getEnumerator() { + return children != null ? children.values().iterator() : null; + } + + Iterable getIterable() { + return children != null ? children.values() : null; + } + + HashSet values; + Map> children; + + boolean getEnd() { + return this.values != null && !values.isEmpty(); + } + + Node get(T c) { + return children != null && children.containsKey(c) ? children.get(c) : null; + } + + void put(T c, Node value) { + if (children == null) { + children = new HashMap<>(); + } + + children.put(c, value); + } + + void addValue(String value) { + if (values == null) { + values = new HashSet<>(); + } + + values.add(value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java new file mode 100644 index 000000000..bd3e65437 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/NumberWithUnitTokenizer.java @@ -0,0 +1,86 @@ +package com.microsoft.recognizers.text.matcher; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +public class NumberWithUnitTokenizer extends SimpleTokenizer { + private static final HashSet specialTokenCharacters = new HashSet(Arrays.asList('$')); + + /* The main difference between this strategy and SimpleTokenizer is for cases like + * 'Bob's $ 100 cash'. 's' and '$' are independent tokens in SimpleTokenizer. + * However, 's$' will return these two tokens too. Here, we let 's$' be a single + * token to avoid the false positive. + * Besides, letters and digits can't be mixed as a token. For cases like '200ml'. + * '200ml' will be a token in SimpleTokenizer. Here, '200' and 'ml' are independent tokens. + */ + + @Override + public List tokenize(String input) { + List tokens = new ArrayList<>(); + + if (StringUtility.isNullOrEmpty(input)) { + return tokens; + } + + boolean inToken = false; + int tokenStart = 0; + char[] chars = input.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + if (Character.isWhitespace(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + } else if ((!specialTokenCharacters.contains(c) && !Character.isLetterOrDigit(c)) || isChinese(c) || isJapanese(c)) { + // Non-splittable currency units (as "$") are treated as regular letters. For instance, 'us$' should be a single token + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + + tokens.add(new Token(i, 1, input.substring(i, i + 1))); + } else { + if (inToken && i > 0) { + char preChar = chars[i - 1]; + if (isSplittableUnit(c, preChar)) { + // Split if letters or non-splittable units are adjacent with digits. + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + tokenStart = i; + } + } + + if (!inToken) { + tokenStart = i; + inToken = true; + } + } + } + + if (inToken) { + tokens.add(new Token(tokenStart, chars.length - tokenStart, input.substring(tokenStart, chars.length))); + } + + return tokens; + } + + private boolean isSplittableUnit(char curChar, char preChar) { + // To handle cases like '200ml', digits and letters cannot be mixed as a single token. '200ml' will be tokenized to '200' and 'ml'. + if ((Character.isLetter(curChar) && Character.isDigit(preChar)) || (Character.isDigit(curChar) && Character.isLetter(preChar))) { + return true; + } + + // Non-splittable currency units can't be mixed with digits. For example, '$100' or '100$' will be tokenized to '$' and '100', + // '1$50' will be tokenized to '1', '$', and '50' + if ((Character.isDigit(curChar) && specialTokenCharacters.contains(preChar)) || (specialTokenCharacters.contains(curChar) && Character.isDigit(preChar))) { + return true; + } + + // Non-splittable currency units adjacent with letters are treated as regular token characters. For instance, 's$' or 'u$d' are single tokens. + return false; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java new file mode 100644 index 000000000..7d836e394 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/SimpleTokenizer.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.matcher; + +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.ArrayList; +import java.util.List; + +public class SimpleTokenizer implements ITokenizer { + @Override + public List tokenize(String input) { + List tokens = new ArrayList<>(); + + if (StringUtility.isNullOrEmpty(input)) { + return tokens; + } + + boolean inToken = false; + int tokenStart = 0; + char[] chars = input.toCharArray(); + for (int i = 0; i < chars.length; i++) { + char c = chars[i]; + + if (Character.isWhitespace(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + } else if (!Character.isLetterOrDigit(c) || isCjk(c)) { + if (inToken) { + tokens.add(new Token(tokenStart, i - tokenStart, input.substring(tokenStart, i))); + inToken = false; + } + + tokens.add(new Token(i, 1, input.substring(i, i + 1))); + } else { + if (!inToken) { + tokenStart = i; + inToken = true; + } + } + } + + if (inToken) { + tokens.add(new Token(tokenStart, chars.length - tokenStart, input.substring(tokenStart))); + } + + return tokens; + } + + protected boolean isChinese(char c) { + int uc = (int)c; + + return (uc >= (int)0x4E00 && uc <= (int)0x9FBF) || (uc >= (int)0x3400 && uc <= (int)0x4DBF); + } + + protected boolean isJapanese(char c) { + int uc = (int)c; + + return (uc >= 0x3040 && uc <= 0x309F) || + (uc >= 0x30A0 && uc <= (int)0x30FF) || + (uc >= (int)0xFF66 && uc <= (int)0xFF9D); + } + + protected boolean isKorean(char c) { + int uc = (int)c; + + return (uc >= (int)0xAC00 && uc <= (int)0xD7AF) || + (uc >= (int)0x1100 && uc <= (int)0x11FF) || + (uc >= (int)0x3130 && uc <= (int)0x318F) || + (uc >= (int)0xFFB0 && uc <= (int)0xFFDC); + } + + // Check the character is Chinese/Japanese/Korean. + // For those languages which are not using whitespace delimited symbol, we only simply tokenize the sentence by each single character. + private boolean isCjk(char c) { + return isChinese(c) || isJapanese(c) || isKorean(c); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java new file mode 100644 index 000000000..10d651d4d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/StringMatcher.java @@ -0,0 +1,99 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class StringMatcher { + private final ITokenizer tokenizer; + private final IMatcher matcher; + + public StringMatcher(MatchStrategy strategy, ITokenizer tokenizer) { + this.tokenizer = tokenizer != null ? tokenizer : new SimpleTokenizer(); + switch (strategy) { + case AcAutomaton: + matcher = new AcAutomation<>(); + break; + case TrieTree: + matcher = new TrieTree<>(); + break; + default: + throw new IllegalArgumentException(); + } + } + + public StringMatcher(MatchStrategy strategy) { + this(strategy, null); + } + + public StringMatcher(ITokenizer tokenizer) { + this(MatchStrategy.TrieTree, tokenizer); + } + + public StringMatcher() { + this(MatchStrategy.TrieTree, null); + } + + public void init(Iterable values) { + init(values, StreamSupport.stream(values.spliterator(), false).toArray(size -> new String[size])); + } + + void init(Iterable values, String[] ids) { + List> tokenizedValues = getTokenizedText(values); + init(tokenizedValues, ids); + } + + void init(Map> valuesMap) { + ArrayList values = new ArrayList<>(); + ArrayList ids = new ArrayList<>(); + + for (Map.Entry> item : valuesMap.entrySet()) { + String id = item.getKey(); + for (String value : item.getValue()) { + values.add(value); + ids.add(id); + } + } + + List> tokenizedValues = getTokenizedText(values); + init(tokenizedValues, ids.toArray(new String[0])); + } + + void init(List> tokenizedValues, String[] ids) { + matcher.init(tokenizedValues, ids); + } + + private List> getTokenizedText(Iterable values) { + List> result = new ArrayList<>(); + for (String value: values) { + result.add(tokenizer.tokenize(value).stream().map(i -> i.text).collect(Collectors.toCollection(ArrayList::new))); + } + + return result; + } + + public Iterable> find(Iterable tokenizedQuery) { + return matcher.find(tokenizedQuery); + } + + public Iterable> find(String queryText) { + List queryTokens = tokenizer.tokenize(queryText); + Iterable tokenizedQueryText = queryTokens.stream().map(t -> t.text).collect(Collectors.toCollection(ArrayList::new)); + + List> result = new ArrayList<>(); + for (MatchResult r : find(tokenizedQueryText)) { + Token startToken = queryTokens.get(r.getStart()); + Token endToken = queryTokens.get(r.getStart() + r.getLength() - 1); + int start = startToken.getStart(); + int length = endToken.getEnd() - startToken.getStart(); + String rtext = queryText.substring(start, start + length); + + result.add(new MatchResult(start, length, r.getCanonicalValues(), rtext)); + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java new file mode 100644 index 000000000..d7ca62808 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/Token.java @@ -0,0 +1,30 @@ +package com.microsoft.recognizers.text.matcher; + +public class Token { + private final int start; + private final int length; + + String text; + + public Token(int start, int length, String text) { + this.start = start; + this.length = length; + this.text = text; + } + + public Token(int start, int length) { + this(start, length, null); + } + + int getStart() { + return start; + } + + int getLength() { + return length; + } + + int getEnd() { + return start + length; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java new file mode 100644 index 000000000..adc663350 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/matcher/TrieTree.java @@ -0,0 +1,67 @@ +package com.microsoft.recognizers.text.matcher; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Iterator; +import java.util.List; + +public class TrieTree extends AbstractMatcher { + protected final Node root; + + public TrieTree() { + root = new Node<>(); + } + + @Override + void insert(Iterable value, String id) { + Node node = root; + + for (T item : value) { + Node child = node.get(item); + + if (child == null) { + node.put(item, new Node<>()); + child = node.get(item); + } + + node = child; + } + + node.addValue(id); + } + + @Override + public void init(List> values, String[] ids) { + batchInsert(values, ids); + } + + @Override + public Iterable> find(Iterable queryText) { + List> result = new ArrayList<>(); + + ArrayList queryArray = new ArrayList<>(); + queryText.iterator().forEachRemaining(queryArray::add); + + for (int i = 0; i < queryArray.size(); i++) { + Node node = root; + for (int j = i; j <= queryArray.size(); j++) { + if (node.getEnd()) { + result.add(new MatchResult<>(i, j - i, node.values)); + } + + if (j == queryArray.size()) { + break; + } + + T text = queryArray.get(j); + if (node.get(text) == null) { + break; + } + + node = node.get(text); + } + } + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java new file mode 100644 index 000000000..1f4458502 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/Constants.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.number; + +public class Constants { + public static final String SYS_NUM_CARDINAL = "builtin.num.cardinal"; + public static final String SYS_NUM_DOUBLE = "builtin.num.double"; + public static final String SYS_NUM_FRACTION = "builtin.num.fraction"; + public static final String SYS_NUM_INTEGER = "builtin.num.integer"; + public static final String SYS_NUM = "builtin.num"; + public static final String SYS_NUM_ORDINAL = "builtin.num.ordinal"; + public static final String SYS_NUM_PERCENTAGE = "builtin.num.percentage"; + public static final String SYS_NUMRANGE = "builtin.num.numberrange"; + + // Model type name + public static final String MODEL_NUMBER = "number"; + public static final String MODEL_NUMBERRANGE = "numberrange"; + public static final String MODEL_ORDINAL = "ordinal"; + public static final String MODEL_PERCENTAGE = "percentage"; + + // NARROW NO-BREAK SPACE + public static final char NO_BREAK_SPACE = '\u202f'; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java new file mode 100644 index 000000000..d801a8236 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/LongFormatType.java @@ -0,0 +1,53 @@ +package com.microsoft.recognizers.text.number; + +public class LongFormatType { + + // Reference Value : 1234567.89 + + // 1,234,567 + public static LongFormatType IntegerNumComma = new LongFormatType(',', '\0'); + + // 1.234.567 + public static LongFormatType IntegerNumDot = new LongFormatType('.', '\0'); + + // 1 234 567 + public static LongFormatType IntegerNumBlank = new LongFormatType(' ', '\0'); + + // 1 234 567 + public static LongFormatType IntegerNumNoBreakSpace = new LongFormatType(Constants.NO_BREAK_SPACE, '\0'); + + // 1'234'567 + public static LongFormatType IntegerNumQuote = new LongFormatType('\'', '\0'); + + // 1,234,567.89 + public static LongFormatType DoubleNumCommaDot = new LongFormatType(',', '.'); + + // 1,234,567·89 + public static LongFormatType DoubleNumCommaCdot = new LongFormatType(',', '·'); + + // 1 234 567,89 + public static LongFormatType DoubleNumBlankComma = new LongFormatType(' ', ','); + + // 1 234 567,89 + public static LongFormatType DoubleNumNoBreakSpaceComma = new LongFormatType(Constants.NO_BREAK_SPACE, ','); + + // 1 234 567.89 + public static LongFormatType DoubleNumBlankDot = new LongFormatType(' ', '.'); + + // 1 234 567.89 + public static LongFormatType DoubleNumNoBreakSpaceDot = new LongFormatType(Constants.NO_BREAK_SPACE, '.'); + + // 1.234.567,89 + public static LongFormatType DoubleNumDotComma = new LongFormatType('.', ','); + + // 1'234'567,89 + public static LongFormatType DoubleNumQuoteComma = new LongFormatType('\'', ','); + + public final char decimalsMark; + public final char thousandsMark; + + public LongFormatType(char thousandsMark, char decimalsMark) { + this.thousandsMark = thousandsMark; + this.decimalsMark = decimalsMark; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java new file mode 100644 index 000000000..08fac2c35 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberMode.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.number; + +public enum NumberMode { + //Default is for unit and datetime + Default, + //Add 67.5 billion & million support. + Currency, + //Don't extract number from cases like 16ml + PureNumber, + // Unit is for unit + Unit +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java new file mode 100644 index 000000000..e3f2e6cfa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberOptions.java @@ -0,0 +1,16 @@ +package com.microsoft.recognizers.text.number; + +public enum NumberOptions { + None(0), + PercentageMode(1); + + private final int value; + + NumberOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java new file mode 100644 index 000000000..d2af53c68 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRangeConstants.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.number; + +public abstract class NumberRangeConstants { + // Number range regex type + public static final String TWONUM = "TwoNum"; + public static final String TWONUMBETWEEN = "TwoNumBetween"; + public static final String TWONUMTILL = "TwoNumTill"; + public static final String MORE = "More"; + public static final String LESS = "Less"; + public static final String EQUAL = "Equal"; + + // Brackets and comma for number range resolution value + public static final char LEFT_OPEN = '('; + public static final char RIGHT_OPEN = ')'; + public static final char LEFT_CLOSED = '['; + public static final char RIGHT_CLOSED = ']'; + public static final char INTERVAL_SEPARATOR = ','; + + // Invalid number + public static final int INVALID_NUM = -1; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java new file mode 100644 index 000000000..2b76f483b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/NumberRecognizer.java @@ -0,0 +1,217 @@ +package com.microsoft.recognizers.text.number; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.german.parsers.GermanNumberParserConfiguration; +import com.microsoft.recognizers.text.number.models.NumberModel; +import com.microsoft.recognizers.text.number.models.NumberRangeModel; +import com.microsoft.recognizers.text.number.models.OrdinalModel; +import com.microsoft.recognizers.text.number.models.PercentModel; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.parsers.BaseNumberRangeParser; +import com.microsoft.recognizers.text.number.portuguese.parsers.PortugueseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; + +import java.util.List; +import java.util.function.Function; + +public class NumberRecognizer extends Recognizer { + + public NumberRecognizer() { + this(null, NumberOptions.None, true); + } + + public NumberRecognizer(String culture) { + this(culture, NumberOptions.None, false); + } + + public NumberRecognizer(NumberOptions numberOptions) { + this(null, numberOptions, true); + } + + public NumberRecognizer(NumberOptions numberOptions, boolean lazyInitialization) { + this(null, numberOptions, lazyInitialization); + } + + public NumberRecognizer(String culture, NumberOptions numberOptions, boolean lazyInitialization) { + super(culture, numberOptions, lazyInitialization); + } + + //region Helper methods for less verbosity + public NumberModel getNumberModel() { + return getNumberModel(null, true); + } + + public NumberModel getNumberModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(NumberModel.class, culture, fallbackToDefaultCulture); + } + + public OrdinalModel getOrdinalModel() { + return getOrdinalModel(null, true); + } + + public OrdinalModel getOrdinalModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(OrdinalModel.class, culture, fallbackToDefaultCulture); + } + + public PercentModel getPercentageModel() { + return getPercentageModel(null, true); + } + + public PercentModel getPercentageModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(PercentModel.class, culture, fallbackToDefaultCulture); + } + + public NumberRangeModel getNumberRangeModel() { + return getNumberRangeModel(null, true); + } + + public NumberRangeModel getNumberRangeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(NumberRangeModel.class, culture, fallbackToDefaultCulture); + } + + public static List recognizeNumber(String query, String culture) { + return recognizeNumber(query, culture, NumberOptions.None, true); + } + + public static List recognizeNumber(String query, String culture, NumberOptions options) { + return recognizeNumber(query, culture, options, true); + } + + public static List recognizeNumber(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getNumberModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeOrdinal(String query, String culture) { + return recognizeOrdinal(query, culture, NumberOptions.None, true); + } + + public static List recognizeOrdinal(String query, String culture, NumberOptions options) { + return recognizeOrdinal(query, culture, options, true); + } + + public static List recognizeOrdinal(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getOrdinalModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizePercentage(String query, String culture) { + return recognizePercentage(query, culture, NumberOptions.None, true); + } + + public static List recognizePercentage(String query, String culture, NumberOptions options) { + return recognizePercentage(query, culture, options, true); + } + + public static List recognizePercentage(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getPercentageModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeNumberRange(String query, String culture) { + return recognizeNumberRange(query, culture, NumberOptions.None, true); + } + + public static List recognizeNumberRange(String query, String culture, NumberOptions options) { + return recognizeNumberRange(query, culture, options, true); + } + + public static List recognizeNumberRange(String query, String culture, NumberOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel((NumberRecognizer recognizer) -> recognizer.getNumberRangeModel(culture, fallbackToDefaultCulture), query, options); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, NumberOptions options) { + NumberRecognizer recognizer = new NumberRecognizer(options); + IModel model = getModelFun.apply(recognizer); + return model.parse(query); + } + + @Override + protected void initializeConfiguration() { + //region English + registerModel(NumberModel.class, Culture.English, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new EnglishNumberParserConfiguration(options)), + com.microsoft.recognizers.text.number.english.extractors.NumberExtractor.getInstance(NumberMode.PureNumber, options))); + registerModel(OrdinalModel.class, Culture.English, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new EnglishNumberParserConfiguration(options)), + com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor.getInstance())); + registerModel(PercentModel.class, Culture.English, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new EnglishNumberParserConfiguration(options)), + new com.microsoft.recognizers.text.number.english.extractors.PercentageExtractor(options))); + registerModel(NumberRangeModel.class, Culture.English, (options) -> new NumberRangeModel( + new BaseNumberRangeParser(new EnglishNumberRangeParserConfiguration()), + new com.microsoft.recognizers.text.number.english.extractors.NumberRangeExtractor())); + + //endregion + + //region Spanish + registerModel(NumberModel.class, Culture.Spanish, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new SpanishNumberParserConfiguration()), + com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.Spanish, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new SpanishNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.spanish.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Spanish, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new SpanishNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.spanish.extractors.PercentageExtractor())); + //endregion + + //region Portuguese + registerModel(NumberModel.class, Culture.Portuguese, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new PortugueseNumberParserConfiguration()), + com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.Portuguese, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new PortugueseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.portuguese.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Portuguese, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new PortugueseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.portuguese.extractors.PercentageExtractor())); + //endregion + + //region French + registerModel(NumberModel.class, Culture.French, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new FrenchNumberParserConfiguration()), + com.microsoft.recognizers.text.number.french.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.French, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new FrenchNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.french.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.French, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new FrenchNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.french.extractors.PercentageExtractor())); + //endregion + + //region German + registerModel(NumberModel.class, Culture.German, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new GermanNumberParserConfiguration()), + com.microsoft.recognizers.text.number.german.extractors.NumberExtractor.getInstance(NumberMode.PureNumber))); + registerModel(OrdinalModel.class, Culture.German, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new GermanNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.german.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.German, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new GermanNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.german.extractors.PercentageExtractor())); + //endregion + + //region Chinese + registerModel(NumberModel.class, Culture.Chinese, (options) -> new NumberModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor())); + registerModel(OrdinalModel.class, Culture.Chinese, (options) -> new OrdinalModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Ordinal, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.OrdinalExtractor())); + registerModel(PercentModel.class, Culture.Chinese, (options) -> new PercentModel( + AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Percentage, new ChineseNumberParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.PercentageExtractor())); + registerModel(NumberRangeModel.class, Culture.Chinese, (options) -> new NumberRangeModel( + new BaseNumberRangeParser(new ChineseNumberRangeParserConfiguration()), + new com.microsoft.recognizers.text.number.chinese.extractors.NumberRangeExtractor())); + //endregion + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java new file mode 100644 index 000000000..4ca896d2b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/ChineseNumberExtractorMode.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.number.chinese; + +/** + * These modes only apply to ChineseNumberExtractor. + * The default more urilizes an allow list to avoid extracting numbers in ambiguous/undesired combinations of Chinese ideograms. + * ExtractAll mode is to be used in cases where extraction should be more aggressive (e.g. in Units extraction). + */ +public enum ChineseNumberExtractorMode { + /** + * Number extraction with an allow list that filters what numbers to extract. + */ + Default, + + /** + * Extract all number-related terms aggressively. + */ + ExtractAll +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java new file mode 100644 index 000000000..016fdc02c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/CardinalExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + public CardinalExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public CardinalExtractor(ChineseNumberExtractorMode mode) { + HashMap builder = new HashMap<>(); + + IntegerExtractor intExtractChs = new IntegerExtractor(mode); + builder.putAll(intExtractChs.getRegexes()); + + DoubleExtractor douExtractorChs = new DoubleExtractor(); + builder.putAll(douExtractorChs.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java new file mode 100644 index 000000000..7f6f2c642 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + // (-)2.5, can avoid cases like ip address xx.xx.xx.xx + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleSpecialsCharsWithNegatives, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + //(-).2 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleDoubleSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + // 1.0 K + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + //15.2万 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleWithThousandsRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + ChineseNumeric.LangMarker); + //四十五点三三 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + ChineseNumeric.LangMarker); + // 2e6, 21.2e0 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + //2^5 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DoubleScientificNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java new file mode 100644 index 000000000..f6b1223c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/FractionExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor() { + HashMap builder = new HashMap<>(); + + // -4 5/2, 4 6/3 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionNotationSpecialsCharsRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + // 8/3 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + //四分之六十五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.AllFractionNumber, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + ChineseNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java new file mode 100644 index 000000000..4217df048 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/IntegerExtractor.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import static com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(Default); + } + + public IntegerExtractor(ChineseNumberExtractorMode mode) { + HashMap builder = new HashMap<>(); + + // 123456, -123456 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsChars, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //15k, 16 G + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsCharsWithSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //1,234, 2,332,111 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.DottedNumbersSpecialsChar, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + //半百 半打 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersWithHalfDozen, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + //一打 五十打 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersWithDozen, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + + switch (mode) { + case Default: + // 一百五十五, 负一亿三百二十二. + // Uses an allow list to avoid extracting "四" from "四川" + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.NumbersWithAllowListRegex, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + break; + + case ExtractAll: + // 一百五十五, 负一亿三百二十二, "四" from "四川". + // Uses no allow lists and extracts all potential integers (useful in Units, for example). + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.NumbersAggressiveRegex, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + ChineseNumeric.LangMarker); + break; + + default: + break; + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java new file mode 100644 index 000000000..b61bd980c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberExtractor.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + public NumberExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public NumberExtractor(ChineseNumberExtractorMode mode) { + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtractChs = new CardinalExtractor(mode); + builder.putAll(cardExtractChs.getRegexes()); + + // Add Fraction + FractionExtractor fracExtractChs = new FractionExtractor(); + builder.putAll(fracExtractChs.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + + for (Map.Entry pair : ChineseNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java new file mode 100644 index 000000000..d1cc5725d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/NumberRangeExtractor.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.extractors.BaseNumberRangeExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParser; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberRangeExtractor extends BaseNumberRangeExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUMRANGE; + } + + public NumberRangeExtractor() { + this(ChineseNumberExtractorMode.Default); + } + + public NumberRangeExtractor(ChineseNumberExtractorMode mode) { + + super(new NumberExtractor(), new OrdinalExtractor(), new BaseCJKNumberParser(new ChineseNumberParserConfiguration())); + + HashMap builder = new HashMap<>(); + + // 在...和...之间 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.TWONUMBETWEEN); + + // 大于...小于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + + // 小于...大于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + + // ...到/至..., 20~30 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.TwoNumberRangeRegex4, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMTILL); + + // 大于/多于/高于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // 比...大/高/多 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // ...多/以上/之上 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeMoreRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + + // 小于/少于/低于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // 比...小/低/少 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // .../以下/之下 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeLessRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + + // 等于... + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.OneNumberRangeEqualRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.EQUAL); + + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..2bcd86e93 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/OrdinalExtractor.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + //第一百五十四 + builder.put(Pattern.compile(ChineseNumeric.OrdinalRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ordinal" + ChineseNumeric.LangMarker); + + //第2565, 第1234 + builder.put(Pattern.compile(ChineseNumeric.OrdinalNumbersRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ordinal" + ChineseNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java new file mode 100644 index 000000000..a3381f574 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/extractors/PercentageExtractor.java @@ -0,0 +1,99 @@ +package com.microsoft.recognizers.text.number.chinese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_PERCENTAGE; + } + + public PercentageExtractor() { + HashMap builder = new HashMap<>(); + + //二十个百分点, 四点五个百分点 + builder.put(RegExpUtility.getSafeRegExp(ChineseNumeric.PercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "Per" + ChineseNumeric.LangMarker); + + //百分之五十 百分之一点五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimplePercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "Per" + ChineseNumeric.LangMarker); + + //百分之56.2 百分之12 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之3,000 百分之1,123 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentageWithSeparatorRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之3.2 k + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12.56个百分点 0.4个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //15,123个百分点 111,111个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentageWithSeparatorRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12.1k个百分点 15.1k个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FractionPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之22 百分之120 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之15k + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //百分之1,111 百分之9,999 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleNumbersPercentagePointRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.IntegerPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //12k个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.IntegerPercentageWithMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //2,123个百分点 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersFractionPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //32.5% + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleIntegerPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerNum"); + + //2折 2.5折 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //三折 六点五折 七五折 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.FoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //5成 6成半 6成4 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //七成半 七成五 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //2成 2.5成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.NumbersSpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //三成 六点五成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SimpleSpecialsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + //打对折 半成 + builder.put(RegExpUtility.getSafeLookbehindRegExp(ChineseNumeric.SpecialsFoldsPercentageRegex, Pattern.UNICODE_CHARACTER_CLASS), "PerSpe"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java new file mode 100644 index 000000000..2a89909c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberParserConfiguration.java @@ -0,0 +1,82 @@ +package com.microsoft.recognizers.text.number.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.TreeMap; +import java.util.regex.Pattern; + +public class ChineseNumberParserConfiguration extends BaseCJKNumberParserConfiguration { + + public ChineseNumberParserConfiguration() { + super( + ChineseNumeric.LangMarker, + new CultureInfo(Culture.Chinese), + ChineseNumeric.CompoundNumberLanguage, + ChineseNumeric.MultiDecimalSeparatorCulture, + NumberOptions.None, + ChineseNumeric.NonDecimalSeparatorChar, + ChineseNumeric.DecimalSeparatorChar, + ChineseNumeric.FractionMarkerToken, + ChineseNumeric.HalfADozenText, + ChineseNumeric.WordSeparatorToken, + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyList(), + Collections.emptyMap(), + Collections.emptyMap(), + ChineseNumeric.RoundNumberMap, + null, + Pattern.compile(ChineseNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.NegativeNumberTermsRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + null, + ChineseNumeric.ZeroToNineMap, + ChineseNumeric.RoundNumberMapChar, + ChineseNumeric.FullToHalfMap, + new TreeMap(new Comparator() { + @Override + public int compare(String a, String b) { + return a.length() > b.length() ? 1 : -1; + } + }) { + { + putAll(ChineseNumeric.UnitMap); + } + }, + ChineseNumeric.TratoSimMap, + ChineseNumeric.RoundDirectList, + Pattern.compile(ChineseNumeric.FracSplitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DigitNumRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.SpeGetNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeRegExp(ChineseNumeric.PercentageRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.PointRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DoubleAndRoundRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.PairRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.DozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(ChineseNumeric.RoundNumberIntegerRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + ChineseNumeric.ZeroChar, + ChineseNumeric.TenChars, + ChineseNumeric.PairChar, + RegExpUtility.getSafeRegExp(ChineseNumeric.PercentageNumRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS) + ); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + return tokens; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java new file mode 100644 index 000000000..3671a4767 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/chinese/parsers/ChineseNumberRangeParserConfiguration.java @@ -0,0 +1,96 @@ +package com.microsoft.recognizers.text.number.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.chinese.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseCJKNumberParser; +import com.microsoft.recognizers.text.number.parsers.INumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.resources.ChineseNumeric; + +import java.util.regex.Pattern; + +public class ChineseNumberRangeParserConfiguration implements INumberRangeParserConfiguration { + + public final CultureInfo cultureInfo; + public final IExtractor numberExtractor; + public final IExtractor ordinalExtractor; + public final IParser numberParser; + public final Pattern moreOrEqual; + public final Pattern lessOrEqual; + public final Pattern moreOrEqualSuffix; + public final Pattern lessOrEqualSuffix; + public final Pattern moreOrEqualSeparate; + public final Pattern lessOrEqualSeparate; + + public ChineseNumberRangeParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public ChineseNumberRangeParserConfiguration(CultureInfo ci) { + this.cultureInfo = ci + ; + this.numberExtractor = new NumberExtractor(); + this.ordinalExtractor = new OrdinalExtractor(); + this.numberParser = new BaseCJKNumberParser(new ChineseNumberParserConfiguration()); + + this.moreOrEqual = Pattern.compile(ChineseNumeric.MoreOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqual = Pattern.compile(ChineseNumeric.LessOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSuffix = Pattern.compile(ChineseNumeric.MoreOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSuffix = Pattern.compile(ChineseNumeric.LessOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSeparate = Pattern.compile(ChineseNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSeparate = Pattern.compile(ChineseNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public IExtractor getNumberExtractor() { + return this.numberExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return this.ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return this.numberParser; + } + + @Override + public Pattern getMoreOrEqual() { + return this.moreOrEqual; + } + + @Override + public Pattern getLessOrEqual() { + return this.lessOrEqual; + } + + @Override + public Pattern getMoreOrEqualSuffix() { + return this.moreOrEqualSuffix; + } + + @Override + public Pattern getLessOrEqualSuffix() { + return this.lessOrEqualSuffix; + } + + @Override + public Pattern getMoreOrEqualSeparate() { + return this.moreOrEqualSeparate; + } + + @Override + public Pattern getLessOrEqualSeparate() { + return this.lessOrEqualSeparate; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java new file mode 100644 index 000000000..33b921949 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/CardinalExtractor.java @@ -0,0 +1,66 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.putAll(IntegerExtractor.getInstance(placeholder).getRegexes()); + builder.putAll(DoubleExtractor.getInstance(placeholder).getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java new file mode 100644 index 000000000..16cb600c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/DoubleExtractor.java @@ -0,0 +1,78 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static DoubleExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static DoubleExtractor getInstance(String placeholder) { + + if (!instances.containsKey(placeholder)) { + DoubleExtractor instance = new DoubleExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private DoubleExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(Pattern.compile(EnglishNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(Pattern.compile(EnglishNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleEng"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumCommaDot, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumBlankDot, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceDot, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java new file mode 100644 index 000000000..23fb2f9c8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/FractionExtractor.java @@ -0,0 +1,74 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap, FractionExtractor> instances = new ConcurrentHashMap<>(); + + public static FractionExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + FractionExtractor instance = new FractionExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private FractionExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + + HashMap builder = new HashMap<>(); + + builder.put(Pattern.compile(EnglishNumeric.FractionNotationWithSpacesRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(Pattern.compile(EnglishNumeric.FractionNotationRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(Pattern.compile(EnglishNumeric.FractionNounRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + builder.put(Pattern.compile(EnglishNumeric.FractionNounWithArticleRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + + if (mode != NumberMode.Unit) { + if ((options.ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + builder.put(Pattern.compile(EnglishNumeric.FractionPrepositionWithinPercentModeRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + } else { + builder.put(Pattern.compile(EnglishNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "FracEng"); + } + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java new file mode 100644 index 000000000..fb21cc9ce --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/IntegerExtractor.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static IntegerExtractor getInstance() { + return getInstance(EnglishNumeric.PlaceHolderDefault); + } + + public static IntegerExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + IntegerExtractor instance = new IntegerExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private IntegerExtractor() { + this(EnglishNumeric.PlaceHolderDefault); + } + + private IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(Pattern.compile(EnglishNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(Pattern.compile(EnglishNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerEng"); + builder.put(Pattern.compile(EnglishNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerEng"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumComma, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java new file mode 100644 index 000000000..7a4179b59 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberExtractor.java @@ -0,0 +1,116 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + private final Pattern negativeNumberTermsRegex; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.of(this.negativeNumberTermsRegex); + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + this.negativeNumberTermsRegex = Pattern.compile(EnglishNumeric.NegativeNumberTermsRegex + '$', Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardinalExtractor = null; + switch (mode) { + case PureNumber: + cardinalExtractor = CardinalExtractor.getInstance(EnglishNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardinalExtractor == null) { + cardinalExtractor = CardinalExtractor.getInstance(); + } + + builder.putAll(cardinalExtractor.getRegexes()); + + // Add Fraction + FractionExtractor fractionExtractor = FractionExtractor.getInstance(mode, this.options); + builder.putAll(fractionExtractor.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : EnglishNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java new file mode 100644 index 000000000..49f515883 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/NumberRangeExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.extractors.BaseNumberRangeExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class NumberRangeExtractor extends BaseNumberRangeExtractor { + + private final Map regexes; + + public NumberRangeExtractor() { + super(NumberExtractor.getInstance(), OrdinalExtractor.getInstance(), new BaseNumberParser(new EnglishNumberParserConfiguration())); + + HashMap builder = new HashMap<>(); + + // between...and... + builder.put(Pattern.compile(EnglishNumeric.TwoNumberRangeRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMBETWEEN); + // more than ... less than ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.TwoNumberRangeRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + // less than ... more than ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.TwoNumberRangeRegex3, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUM); + // from ... to/~/- ... + builder.put(Pattern.compile(EnglishNumeric.TwoNumberRangeRegex4, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.TWONUMTILL); + // more/greater/higher than ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeMoreRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + // 30 and/or greater/higher + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeMoreRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.MORE); + // less/smaller/lower than ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeLessRegex1, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + // 30 and/or less/smaller/lower + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeLessRegex2, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.LESS); + // equal to ... + builder.put(Pattern.compile(EnglishNumeric.OneNumberRangeEqualRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), NumberRangeConstants.EQUAL); + // equal to 30 or more than, larger than 30 or equal to ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.MORE); + // equal to 30 or less, smaller than 30 or equal ... + builder.put(RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + NumberRangeConstants.LESS); + + this.regexes = Collections.unmodifiableMap(builder); + } + + @Override + protected Map getRegexes() { + return this.regexes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..6c0979315 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/OrdinalExtractor.java @@ -0,0 +1,65 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + @Override + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + @Override + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static OrdinalExtractor getInstance() { + return getInstance(""); + } + + private static OrdinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + OrdinalExtractor instance = new OrdinalExtractor(); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(Pattern.compile(EnglishNumeric.OrdinalSuffixRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(Pattern.compile(EnglishNumeric.OrdinalNumericRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(Pattern.compile(EnglishNumeric.OrdinalEnglishRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdEng"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(EnglishNumeric.OrdinalRoundNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "OrdEng"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java new file mode 100644 index 000000000..1799b73c1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/extractors/PercentageExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.english.extractors; + +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + public NumberOptions getOptions() { + return options; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(NumberMode.Default, options)); + + this.options = options; + + Set builder = new HashSet<>(); + builder.add(EnglishNumeric.NumberWithSuffixPercentage); + builder.add(EnglishNumeric.NumberWithPrefixPercentage); + + if ((options.ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + builder.add(EnglishNumeric.FractionNumberWithSuffixPercentage); + builder.add(EnglishNumeric.NumberWithPrepositionPercentage); + } + + this.regexes = buildRegexes(builder); + } + + @Override + protected Set getRegexes() { + return this.regexes; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java new file mode 100644 index 000000000..67a9beb76 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberParserConfiguration.java @@ -0,0 +1,105 @@ +package com.microsoft.recognizers.text.number.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +public class EnglishNumberParserConfiguration extends BaseNumberParserConfiguration { + + public EnglishNumberParserConfiguration() { + this(NumberOptions.None); + } + + public EnglishNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.English), options); + } + + public EnglishNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + EnglishNumeric.LangMarker, + cultureInfo, + EnglishNumeric.CompoundNumberLanguage, + EnglishNumeric.MultiDecimalSeparatorCulture, + options, + EnglishNumeric.NonDecimalSeparatorChar, + EnglishNumeric.DecimalSeparatorChar, + EnglishNumeric.FractionMarkerToken, + EnglishNumeric.HalfADozenText, + EnglishNumeric.WordSeparatorToken, + EnglishNumeric.WrittenDecimalSeparatorTexts, + EnglishNumeric.WrittenGroupSeparatorTexts, + EnglishNumeric.WrittenIntegerSeparatorTexts, + EnglishNumeric.WrittenFractionSeparatorTexts, + EnglishNumeric.CardinalNumberMap, + EnglishNumeric.OrdinalNumberMap, + EnglishNumeric.RoundNumberMap, + Pattern.compile(EnglishNumeric.HalfADozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.NegativeNumberSignRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(EnglishNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + List words = new ArrayList<>(); + + for (int i = 0; i < tokens.size(); i++) { + if (tokens.get(i).contains("-")) { + String[] splitTokens = tokens.get(i).split(Pattern.quote("-")); + if (splitTokens.length == 2 && getOrdinalNumberMap().containsKey(splitTokens[1])) { + words.add(splitTokens[0]); + words.add(splitTokens[1]); + } else { + words.add(tokens.get(i)); + } + } else if (i < tokens.size() - 2 && tokens.get(i + 1).equals("-")) { + if (getOrdinalNumberMap().containsKey(tokens.get(i + 2))) { + words.add(tokens.get(i)); + words.add(tokens.get(i + 2)); + } else { + words.add(tokens.get(i) + tokens.get(i + 1) + tokens.get(i + 2)); + } + + i += 2; + } else { + words.add(tokens.get(i)); + } + } + + return words; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + if (numberStr.contains("-")) { + String[] numbers = numberStr.split(Pattern.quote("-")); + long ret = 0; + for (String number : numbers) { + if (getOrdinalNumberMap().containsKey(number)) { + ret += getOrdinalNumberMap().get(number); + } else if (getCardinalNumberMap().containsKey(number)) { + ret += getCardinalNumberMap().get(number); + } + } + + return ret; + } + + if (getOrdinalNumberMap().containsKey(numberStr)) { + return getOrdinalNumberMap().get(numberStr); + } + + if (getCardinalNumberMap().containsKey(numberStr)) { + return getCardinalNumberMap().get(numberStr); + } + + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java new file mode 100644 index 000000000..4a0706885 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/english/parsers/EnglishNumberRangeParserConfiguration.java @@ -0,0 +1,97 @@ +package com.microsoft.recognizers.text.number.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.english.extractors.OrdinalExtractor; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.number.parsers.INumberRangeParserConfiguration; +import com.microsoft.recognizers.text.number.resources.EnglishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.regex.Pattern; + +public class EnglishNumberRangeParserConfiguration implements INumberRangeParserConfiguration { + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public IExtractor getNumberExtractor() { + return this.numberExtractor; + } + + @Override + public IExtractor getOrdinalExtractor() { + return this.ordinalExtractor; + } + + @Override + public IParser getNumberParser() { + return this.numberParser; + } + + @Override + public Pattern getMoreOrEqual() { + return this.moreOrEqual; + } + + @Override + public Pattern getLessOrEqual() { + return this.lessOrEqual; + } + + @Override + public Pattern getMoreOrEqualSuffix() { + return this.moreOrEqualSuffix; + } + + @Override + public Pattern getLessOrEqualSuffix() { + return this.lessOrEqualSuffix; + } + + @Override + public Pattern getMoreOrEqualSeparate() { + return this.moreOrEqualSeparate; + } + + @Override + public Pattern getLessOrEqualSeparate() { + return this.lessOrEqualSeparate; + } + + private final CultureInfo cultureInfo; + private final IExtractor numberExtractor; + private final IExtractor ordinalExtractor; + private final IParser numberParser; + private final Pattern moreOrEqual; + private final Pattern lessOrEqual; + private final Pattern moreOrEqualSuffix; + private final Pattern lessOrEqualSuffix; + private final Pattern moreOrEqualSeparate; + private final Pattern lessOrEqualSeparate; + + public EnglishNumberRangeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public EnglishNumberRangeParserConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.numberExtractor = NumberExtractor.getInstance(); + this.ordinalExtractor = OrdinalExtractor.getInstance(); + this.numberParser = new BaseNumberParser(new EnglishNumberParserConfiguration()); + + this.moreOrEqual = Pattern.compile(EnglishNumeric.MoreOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqual = Pattern.compile(EnglishNumeric.LessOrEqual, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSuffix = Pattern.compile(EnglishNumeric.MoreOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSuffix = Pattern.compile(EnglishNumeric.LessOrEqualSuffix, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.moreOrEqualSeparate = RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeMoreSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + this.lessOrEqualSeparate = RegExpUtility.getSafeRegExp(EnglishNumeric.OneNumberRangeLessSeparateRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java new file mode 100644 index 000000000..80a2993d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberExtractor.java @@ -0,0 +1,155 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public abstract class BaseNumberExtractor implements IExtractor { + + protected abstract Map getRegexes(); + + protected Map getAmbiguityFiltersDict() { + return null; + } + + protected abstract String getExtractType(); + + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + protected Optional getNegativeNumberTermsRegex() { + return Optional.empty(); + } + + public List extract(String source) { + + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + + ArrayList result = new ArrayList<>(); + + Boolean[] matched = new Boolean[source.length()]; + Arrays.fill(matched, false); + + HashMap matchSource = new HashMap<>(); + + getRegexes().forEach((k, value) -> { + + Match[] matches = RegExpUtility.getMatches(k, source); + + for (Match m : matches) { + int start = m.index; + int length = m.length; + for (int j = 0; j < length; j++) { + matched[start + j] = true; + } + + // Keep Source Data for extra information + matchSource.put(m, value); + } + }); + + int last = -1; + for (int i = 0; i < source.length(); i++) { + + if (matched[i]) { + + if (i + 1 == source.length() || !matched[i + 1]) { + + int start = last + 1; + int length = i - last; + String subStr = source.substring(start, start + length); + + int finalStart = start; + int finalLength = length; + + Optional srcMatches = matchSource.keySet().stream().filter(o -> o.index == finalStart && o.length == finalLength).findFirst(); + + if (srcMatches.isPresent()) { + Match srcMatch = srcMatches.get(); + + // Extract negative numbers + if (getNegativeNumberTermsRegex().isPresent()) { + + Matcher match = getNegativeNumberTermsRegex().get().matcher(source.substring(0, start)); + if (match.find()) { + start = match.start(); + length = length + (match.end() - match.start()); + subStr = match.group() + subStr; + } + } + + ExtractResult er = new ExtractResult( + start, + length, + subStr, + getExtractType(), + matchSource.containsKey(srcMatch) ? matchSource.get(srcMatch) : null); + + result.add(er); + } + } + } else { + last = i; + } + } + + result = filterAmbiguity(result, source); + + return result; + } + + private ArrayList filterAmbiguity(ArrayList extractResults, String input) { + if (getAmbiguityFiltersDict() != null) { + for (Map.Entry pair : getAmbiguityFiltersDict().entrySet()) { + final Pattern key = pair.getKey(); + final Pattern value = pair.getValue(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toCollection(ArrayList::new)); + } + } + } + } + + return extractResults; + } + + protected Pattern generateLongFormatNumberRegexes(LongFormatType type) { + return generateLongFormatNumberRegexes(type, BaseNumbers.PlaceHolderDefault); + } + + protected Pattern generateLongFormatNumberRegexes(LongFormatType type, String placeholder) { + + String thousandsMark = Pattern.quote(String.valueOf(type.thousandsMark)); + String decimalsMark = Pattern.quote(String.valueOf(type.decimalsMark)); + + String regexDefinition = type.decimalsMark == '\0' ? + BaseNumbers.IntegerRegexDefinition(placeholder, thousandsMark) : + BaseNumbers.DoubleRegexDefinition(placeholder, thousandsMark, decimalsMark); + + return RegExpUtility.getSafeLookbehindRegExp(regexDefinition, Pattern.UNICODE_CHARACTER_CLASS); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java new file mode 100644 index 000000000..c6540c350 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BaseNumberRangeExtractor.java @@ -0,0 +1,232 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberRangeConstants; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParser; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; +import org.javatuples.Triplet; + +public abstract class BaseNumberRangeExtractor implements IExtractor { + + private final BaseNumberExtractor numberExtractor; + + private final BaseNumberExtractor ordinalExtractor; + + private final BaseNumberParser numberParser; + + protected abstract Map getRegexes(); + + protected String getExtractType() { + return ""; + } + + protected BaseNumberRangeExtractor(BaseNumberExtractor numberExtractor, BaseNumberExtractor ordinalExtractor, BaseNumberParser numberParser) { + this.numberExtractor = numberExtractor; + this.ordinalExtractor = ordinalExtractor; + this.numberParser = numberParser; + } + + @Override + public List extract(String source) { + if (source == null || source.isEmpty()) { + return Collections.emptyList(); + } + + List result = new ArrayList<>(); + Map, String> matchSource = new HashMap<>(); + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + + List> matches = new ArrayList<>(); + getRegexes().forEach((k, value) -> { + Matcher matcher = k.matcher(source); + if (matcher.find()) { + matcher.reset(); + matches.add(Pair.with(matcher, value)); + } + }); + + for (Pair pair : matches) { + Matcher matcher = pair.getValue0(); + String value = pair.getValue1(); + while (matcher.find()) { + int start = NumberRangeConstants.INVALID_NUM; + int length = NumberRangeConstants.INVALID_NUM; + Pair startAndLength = getMatchedStartAndLength(matcher, value, source, start, length); + start = startAndLength.getValue0(); + length = startAndLength.getValue1(); + + if (start >= 0 && length > 0) { + for (int j = 0; j < length; j++) { + matched[start + j] = true; + } + + // Keep Source Data for extra information + matchSource.put(Pair.with(start, length), value); + } + } + } + + int last = -1; + for (int i = 0; i < source.length(); i++) { + if (matched[i]) { + if (i + 1 == source.length() || !matched[i + 1]) { + int start = last + 1; + int length = i - last; + String substr = source.substring(start, start + length); + + Optional> srcMatches = matchSource.keySet().stream().filter(o -> o.getValue0() == start && o.getValue1() == length).findFirst(); + if (srcMatches.isPresent()) { + Pair srcMatch = srcMatches.get(); + ExtractResult er = new ExtractResult(start, length, substr, getExtractType(), matchSource.containsKey(srcMatch) ? matchSource.get(srcMatch) : null); + result.add(er); + } + } + } else { + last = i; + } + } + + return result; + } + + private Pair getMatchedStartAndLength(Matcher match, String type, String source, int start, int length) { + + Map groupValues = RegExpUtility.getNamedGroups(match, true); + String numberStr1 = groupValues.containsKey("number1") ? groupValues.get("number1") : ""; + String numberStr2 = groupValues.containsKey("number2") ? groupValues.get("number2") : ""; + + if (type.contains(NumberRangeConstants.TWONUM)) { + List extractNumList1 = extractNumberAndOrdinalFromStr(numberStr1); + List extractNumList2 = extractNumberAndOrdinalFromStr(numberStr2); + + if (extractNumList1 != null && extractNumList2 != null) { + if (type.contains(NumberRangeConstants.TWONUMTILL)) { + // num1 must have same type with num2 + if (!extractNumList1.get(0).getType().equals(extractNumList2.get(0).getType())) { + return Pair.with(start, length); + } + + // num1 must less than num2 + ParseResult numExt1 = numberParser.parse(extractNumList1.get(0)); + ParseResult numExt2 = numberParser.parse(extractNumList2.get(0)); + double num1 = numExt1.getValue() != null ? (double)numExt1.getValue() : 0; + double num2 = numExt1.getValue() != null ? (double)numExt2.getValue() : 0; + + if (num1 > num2) { + return Pair.with(start, length); + } + + extractNumList1.subList(1, extractNumList1.size()).clear(); + extractNumList2.subList(1, extractNumList2.size()).clear(); + } + + start = match.start(); + length = match.end() - start; + + Triplet num1 = validateMatchAndGetStartAndLength(extractNumList1, numberStr1, match, source, start, length); + start = num1.getValue1(); + length = num1.getValue2(); + Triplet num2 = validateMatchAndGetStartAndLength(extractNumList2, numberStr2, match, source, start, length); + start = num2.getValue1(); + length = num2.getValue2(); + + if (!num1.getValue0() || !num2.getValue0()) { + start = NumberRangeConstants.INVALID_NUM; + length = NumberRangeConstants.INVALID_NUM; + } + } + } else { + String numberStr = numberStr1 == null || numberStr1.isEmpty() ? numberStr2 : numberStr1; + + List extractNumList = extractNumberAndOrdinalFromStr(numberStr); + + if (extractNumList != null) { + start = match.start(); + length = match.end() - start; + + Triplet num = validateMatchAndGetStartAndLength(extractNumList, numberStr, match, source, start, length); + start = num.getValue1(); + length = num.getValue2(); + if (!num.getValue0()) { + start = NumberRangeConstants.INVALID_NUM; + length = NumberRangeConstants.INVALID_NUM; + } + } + } + + return Pair.with(start, length); + } + + private Triplet + validateMatchAndGetStartAndLength(List extractNumList, String numberStr, MatchResult match, String source, int start, int length) { + + boolean validNum = false; + + for (ExtractResult extractNum : extractNumList) { + if (numberStr.trim().endsWith(extractNum.getText()) && match.group().startsWith(numberStr)) { + start = source.indexOf(numberStr) + (extractNum.getStart() != null ? extractNum.getStart() : 0); + length = length - (extractNum.getStart() != null ? extractNum.getStart() : 0); + validNum = true; + } else if (extractNum.getStart() == 0 && match.group().endsWith(numberStr)) { + length = length - numberStr.length() + (extractNum.getLength() != null ? extractNum.getLength() : 0); + validNum = true; + } else if (extractNum.getStart() == 0 && extractNum.getLength() == numberStr.trim().length()) { + validNum = true; + } + + if (validNum) { + break; + } + } + + return Triplet.with(validNum, start, length); + } + + private List extractNumberAndOrdinalFromStr(String numberStr) { + List extractNumber = numberExtractor.extract(numberStr); + List extractOrdinal = ordinalExtractor.extract(numberStr); + + if (extractNumber.size() == 0) { + return extractOrdinal.size() == 0 ? null : extractOrdinal; + } + + if (extractOrdinal.size() == 0) { + return extractNumber; + } + + extractNumber.addAll(extractOrdinal); + + // extractNumber = extractNumber.OrderByDescending(num => num.Length).ThenByDescending(num => num.Start).ToList(); + Collections.sort(extractNumber, (Comparator)(o1, o2) -> { + Integer x1 = ((ExtractResult)o1).getLength(); + Integer x2 = ((ExtractResult)o2).getLength(); + int scomp = x2.compareTo(x1); + + if (scomp != 0) { + return scomp; + } + + x1 = ((ExtractResult)o1).getStart(); + x2 = ((ExtractResult)o2).getStart(); + return x2.compareTo(x1); + }); + + return extractNumber; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java new file mode 100644 index 000000000..a7c5fe57b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/BasePercentageExtractor.java @@ -0,0 +1,241 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberOptions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public abstract class BasePercentageExtractor implements IExtractor { + + private final BaseNumberExtractor numberExtractor; + + protected abstract Set getRegexes(); + + protected NumberOptions getOptions() { + return NumberOptions.None; + } + + protected String getExtractType() { + return Constants.SYS_NUM_PERCENTAGE; + } + + protected static final String NumExtType = Constants.SYS_NUM; + + protected static final String FracNumExtType = Constants.SYS_NUM_FRACTION; + + protected BasePercentageExtractor(BaseNumberExtractor numberExtractor) { + this.numberExtractor = numberExtractor; + } + + public List extract(String source) { + + String originSource = source; + // preprocess the source sentence via extracting and replacing the numbers in it + PreProcessResult preProcessResult = preProcessStrWithNumberExtracted(originSource); + source = preProcessResult.string; + Map positionMap = preProcessResult.positionMap; + List numExtResults = preProcessResult.numExtractResults; + + List allMatches = new ArrayList<>(); + // match percentage with regexes + for (Pattern regex : getRegexes()) { + allMatches.add(regex.matcher(source)); + } + + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + + for (Matcher matcher : allMatches) { + while (matcher.find()) { + MatchResult r = matcher.toMatchResult(); + int start = r.start(); + int end = r.end(); + int length = end - start; + for (int j = 0; j < length; j++) { + matched[j + start] = true; + } + } + } + + List result = new ArrayList<>(); + int last = -1; + + // get index of each matched results + for (int i = 0; i < source.length(); i++) { + if (matched[i]) { + if (i + 1 == source.length() || !matched[i + 1]) { + int start = last + 1; + int length = i - last; + String substr = source.substring(start, start + length); + ExtractResult er = new ExtractResult(start, length, substr, getExtractType(), null); + result.add(er); + } + } else { + last = i; + } + } + + // post-processing, restoring the extracted numbers + postProcessing(result, originSource, positionMap, numExtResults); + + return result; + } + + private void postProcessing(List results, String originSource, Map positionMap, List numExtResults) { + String replaceNumText = "@" + NumExtType; + String replaceFracNumText = "@" + FracNumExtType; + + for (int i = 0; i < results.size(); i++) { + int start = results.get(i).getStart(); + int end = start + results.get(i).getLength(); + String str = results.get(i).getText(); + List> data = new ArrayList<>(); + + String replaceText; + if ((getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0 && str.contains(replaceFracNumText)) { + replaceText = replaceFracNumText; + } else { + replaceText = replaceNumText; + } + + if (positionMap.containsKey(start) && positionMap.containsKey(end)) { + int originStart = positionMap.get(start); + int originLength = positionMap.get(end) - originStart; + results.set(i, new ExtractResult(originStart, originLength, originSource.substring(originStart, originLength + originStart), results.get(i).getType(), null)); + + int numStart = str.indexOf(replaceText); + if (numStart != -1) { + if (positionMap.containsKey(numStart)) { + for (int j = i; j < numExtResults.size(); j++) { + ExtractResult r = results.get(i); + ExtractResult n = numExtResults.get(j); + if ((r.getStart().equals(n.getStart()) || + r.getStart() + r.getLength() == n.getStart() + n.getLength()) && + r.getText().contains(n.getText())) { + data.add(Pair.with(n.getText(), n)); + } + } + } + } + } + + if ((getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0) { + // deal with special cases like " of" and "one in two" in percentageMode + if (str.contains(replaceFracNumText) || data.size() > 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data)); + } else if (data.size() == 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data.get(0))); + } + } else if (data.size() == 1) { + ExtractResult r = results.get(i); + results.set(i, new ExtractResult(r.getStart(), r.getLength(), r.getText(), r.getType(), data.get(0))); + } + } + } + + private PreProcessResult preProcessStrWithNumberExtracted(String input) { + + Map positionMap = new HashMap<>(); + List numExtractResults = numberExtractor.extract(input); + + String replaceNumText = "@" + NumExtType; + String replaceFracText = "@" + FracNumExtType; + boolean percentModeEnabled = (getOptions().ordinal() & NumberOptions.PercentageMode.ordinal()) != 0; + + //@TODO potential cause of GC + int[] match = new int[input.length()]; + Arrays.fill(match, 0); + List> strParts = new ArrayList<>(); + int start; + int end; + + for (int i = 0; i < numExtractResults.size(); i++) { + ExtractResult extraction = numExtractResults.get(i); + start = extraction.getStart(); + end = extraction.getLength() + start; + for (int j = start; j < end; j++) { + if (match[j] == 0) { + if (percentModeEnabled && extraction.getData().toString().startsWith("Frac")) { + match[j] = -(i + 1); + } else { + match[j] = i + 1; + } + } + } + } + + start = 0; + for (int i = 1; i < input.length(); i++) { + if (match[i] != match[i - 1]) { + strParts.add(Pair.with(start, i - 1)); + start = i; + } + } + + strParts.add(Pair.with(start, input.length() - 1)); + + String ret = ""; + int index = 0; + for (Pair strPart : strParts) { + start = strPart.getValue0(); + end = strPart.getValue1(); + int type = match[start]; + + if (type == 0) { + // subsequence which won't be extracted + ret += input.substring(start, end + 1); + for (int i = start; i <= end; i++) { + positionMap.put(index++, i); + } + } else { + // subsequence which will be extracted as number, type is negative for fraction number extraction + String replaceText = type > 0 ? replaceNumText : replaceFracText; + ret += replaceText; + for (int i = 0; i < replaceText.length(); i++) { + positionMap.put(index++, start); + } + } + } + + positionMap.put(index, input.length()); + + return new PreProcessResult(ret, positionMap, numExtractResults); + } + + protected static Set buildRegexes(Set regexStrs) { + return buildRegexes(regexStrs, true); + } + + protected static Set buildRegexes(Set regexStrs, boolean ignoreCase) { + + Set regexes = new HashSet<>(); + for (String regexStr : regexStrs) { + //var sl = "(?=\\b)(" + regexStr + ")(?=(s?\\b))"; + + int options = 0; + if (ignoreCase) { + options = options | Pattern.CASE_INSENSITIVE; + } + + Pattern regex = Pattern.compile(regexStr, options); + + regexes.add(regex); + } + + return Collections.unmodifiableSet(regexes); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java new file mode 100644 index 000000000..b2f439b04 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/extractors/PreProcessResult.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.number.extractors; + +import com.microsoft.recognizers.text.ExtractResult; + +import java.util.List; +import java.util.Map; + +public class PreProcessResult { + public final String string; + public final Map positionMap; + public final List numExtractResults; + + public PreProcessResult(String string, Map positionMap, List numExtractResults) { + this.string = string; + this.positionMap = positionMap; + this.numExtractResults = numExtractResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java new file mode 100644 index 000000000..974c6bb73 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/CardinalExtractor.java @@ -0,0 +1,55 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(FrenchNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java new file mode 100644 index 000000000..6d0ea2f65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(FrenchNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "Double" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java new file mode 100644 index 000000000..18f855c70 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/FractionExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "Frac" + FrenchNumeric.LangMarker); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java new file mode 100644 index 000000000..f349f0882 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/IntegerExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(FrenchNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + FrenchNumeric.LangMarker); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "Integer" + FrenchNumeric.LangMarker); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java new file mode 100644 index 000000000..4ce68f8f3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/NumberExtractor.java @@ -0,0 +1,106 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + HashMap builder = new HashMap<>(); + + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(FrenchNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : FrenchNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..188357df1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/OrdinalExtractor.java @@ -0,0 +1,35 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(FrenchNumeric.OrdinalFrenchRegex, Pattern.UNICODE_CHARACTER_CLASS), "Ord" + FrenchNumeric.LangMarker); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java new file mode 100644 index 000000000..476ade10b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/extractors/PercentageExtractor.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.number.french.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(FrenchNumeric.NumberWithSuffixPercentage); + builder.add(FrenchNumeric.NumberWithPrefixPercentage); + + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java new file mode 100644 index 000000000..e878ca632 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/french/parsers/FrenchNumberParserConfiguration.java @@ -0,0 +1,105 @@ +package com.microsoft.recognizers.text.number.french.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.FrenchNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class FrenchNumberParserConfiguration extends BaseNumberParserConfiguration { + + public FrenchNumberParserConfiguration() { + this(NumberOptions.None); + } + + public FrenchNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.French), options); + } + + public FrenchNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + FrenchNumeric.LangMarker, + cultureInfo, + FrenchNumeric.CompoundNumberLanguage, + FrenchNumeric.MultiDecimalSeparatorCulture, + options, + FrenchNumeric.NonDecimalSeparatorChar, + FrenchNumeric.DecimalSeparatorChar, + FrenchNumeric.FractionMarkerToken, + FrenchNumeric.HalfADozenText, + FrenchNumeric.WordSeparatorToken, + FrenchNumeric.WrittenDecimalSeparatorTexts, + FrenchNumeric.WrittenGroupSeparatorTexts, + FrenchNumeric.WrittenIntegerSeparatorTexts, + FrenchNumeric.WrittenFractionSeparatorTexts, + FrenchNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + FrenchNumeric.RoundNumberMap, + + Pattern.compile(FrenchNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(FrenchNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + return new ArrayList(tokens); + } + + @Override + public long resolveCompositeNumber(String numberStr) { + + Map ordinalNumberMap = getOrdinalNumberMap(); + Map cardinalNumberMap = getCardinalNumberMap(); + + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + + String tmp = strBuilder.toString(); + if (cardinalNumberMap.containsKey(tmp) && cardinalNumberMap.get(tmp) > value) { + lastGoodChar = i; + value = cardinalNumberMap.get(tmp); + } + + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .putAll(FrenchNumeric.OrdinalNumberMap); + + FrenchNumeric.SuffixOrdinalMap.forEach((suffixKey, suffixValue) -> + FrenchNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + builder.put(prefixKey + suffixKey, prefixValue * suffixValue))); + + return builder.build(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java new file mode 100644 index 000000000..43ad6dbd5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/CardinalExtractor.java @@ -0,0 +1,55 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(GermanNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java new file mode 100644 index 000000000..acff62fa1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/DoubleExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(GermanNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS),"DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS),"DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java new file mode 100644 index 000000000..c6578fd41 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracGer"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java new file mode 100644 index 000000000..2cc1b8c21 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/IntegerExtractor.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(GermanNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithPlaceHolder(placeholder), Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.RoundNumberIntegerRegexWithLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.NumbersWithDozenSuffix,Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.AllIntRegexWithLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerGer"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumComma, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java new file mode 100644 index 000000000..7339e7264 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/NumberExtractor.java @@ -0,0 +1,110 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + private final NumberOptions options; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + + private NumberExtractor(NumberMode mode, NumberOptions options) { + this.options = options; + + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(GermanNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + // Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : GermanNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..8a7ae08e5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/OrdinalExtractor.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalNumericRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalGermanRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdGer"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(GermanNumeric.OrdinalRoundNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdGer"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java new file mode 100644 index 000000000..643e834c7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/extractors/PercentageExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.german.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(GermanNumeric.NumberWithSuffixPercentage); + builder.add(GermanNumeric.NumberWithPrefixPercentage); + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java new file mode 100644 index 000000000..a301ebbd1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/german/parsers/GermanNumberParserConfiguration.java @@ -0,0 +1,113 @@ +package com.microsoft.recognizers.text.number.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.GermanNumeric; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class GermanNumberParserConfiguration extends BaseNumberParserConfiguration { + + public GermanNumberParserConfiguration() { + this(NumberOptions.None); + } + + public GermanNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.German), options); + } + + public GermanNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + GermanNumeric.LangMarker, + cultureInfo, + GermanNumeric.CompoundNumberLanguage, + GermanNumeric.MultiDecimalSeparatorCulture, + options, + GermanNumeric.NonDecimalSeparatorChar, + GermanNumeric.DecimalSeparatorChar, + GermanNumeric.FractionMarkerToken, + GermanNumeric.HalfADozenText, + GermanNumeric.WordSeparatorToken, + GermanNumeric.WrittenDecimalSeparatorTexts, + GermanNumeric.WrittenGroupSeparatorTexts, + GermanNumeric.WrittenIntegerSeparatorTexts, + GermanNumeric.WrittenFractionSeparatorTexts, + GermanNumeric.CardinalNumberMap, + GermanNumeric.OrdinalNumberMap, + GermanNumeric.RoundNumberMap, + Pattern.compile(GermanNumeric.HalfADozenRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.DigitalNumberRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.NegativeNumberSignRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), + Pattern.compile(GermanNumeric.FractionPrepositionRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS)); + } + + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + List fracWords = new ArrayList<>(); + List tokenList = new ArrayList<>(tokens); + int tokenLen = tokenList.size(); + + for (int i = 0; i < tokenLen; i++) { + if (tokenList.get(i).contains("-")) { + String[] splitedTokens = tokenList.get(i).split(Pattern.quote("-")); + if (splitedTokens.length == 2 && getOrdinalNumberMap().containsKey(splitedTokens[1])) { + fracWords.add(splitedTokens[0]); + fracWords.add(splitedTokens[1]); + } else { + fracWords.add(tokenList.get(i)); + } + } else if (i < tokenLen - 2 && tokenList.get(i + 1).equals("-")) { + if (getOrdinalNumberMap().containsKey(tokenList.get(i + 2))) { + fracWords.add(tokenList.get(i)); + fracWords.add(tokenList.get(i + 2)); + } else { + fracWords.add(tokenList.get(i) + tokenList.get(i + 1) + tokenList.get(i + 2)); + } + + i += 2; + } else { + fracWords.add(tokenList.get(i)); + } + } + + return fracWords; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + + Map ordinalNumberMap = getOrdinalNumberMap(); + Map cardinalNumberMap = getCardinalNumberMap(); + + if (numberStr.contains("-")) { + String[] numbers = numberStr.split(Pattern.quote("-")); + long ret = 0; + for (String number : numbers) { + if (ordinalNumberMap.containsKey(number)) { + ret += ordinalNumberMap.get(number); + } else if (cardinalNumberMap.containsKey(number)) { + ret += cardinalNumberMap.get(number); + } + } + + return ret; + } + + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + return 0; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java new file mode 100644 index 000000000..95101a349 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/AbstractNumberModel.java @@ -0,0 +1,63 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.utilities.QueryProcessor; + +import java.util.ArrayList; +import java.util.List; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public abstract class AbstractNumberModel implements IModel { + + protected final IParser parser; + protected final IExtractor extractor; + + protected AbstractNumberModel(IParser parser, IExtractor extractor) { + this.parser = parser; + this.extractor = extractor; + } + + @Override + public List parse(String query) { + + // Pre-process the query + query = QueryProcessor.preprocess(query, true); + + List parsedNumbers = new ArrayList(); + + try { + List extractResults = extractor.extract(query); + for (ExtractResult result : extractResults) { + ParseResult parsedResult = parser.parse(result); + if (parsedResult != null) { + parsedNumbers.add(parsedResult); + } + } + } catch (Exception ex) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + ex.printStackTrace(); + } + + return parsedNumbers.stream().map(o -> { + SortedMap sortedMap = new TreeMap(); + sortedMap.put(ResolutionKey.Value, o.getResolutionStr()); + + return new ModelResult( + o.getText(), + o.getStart(), + o.getStart() + o.getLength(), + getModelTypeName(), + sortedMap + ); + }).collect(Collectors.toCollection(ArrayList::new)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java new file mode 100644 index 000000000..cf064a3af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class NumberModel extends AbstractNumberModel { + + public NumberModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_NUMBER; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java new file mode 100644 index 000000000..d0da81cf6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/NumberRangeModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class NumberRangeModel extends AbstractNumberModel { + + public NumberRangeModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_NUMBERRANGE; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java new file mode 100644 index 000000000..f5390a84c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/OrdinalModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class OrdinalModel extends AbstractNumberModel { + + public OrdinalModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_ORDINAL; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java new file mode 100644 index 000000000..71adba257 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/models/PercentModel.java @@ -0,0 +1,17 @@ +package com.microsoft.recognizers.text.number.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.Constants; + +public class PercentModel extends AbstractNumberModel { + + public PercentModel(IParser parser, IExtractor extractor) { + super(parser, extractor); + } + + @Override + public String getModelTypeName() { + return Constants.MODEL_PERCENTAGE; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java new file mode 100644 index 000000000..9e46d8290 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserFactory.java @@ -0,0 +1,52 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.number.Constants; + +import java.util.Arrays; + +public abstract class AgnosticNumberParserFactory { + + public static BaseNumberParser getParser(AgnosticNumberParserType type, INumberParserConfiguration languageConfiguration) { + + boolean isChinese = languageConfiguration.getCultureInfo().cultureCode.equalsIgnoreCase(Culture.Chinese); + boolean isJapanese = languageConfiguration.getCultureInfo().cultureCode.equalsIgnoreCase(Culture.Japanese); + + BaseNumberParser parser; + + + if (isChinese || isJapanese) { + parser = new BaseCJKNumberParser(languageConfiguration); + } else { + parser = new BaseNumberParser(languageConfiguration); + } + + switch (type) { + case Cardinal: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_CARDINAL, Constants.SYS_NUM_INTEGER, Constants.SYS_NUM_DOUBLE)); + break; + case Double: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_DOUBLE)); + break; + case Fraction: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_FRACTION)); + break; + case Integer: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_INTEGER)); + break; + case Ordinal: + parser.setSupportedTypes(Arrays.asList(Constants.SYS_NUM_ORDINAL)); + break; + case Percentage: + if (!isChinese && !isJapanese) { + parser = new BasePercentageParser(languageConfiguration); + } + break; + default: + break; + } + + return parser; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java new file mode 100644 index 000000000..a505ff5a5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/AgnosticNumberParserType.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.number.parsers; + +public enum AgnosticNumberParserType { + Cardinal, + Double, + Fraction, + Integer, + Number, + Ordinal, + Percentage +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java new file mode 100644 index 000000000..cf6e6167a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParser.java @@ -0,0 +1,498 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import com.microsoft.recognizers.text.utilities.StringUtility; + +import java.util.Locale; +import java.util.Map; +import java.util.regex.Pattern; + +public class BaseCJKNumberParser extends BaseNumberParser { + + protected final ICJKNumberParserConfiguration cjkConfig; + + public BaseCJKNumberParser(INumberParserConfiguration config) { + super(config); + this.cjkConfig = (ICJKNumberParserConfiguration)config; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + // check if the parser is configured to support specific types + if (supportedTypes.isPresent() && !supportedTypes.get().stream().anyMatch(t -> extResult.getType().equals(t))) { + return null; + } + + String extra = extResult.getData() instanceof String ? (String)extResult.getData() : null; + ParseResult ret = null; + + ExtractResult getExtResult = new ExtractResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), extResult.getData()); + + if (config.getCultureInfo().cultureCode.equalsIgnoreCase("zh-CN")) { + getExtResult.setText(replaceTraWithSim(getExtResult.getText())); + } + + if (extra == null) { + return null; + } + + if (extra.contains("Per")) { + ret = parsePercentage(getExtResult); + } else if (extra.contains("Num")) { + getExtResult.setText(normalizeCharWidth(getExtResult.getText())); + ret = digitNumberParse(getExtResult); + if (config.getNegativeNumberSignRegex().matcher(getExtResult.getText()).find() && (double)ret.getValue() > 0) { + ret.setValue(-(double)ret.getValue()); + } + + ret.setResolutionStr(getResolutionString((double)ret.getValue())); + } else if (extra.contains("Pow")) { + getExtResult.setText(normalizeCharWidth(getExtResult.getText())); + ret = powerNumberParse(getExtResult); + ret.setResolutionStr(getResolutionString((double)ret.getValue())); + } else if (extra.contains("Frac")) { + ret = parseFraction(getExtResult); + } else if (extra.contains("Dou")) { + ret = parseDouble(getExtResult); + } else if (extra.contains("Integer")) { + ret = parseInteger(getExtResult); + } else if (extra.contains("Ordinal")) { + ret = parseOrdinal(getExtResult); + } + + if (ret != null) { + ret.setText(extResult.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + // Parse Fraction phrase. + protected ParseResult parseFraction(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + + String resultText = extResult.getText(); + String[] splitResult = cjkConfig.getFracSplitRegex().split(resultText); + String intPart = ""; + String demoPart = ""; + String numPart = ""; + + if (splitResult.length == 3) { + intPart = splitResult[0]; + demoPart = splitResult[1]; + numPart = splitResult[2]; + } else { + intPart = String.valueOf(cjkConfig.getZeroChar()); + demoPart = splitResult[0]; + numPart = splitResult[1]; + } + + Pattern digitNumRegex = cjkConfig.getDigitNumRegex(); + Pattern pointRegex = cjkConfig.getPointRegex(); + + double intValue = digitNumRegex.matcher(intPart).find() ? + getDigitValue(intPart, 1.0) : + getIntValue(intPart); + + double numValue = digitNumRegex.matcher(numPart).find() ? + getDigitValue(numPart, 1.0) : + (pointRegex.matcher(numPart).find() ? + getIntValue(pointRegex.split(numPart)[0]) + getPointValue(pointRegex.split(numPart)[1]) : + getIntValue(numPart)); + + double demoValue = digitNumRegex.matcher(demoPart).find() ? + getDigitValue(demoPart, 1.0) : + getIntValue(demoPart); + + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intPart).find()) { + result.setValue(intValue - numValue / demoValue); + } else { + result.setValue(intValue + numValue / demoValue); + } + + result.setResolutionStr(getResolutionString((double)result.getValue())); + return result; + } + + // Parse percentage phrase. + protected ParseResult parsePercentage(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + Map zeroToNineMap = cjkConfig.getZeroToNineMap(); + + String resultText = extResult.getText(); + long power = 1; + + if (extResult.getData().toString().contains("Spe")) { + resultText = normalizeCharWidth(resultText); + resultText = replaceUnit(resultText); + + if (resultText.equals("半額") || resultText.equals("半値") || resultText.equals("半折")) { + result.setValue(50); + } else if (resultText.equals("10成") || resultText.equals("10割") || resultText.equals("十割")) { + result.setValue(100); + } else { + Match[] matches = RegExpUtility.getMatches(this.cjkConfig.getSpeGetNumberRegex(), resultText); + double intNumber; + + if (matches.length == 2) { + char intNumberChar = matches[0].value.charAt(0); + + if (intNumberChar == cjkConfig.getPairChar()) { + intNumber = 5; + } else if (cjkConfig.getTenChars().contains(intNumberChar)) { + intNumber = 10; + } else { + intNumber = zeroToNineMap.get(intNumberChar); + } + + char pointNumberChar = matches[1].value.charAt(0); + double pointNumber; + if (pointNumberChar == '半') { + pointNumber = 0.5; + } else { + pointNumber = zeroToNineMap.get(pointNumberChar) * 0.1; + } + + result.setValue((intNumber + pointNumber) * 10); + } else if (matches.length == 5) { + // Deal the Japanese percentage case like "xxx割xxx分xxx厘", get the integer value and convert into result. + char intNumberChar = matches[0].value.charAt(0); + char pointNumberChar = matches[1].value.charAt(0); + char dotNumberChar = matches[3].value.charAt(0); + + double pointNumber = zeroToNineMap.get(pointNumberChar) * 0.1; + double dotNumber = zeroToNineMap.get(dotNumberChar) * 0.01; + + intNumber = zeroToNineMap.get(intNumberChar); + + result.setValue((intNumber + pointNumber + dotNumber) * 10); + } else { + char intNumberChar = matches[0].value.charAt(0); + + if (intNumberChar == cjkConfig.getPairChar()) { + intNumber = 5; + } else if (cjkConfig.getTenChars().contains(intNumberChar)) { + intNumber = 10; + } else { + intNumber = zeroToNineMap.get(intNumberChar); + } + + result.setValue(intNumber * 10); + } + } + } else if (extResult.getData().toString().contains("Num")) { + + Match[] doubleMatches = RegExpUtility.getMatches(cjkConfig.getPercentageRegex(), resultText); + String doubleText = doubleMatches[doubleMatches.length - 1].value; + + if (doubleText.contains("k") || doubleText.contains("K") || doubleText.contains("k") || + doubleText.contains("K")) { + power = 1000; + } + + if (doubleText.contains("M") || doubleText.contains("M")) { + power = 1000000; + } + + if (doubleText.contains("G") || doubleText.contains("G")) { + power = 1000000000; + } + + if (doubleText.contains("T") || doubleText.contains("T")) { + power = 1000000000000L; + } + + result.setValue(getDigitValue(resultText, power)); + } else { + Match[] doubleMatches = RegExpUtility.getMatches(cjkConfig.getPercentageRegex(), resultText); + String doubleText = doubleMatches[doubleMatches.length - 1].value; + + doubleText = replaceUnit(doubleText); + + String[] splitResult = cjkConfig.getPointRegex().split(doubleText); + if (splitResult[0].equals("")) { + splitResult[0] = String.valueOf(cjkConfig.getZeroChar()); + } + + double doubleValue = getIntValue(splitResult[0]); + if (splitResult.length == 2) { + if (cjkConfig.getNegativeNumberSignRegex().matcher(splitResult[0]).find()) { + doubleValue -= getPointValue(splitResult[1]); + } else { + doubleValue += getPointValue(splitResult[1]); + } + } + + result.setValue(doubleValue); + } + + Match[] matches = RegExpUtility.getMatches(this.cjkConfig.getPercentageNumRegex(), resultText); + if (matches.length > 0) { + String demoString = matches[0].value; + String[] splitResult = cjkConfig.getFracSplitRegex().split(demoString); + String demoPart = splitResult[0]; + + Pattern digitNumRegex = cjkConfig.getDigitNumRegex(); + + double demoValue = digitNumRegex.matcher(demoPart).find() ? + getDigitValue(demoPart, 1.0) : + getIntValue(demoPart); + if (demoValue < 100) { + result.setValue((double)result.getValue() * (100 / demoValue)); + } else { + result.setValue((double)result.getValue() / (demoValue / 100)); + } + } + + if (result.getValue() instanceof Double) { + result.setResolutionStr(getResolutionString((double)result.getValue()) + "%"); + } else if (result.getValue() instanceof Integer) { + result.setResolutionStr(getResolutionString((int)result.getValue()) + "%"); + } + + return result; + } + + // Parse ordinal phrase. + protected ParseResult parseOrdinal(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + + String resultText = extResult.getText(); + resultText = resultText.substring(1); + + boolean isDigit = cjkConfig.getDigitNumRegex().matcher(resultText).find(); + boolean isRoundInt = cjkConfig.getRoundNumberIntegerRegex().matcher(resultText).find(); + + double newValue = isDigit && !isRoundInt ? getDigitValue(resultText, 1) : getIntValue(resultText); + + result.setValue(newValue); + result.setResolutionStr(getResolutionString(newValue)); + + return result; + } + + // Parse double phrase + protected ParseResult parseDouble(ExtractResult extResult) { + ParseResult result = new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), null, null, null); + String resultText = extResult.getText(); + + if (cjkConfig.getDoubleAndRoundRegex().matcher(resultText).find()) { + resultText = replaceUnit(resultText); + result.setValue(getDigitValue( + resultText.substring(0, resultText.length() - 1), + cjkConfig.getRoundNumberMapChar().get(resultText.charAt(resultText.length() - 1)))); + } else { + resultText = replaceUnit(resultText); + String[] splitResult = cjkConfig.getPointRegex().split(resultText); + + if (splitResult[0].equals("")) { + splitResult[0] = String.valueOf(cjkConfig.getZeroChar()); + } + + if (cjkConfig.getNegativeNumberSignRegex().matcher(splitResult[0]).find()) { + result.setValue(getIntValue(splitResult[0]) - getPointValue(splitResult[1])); + } else { + result.setValue(getIntValue(splitResult[0]) + getPointValue(splitResult[1])); + } + } + + result.setResolutionStr(getResolutionString((double)result.getValue())); + return result; + } + + // Parse integer phrase + protected ParseResult parseInteger(ExtractResult extResult) { + double value = getIntValue(extResult.getText()); + return new ParseResult(extResult.getStart(), extResult.getLength(), extResult.getText(), extResult.getType(), extResult.getText(), value, getResolutionString(value)); + } + + // Replace traditional Chinese characters with simpilified Chinese ones. + private String replaceTraWithSim(String text) { + if (StringUtility.isNullOrWhiteSpace(text)) { + return text; + } + + Map tratoSimMap = cjkConfig.getTratoSimMap(); + + StringBuilder builder = new StringBuilder(); + text.chars().mapToObj(i -> (char)i).forEach(c -> { + builder.append(tratoSimMap.containsKey(c) ? tratoSimMap.get(c) : c); + }); + + return builder.toString(); + } + + // Replace full digtal numbers with half digtal numbers. "4" and "4" are both legal in Japanese, replace "4" with "4", then deal with "4" + private String normalizeCharWidth(String text) { + if (StringUtility.isNullOrWhiteSpace(text)) { + return text; + } + + Map fullToHalfMap = cjkConfig.getFullToHalfMap(); + StringBuilder builder = new StringBuilder(); + text.chars().mapToObj(i -> (char)i).forEach(c -> { + builder.append(fullToHalfMap.containsKey(c) ? fullToHalfMap.get(c) : c); + }); + + return builder.toString(); + } + + // Parse unit phrase. "万", "億",... + private String replaceUnit(String resultText) { + for (Map.Entry p : cjkConfig.getUnitMap().entrySet()) { + resultText = resultText.replace(p.getKey(), p.getValue()); + } + + return resultText; + } + + private double getDigitValue(String intStr, double power) { + boolean isNegative = false; + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intStr).find()) { + isNegative = true; + intStr = intStr.substring(1); + } + + intStr = normalizeCharWidth(intStr); + double intValue = getDigitalValue(intStr, power); + if (isNegative) { + intValue = -intValue; + } + + return intValue; + } + + private double getIntValue(String intStr) { + Map roundNumberMapChar = cjkConfig.getRoundNumberMapChar(); + + intStr = replaceUnit(intStr); + double intValue = 0; + double partValue = 0; + double beforeValue = 1; + + boolean isRoundBefore = false; + long roundBefore = -1; + long roundDefault = 1; + boolean isNegative = false; + boolean hasPreviousDigits = false; + + boolean isDozen = false; + boolean isPair = false; + + if (cjkConfig.getDozenRegex().matcher(intStr).find()) { + isDozen = true; + if (cjkConfig.getCultureInfo().cultureCode.equalsIgnoreCase("zh-CN")) { + intStr = intStr.substring(0, intStr.length() - 1); + } else if (cjkConfig.getCultureInfo().cultureCode.equalsIgnoreCase("ja-JP")) { + intStr = intStr.substring(0, intStr.length() - 3); + } + + } else if (cjkConfig.getPairRegex().matcher(intStr).find()) { + isPair = true; + intStr = intStr.substring(0, intStr.length() - 1); + } + + if (cjkConfig.getNegativeNumberSignRegex().matcher(intStr).find()) { + isNegative = true; + intStr = intStr.substring(1); + } + + for (int i = 0; i < intStr.length(); i++) { + if (roundNumberMapChar.containsKey(intStr.charAt(i))) { + + Long roundRecent = roundNumberMapChar.get(intStr.charAt(i)); + if (roundBefore != -1 && roundRecent > roundBefore) { + if (isRoundBefore) { + intValue += partValue * roundRecent; + isRoundBefore = false; + } else { + partValue += beforeValue * roundDefault; + intValue += partValue * roundRecent; + } + roundBefore = -1; + partValue = 0; + } else { + isRoundBefore = true; + partValue += beforeValue * roundRecent; + roundBefore = roundRecent; + + if (i == intStr.length() - 1 || cjkConfig.getRoundDirectList().contains(intStr.charAt(i))) { + intValue += partValue; + partValue = 0; + } + } + + roundDefault = roundRecent / 10; + } else if (cjkConfig.getZeroToNineMap().containsKey(intStr.charAt(i))) { + if (i != intStr.length() - 1) { + boolean isNotRoundNext = cjkConfig.getTenChars().contains(intStr.charAt(i + 1)) || !roundNumberMapChar.containsKey(intStr.charAt(i + 1)); + if (intStr.charAt(i) == cjkConfig.getZeroChar() && isNotRoundNext) { + beforeValue = 1; + roundDefault = 1; + } else { + double currentDigit = cjkConfig.getZeroToNineMap().get(intStr.charAt(i)); + if (hasPreviousDigits) { + beforeValue = beforeValue * 10 + currentDigit; + } else { + beforeValue = currentDigit; + } + isRoundBefore = false; + } + } else { + if (Character.isDigit(intStr.charAt(i))) { + roundDefault = 1; + } + double currentDigit = cjkConfig.getZeroToNineMap().get(intStr.charAt(i)); + if (hasPreviousDigits) { + beforeValue = beforeValue * 10 + currentDigit; + } else { + beforeValue = currentDigit; + } + partValue += beforeValue * roundDefault; + intValue += partValue; + partValue = 0; + } + } + hasPreviousDigits = Character.isDigit(intStr.charAt(i)); + } + + if (isNegative) { + intValue = -intValue; + } + + if (isDozen) { + intValue = intValue * 12; + } else if (isPair) { + intValue = intValue * 2; + } + + return intValue; + } + + private double getPointValue(String pointStr) { + double pointValue = 0; + double scale = 0.1; + + Map zeroToNineMap = cjkConfig.getZeroToNineMap(); + + for (int i : pointStr.chars().toArray()) { + char c = (char)i; + pointValue += scale * zeroToNineMap.get(c); + scale *= 0.1; + } + + return pointValue; + } + + private String getResolutionString(double value) { + return config.getCultureInfo() != null ? + NumberFormatUtility.format(value, config.getCultureInfo()) : + String.valueOf(value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java new file mode 100644 index 000000000..6d46c13ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseCJKNumberParserConfiguration.java @@ -0,0 +1,314 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class BaseCJKNumberParserConfiguration implements ICJKNumberParserConfiguration { + + private final String langMarker; + private final CultureInfo cultureInfo; + private final NumberOptions options; + + private final char nonDecimalSeparatorChar; + private final char decimalSeparatorChar; + private final char zeroChar; + private final char pairChar; + private final String fractionMarkerToken; + private final String halfADozenText; + private final String wordSeparatorToken; + + private final List writtenDecimalSeparatorTexts; + private final List writtenGroupSeparatorTexts; + private final List writtenIntegerSeparatorTexts; + private final List writtenFractionSeparatorTexts; + + private final Map cardinalNumberMap; + private final Map ordinalNumberMap; + private final Map roundNumberMap; + + private final Pattern halfADozenRegex; + private final Pattern digitalNumberRegex; + private final Pattern negativeNumberSignRegex; + private final Pattern fractionPrepositionRegex; + + //region ICJKNumberParserConfiguration + private final Map zeroToNineMap; + private final Map roundNumberMapChar; + private final Map fullToHalfMap; + private final Map unitMap; + private final Map tratoSimMap; + + private final List roundDirectList; + private final List tenChars; + + private final Pattern fracSplitRegex; + private final Pattern digitNumRegex; + private final Pattern speGetNumberRegex; + private final Pattern percentageRegex; + private final Pattern percentageNumRegex; + private final Pattern pointRegex; + private final Pattern doubleAndRoundRegex; + private final Pattern pairRegex; + private final Pattern dozenRegex; + private final Pattern roundNumberIntegerRegex; + + private final boolean isCompoundNumberLanguage; + private final boolean isMultiDecimalSeparatorCulture; + + protected BaseCJKNumberParserConfiguration(String langMarker, CultureInfo cultureInfo, boolean isCompoundNumberLanguage, boolean isMultiDecimalSeparatorCulture, + NumberOptions options, char nonDecimalSeparatorChar, char decimalSeparatorChar, + String fractionMarkerToken, String halfADozenText, String wordSeparatorToken, List writtenDecimalSeparatorTexts, List writtenGroupSeparatorTexts, + List writtenIntegerSeparatorTexts, List writtenFractionSeparatorTexts, Map cardinalNumberMap, Map ordinalNumberMap, + Map roundNumberMap, Pattern halfADozenRegex, Pattern digitalNumberRegex, Pattern negativeNumberSignRegex, Pattern fractionPrepositionRegex, Map zeroToNineMap, Map roundNumberMapChar, Map fullToHalfMap, Map unitMap, Map tratoSimMap, + List roundDirectList, Pattern fracSplitRegex, Pattern digitNumRegex, Pattern speGetNumberRegex, Pattern percentageRegex, Pattern pointRegex, + Pattern doubleAndRoundRegex, Pattern pairRegex, Pattern dozenRegex, Pattern roundNumberIntegerRegex,char zeroChar, List tenChars,char pairChar, + Pattern percentageNumRegex) { + + this.langMarker = langMarker; + this.cultureInfo = cultureInfo; + this.isCompoundNumberLanguage = isCompoundNumberLanguage; + this.isMultiDecimalSeparatorCulture = isMultiDecimalSeparatorCulture; + this.options = options; + this.nonDecimalSeparatorChar = nonDecimalSeparatorChar; + this.decimalSeparatorChar = decimalSeparatorChar; + this.zeroChar = zeroChar; + this.pairChar = pairChar; + this.fractionMarkerToken = fractionMarkerToken; + this.halfADozenText = halfADozenText; + this.wordSeparatorToken = wordSeparatorToken; + this.writtenDecimalSeparatorTexts = writtenDecimalSeparatorTexts; + this.writtenGroupSeparatorTexts = writtenGroupSeparatorTexts; + this.writtenIntegerSeparatorTexts = writtenIntegerSeparatorTexts; + this.writtenFractionSeparatorTexts = writtenFractionSeparatorTexts; + this.cardinalNumberMap = cardinalNumberMap; + this.ordinalNumberMap = ordinalNumberMap; + this.roundNumberMap = roundNumberMap; + this.halfADozenRegex = halfADozenRegex; + this.digitalNumberRegex = digitalNumberRegex; + this.negativeNumberSignRegex = negativeNumberSignRegex; + this.fractionPrepositionRegex = fractionPrepositionRegex; + this.zeroToNineMap = zeroToNineMap; + this.roundNumberMapChar = roundNumberMapChar; + this.fullToHalfMap = fullToHalfMap; + this.unitMap = unitMap; + this.tratoSimMap = tratoSimMap; + this.roundDirectList = roundDirectList; + this.tenChars = tenChars; + this.fracSplitRegex = fracSplitRegex; + this.digitNumRegex = digitNumRegex; + this.speGetNumberRegex = speGetNumberRegex; + this.percentageRegex = percentageRegex; + this.pointRegex = pointRegex; + this.doubleAndRoundRegex = doubleAndRoundRegex; + this.pairRegex = pairRegex; + this.dozenRegex = dozenRegex; + this.roundNumberIntegerRegex = roundNumberIntegerRegex; + this.percentageNumRegex = percentageNumRegex; + } + //endregion + + @Override + public Map getZeroToNineMap() { + return this.zeroToNineMap; + } + + @Override + public Map getRoundNumberMapChar() { + return this.roundNumberMapChar; + } + + @Override + public Map getFullToHalfMap() { + return this.fullToHalfMap; + } + + @Override + public Map getUnitMap() { + return this.unitMap; + } + + @Override + public Map getTratoSimMap() { + return this.tratoSimMap; + } + + @Override + public List getRoundDirectList() { + return this.roundDirectList; + } + + @Override + public List getTenChars() { + return this.tenChars; + } + + @Override + public Pattern getFracSplitRegex() { + return this.fracSplitRegex; + } + + @Override + public Pattern getDigitNumRegex() { + return this.digitNumRegex; + } + + @Override + public Pattern getSpeGetNumberRegex() { + return this.speGetNumberRegex; + } + + @Override + public Pattern getPercentageRegex() { + return this.percentageRegex; + } + + @Override + public Pattern getPercentageNumRegex() { + return this.percentageNumRegex; + } + + @Override + public Pattern getPointRegex() { + return this.pointRegex; + } + + @Override + public Pattern getDoubleAndRoundRegex() { + return this.doubleAndRoundRegex; + } + + @Override + public Pattern getPairRegex() { + return this.pairRegex; + } + + @Override + public Pattern getDozenRegex() { + return this.dozenRegex; + } + + @Override + public Pattern getRoundNumberIntegerRegex() { + return this.roundNumberIntegerRegex; + } + + @Override + public Map getCardinalNumberMap() { + return this.cardinalNumberMap; + } + + @Override + public Map getOrdinalNumberMap() { + return this.ordinalNumberMap; + } + + @Override + public Map getRoundNumberMap() { + return this.roundNumberMap; + } + + @Override + public NumberOptions getOptions() { + return this.options; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public Pattern getDigitalNumberRegex() { + return this.digitalNumberRegex; + } + + @Override + public Pattern getFractionPrepositionRegex() { + return this.fractionPrepositionRegex; + } + + @Override + public String getFractionMarkerToken() { + return this.fractionMarkerToken; + } + + @Override + public Pattern getHalfADozenRegex() { + return this.halfADozenRegex; + } + + @Override + public String getHalfADozenText() { + return this.halfADozenText; + } + + @Override + public String getLangMarker() { + return this.langMarker; + } + + @Override + public char getNonDecimalSeparatorChar() { + return this.nonDecimalSeparatorChar; + } + + @Override + public char getDecimalSeparatorChar() { + return this.decimalSeparatorChar; + } + + @Override + public char getZeroChar() { + return this.zeroChar; + } + + @Override + public char getPairChar() { + return this.pairChar; + } + + @Override + public String getWordSeparatorToken() { + return this.wordSeparatorToken; + } + + @Override + public List getWrittenDecimalSeparatorTexts() { + return this.writtenDecimalSeparatorTexts; + } + + @Override + public List getWrittenGroupSeparatorTexts() { + return this.writtenGroupSeparatorTexts; + } + + @Override + public List getWrittenIntegerSeparatorTexts() { + return this.writtenIntegerSeparatorTexts; + } + + @Override + public List getWrittenFractionSeparatorTexts() { + return this.writtenFractionSeparatorTexts; + } + + @Override + public Pattern getNegativeNumberSignRegex() { + return this.negativeNumberSignRegex; + } + + @Override + public boolean isCompoundNumberLanguage() { + return this.isCompoundNumberLanguage; + } + + @Override + public boolean isMultiDecimalSeparatorCulture() { + return this.isMultiDecimalSeparatorCulture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java new file mode 100644 index 000000000..795aebeb7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParser.java @@ -0,0 +1,635 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.utilities.QueryProcessor; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; +import java.util.Optional; +import java.util.Set; +import java.util.Stack; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class BaseNumberParser implements IParser { + + protected final INumberParserConfiguration config; + + protected final Pattern textNumberRegex; + + protected final Pattern longFormatRegex; + + protected final Set roundNumberSet; + + protected Optional> supportedTypes = Optional.empty(); + + public void setSupportedTypes(List types) { + this.supportedTypes = Optional.of(types); + } + + public BaseNumberParser(INumberParserConfiguration config) { + this.config = config; + + String singleIntFrac = config.getWordSeparatorToken() + "| -|" + + getKeyRegex(config.getCardinalNumberMap().keySet()) + "|" + + getKeyRegex(config.getOrdinalNumberMap().keySet()); + + // Necessary for the german language because bigger numbers are not separated by whitespaces or special characters like in other languages + if (config.getCultureInfo().cultureCode.equalsIgnoreCase("de-DE")) { + this.textNumberRegex = Pattern.compile("(" + singleIntFrac + ")", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } else { + this.textNumberRegex = Pattern.compile("(?<=\\b)(" + singleIntFrac + ")(?=\\b)", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + this.longFormatRegex = Pattern.compile("\\d+", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.roundNumberSet = new HashSet<>(config.getRoundNumberMap().keySet()); + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + // check if the parser is configured to support specific types + if (supportedTypes.isPresent() && !this.supportedTypes.get().contains(extractResult.getType())) { + return null; + } + + String extra; + ParseResult ret = null; + + if (extractResult.getData() instanceof String) { + extra = (String)extractResult.getData(); + } else { + if (this.longFormatRegex.matcher(extractResult.getText()).find()) { + extra = "Num"; + } else { + extra = config.getLangMarker(); + } + } + + // Resolve symbol prefix + boolean isNegative = false; + Matcher matchNegative = config.getNegativeNumberSignRegex().matcher(extractResult.getText()); + if (matchNegative.find()) { + isNegative = true; + extractResult = new ExtractResult( + extractResult.getStart(), + extractResult.getLength(), + extractResult.getText().substring(matchNegative.group(1).length()), + extractResult.getType(), + extractResult.getData(), + extractResult.getMetadata()); + } + + if (extra.contains("Num")) { + ret = digitNumberParse(extractResult); + } else if (extra.contains("Frac" + config.getLangMarker())) { + // Frac is a special number, parse via another method + ret = fracLikeNumberParse(extractResult); + } else if (extra.contains(config.getLangMarker())) { + ret = textNumberParse(extractResult); + } else if (extra.contains("Pow")) { + ret = powerNumberParse(extractResult); + } + + if (ret != null && ret.getValue() != null) { + if (isNegative) { + // Recover to the original extracted Text + ret = new ParseResult( + ret.getStart(), + ret.getLength(), + matchNegative.group(1) + extractResult.getText(), + ret.getType(), + ret.getData(), + -(double)ret.getValue(), + ret.getResolutionStr()); + } + + String resolutionStr = config.getCultureInfo() != null ? NumberFormatUtility.format(ret.getValue(), config.getCultureInfo()) : ret.getValue().toString(); + ret.setResolutionStr(resolutionStr); + } + + if (ret != null) { + ret.setText(ret.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + /** + * Precondition: ExtResult must have arabic numerals. + * + * @param extractResult input arabic number + * @return + */ + protected ParseResult digitNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), + extractResult.getType(),null,null,null); + + //[1] 24 + //[2] 12 32/33 + //[3] 1,000,000 + //[4] 234.567 + //[5] 44/55 + //[6] 2 hundred + //dot occurred. + double power = 1; + String handle = extractResult.getText().toLowerCase(); + Matcher match = config.getDigitalNumberRegex().matcher(handle); + int startIndex = 0; + while (match.find()) { + int tmpIndex = -1; + String matched = match.group(); + double rep = config.getRoundNumberMap().get(matched); + + // \\s+ for filter the spaces. + power *= rep; + + while ((tmpIndex = handle.indexOf(matched.toLowerCase(), startIndex)) >= 0) { + String front = QueryProcessor.trimEnd(handle.substring(0, tmpIndex)); + startIndex = front.length(); + handle = front + handle.substring(tmpIndex + matched.length()); + } + } + + // Scale used in the calculate of double + result.setValue(getDigitalValue(handle, power)); + + return result; + } + + private ParseResult fracLikeNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + String resultText = extractResult.getText().toLowerCase(); + + Matcher match = config.getFractionPrepositionRegex().matcher(resultText); + if (match.find()) { + + String numerator = match.group("numerator"); + String denominator = match.group("denominator"); + + double smallValue = Character.isDigit(numerator.charAt(0)) ? + getDigitalValue(numerator, 1) : + getIntValue(getMatches(numerator)); + + double bigValue = Character.isDigit(denominator.charAt(0)) ? + getDigitalValue(denominator, 1) : + getIntValue(getMatches(denominator)); + + result.setValue(smallValue / bigValue); + } else { + List fracWords = config.normalizeTokenSet(Arrays.asList(resultText.split(" ")), result); + + // Split fraction with integer + int splitIndex = fracWords.size() - 1; + long currentValue = config.resolveCompositeNumber(fracWords.get(splitIndex)); + long roundValue = 1; + + // For case like "half" + if (fracWords.size() == 1) { + result.setValue(1 / getIntValue(fracWords)); + return result; + } + + for (splitIndex = fracWords.size() - 2; splitIndex >= 0; splitIndex--) { + + String fracWord = fracWords.get(splitIndex); + if (config.getWrittenFractionSeparatorTexts().contains(fracWord) || + config.getWrittenIntegerSeparatorTexts().contains(fracWord)) { + continue; + } + + long previousValue = currentValue; + currentValue = config.resolveCompositeNumber(fracWord); + + int smHundreds = 100; + + // previous : hundred + // current : one + if ((previousValue >= smHundreds && previousValue > currentValue) || + (previousValue < smHundreds && isComposable(currentValue, previousValue))) { + if (previousValue < smHundreds && currentValue >= roundValue) { + roundValue = currentValue; + } else if (previousValue < smHundreds && currentValue < roundValue) { + splitIndex++; + break; + } + + // current is the first word + if (splitIndex == 0) { + // scan, skip the first word + splitIndex = 1; + while (splitIndex <= fracWords.size() - 2) { + // e.g. one hundred thousand + // frac[i+1] % 100 && frac[i] % 100 = 0 + if (config.resolveCompositeNumber(fracWords.get(splitIndex)) >= smHundreds && + !config.getWrittenFractionSeparatorTexts().contains(fracWords.get(splitIndex + 1)) && + config.resolveCompositeNumber(fracWords.get(splitIndex + 1)) < smHundreds) { + splitIndex++; + break; + } + splitIndex++; + } + break; + } + continue; + } + splitIndex++; + break; + } + + if (splitIndex < 0) { + splitIndex = 0; + } + + List fracPart = new ArrayList(); + for (int i = splitIndex; i < fracWords.size(); i++) { + if (fracWords.get(i).contains("-")) { + String[] split = fracWords.get(i).split(Pattern.quote("-")); + fracPart.add(split[0]); + fracPart.add("-"); + fracPart.add(split[1]); + } else { + fracPart.add(fracWords.get(i)); + } + } + + fracWords.subList(splitIndex, fracWords.size()).clear(); + + // denomi = denominator + double denomiValue = getIntValue(fracPart); + // Split mixed number with fraction + double numerValue = 0; + double intValue = 0; + + int mixedIndex = fracWords.size(); + for (int i = fracWords.size() - 1; i >= 0; i--) { + if (i < fracWords.size() - 1 && config.getWrittenFractionSeparatorTexts().contains(fracWords.get(i))) { + String numerStr = String.join(" ", fracWords.subList(i + 1, fracWords.size())); + numerValue = getIntValue(getMatches(numerStr)); + mixedIndex = i + 1; + break; + } + } + + String intStr = String.join(" ", fracWords.subList(0, mixedIndex)); + intValue = getIntValue(getMatches(intStr)); + + // Find mixed number + if (mixedIndex != fracWords.size() && numerValue < denomiValue) { + result.setValue(intValue + numerValue / denomiValue); + } else { + result.setValue((intValue + numerValue) / denomiValue); + } + } + + return result; + } + + private ParseResult textNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + String handle = extractResult.getText().toLowerCase(); + + //region Special case for "dozen" + handle = config.getHalfADozenRegex().matcher(handle).replaceAll(config.getHalfADozenText()); + //endregion + + List numGroup = QueryProcessor.split(handle, config.getWrittenDecimalSeparatorTexts()); + + //region IntegerPart + String intPart = numGroup.get(0); + + //Store all match str. + List matchStrs = new ArrayList<>(); + Matcher smatch = textNumberRegex.matcher(intPart); + while (smatch.find()) { + String matchStr = smatch.group().toLowerCase(); + matchStrs.add(matchStr); + } + + // Get the value recursively + double intPartRet = getIntValue(matchStrs); + //endregion + + //region DecimalPart + double pointPartRet = 0; + if (numGroup.size() == 2) { + String pointPart = numGroup.get(1); + smatch = textNumberRegex.matcher(pointPart); + matchStrs.clear(); + while (smatch.find()) { + String matchStr = smatch.group().toLowerCase(); + matchStrs.add(matchStr); + } + pointPartRet += getPointValue(matchStrs); + } + //endregion + + result.setValue(intPartRet + pointPartRet); + + return result; + } + + protected ParseResult powerNumberParse(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + String handle = extractResult.getText().toUpperCase(); + boolean isE = !extractResult.getText().contains("^"); + + //[1] 1e10 + //[2] 1.1^-23 + Stack calStack = new Stack<>(); + + double scale = 10; + boolean dot = false; + boolean isNegative = false; + double tmp = 0; + for (int i = 0; i < handle.length(); i++) { + char ch = handle.charAt(i); + if (ch == '^' || ch == 'E') { + if (isNegative) { + calStack.add(-tmp); + } else { + calStack.add(tmp); + } + tmp = 0; + scale = 10; + dot = false; + isNegative = false; + } else if (ch >= '0' && ch <= '9') { + if (dot) { + tmp = tmp + scale * (ch - '0'); + scale *= 0.1; + } else { + tmp = tmp * scale + (ch - '0'); + } + } else if (ch == config.getDecimalSeparatorChar()) { + dot = true; + scale = 0.1; + } else if (ch == '-') { + isNegative = !isNegative; + } else if (ch == '+') { + continue; + } + + if (i == handle.length() - 1) { + if (isNegative) { + calStack.add(-tmp); + } else { + calStack.add(tmp); + } + } + } + + double ret; + if (isE) { + ret = calStack.remove(0) * Math.pow(10, calStack.remove(0)); + } else { + ret = Math.pow(calStack.remove(0), calStack.remove(0)); + } + + + result.setValue(ret); + result.setResolutionStr(NumberFormatUtility.format(ret, config.getCultureInfo())); + + return result; + } + + protected String getKeyRegex(Set keyCollection) { + ArrayList keys = new ArrayList<>(keyCollection); + Collections.sort(keys, Collections.reverseOrder()); + + return String.join("|", keys); + } + + private boolean skipNonDecimalSeparator(char ch, int distance, CultureInfo culture) { + int decimalLength = 3; + + // @TODO: Add this to project level configuration file to be kept in sync + // Special cases for multi-language countries where decimal separators can be used interchangeably. Mostly informally. + // Ex: South Africa, Namibia; Puerto Rico in ES; or in Canada for EN and FR. + // "me pidio $5.00 prestados" and "me pidio $5,00 prestados" -> currency $5 + Pattern cultureRegex = Pattern.compile("^(en|es|fr)(-)?\\b", Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + return (ch == config.getNonDecimalSeparatorChar() && !(distance <= decimalLength && cultureRegex.matcher(culture.cultureCode).find())); + } + + protected double getDigitalValue(String digitsStr, double power) { + double temp = 0; + double scale = 10; + boolean decimalSeparator = false; + int strLength = digitsStr.length(); + boolean isNegative = false; + boolean isFrac = digitsStr.contains("/"); + + Stack calStack = new Stack<>(); + + for (int i = 0; i < digitsStr.length(); i++) { + + char ch = digitsStr.charAt(i); + boolean skippableNonDecimal = skipNonDecimalSeparator(ch, strLength - i, config.getCultureInfo()); + + if (!isFrac && (ch == ' ' || ch == Constants.NO_BREAK_SPACE || skippableNonDecimal)) { + continue; + } + + if (ch == ' ' || ch == '/') { + calStack.push(temp); + temp = 0; + } else if (ch >= '0' && ch <= '9') { + if (decimalSeparator) { + temp = temp + scale * (ch - '0'); + scale *= 0.1; + } else { + temp = temp * scale + (ch - '0'); + } + } else if (ch == config.getDecimalSeparatorChar() || (!skippableNonDecimal && ch == config.getNonDecimalSeparatorChar())) { + decimalSeparator = true; + scale = 0.1; + } else if (ch == '-') { + isNegative = true; + } + } + calStack.push(temp); + + // is the number is a fraction. + double calResult = 0; + if (isFrac) { + double deno = calStack.pop(); + double mole = calStack.pop(); + calResult += mole / deno; + } + + while (!calStack.empty()) { + calResult += calStack.pop(); + } + calResult *= power; + + if (isNegative) { + return -calResult; + } + + return calResult; + } + + + protected Double getIntValue(List matchStrs) { + boolean[] isEnd = new boolean[matchStrs.size()]; + Arrays.fill(isEnd, false); + + double tempValue = 0; + long endFlag = 1; + + //Scan from end to start, find the end word + for (int i = matchStrs.size() - 1; i >= 0; i--) { + if (roundNumberSet.contains(matchStrs.get(i))) { + //if false,then continue + //You will meet hundred first, then thousand. + if (endFlag > config.getRoundNumberMap().get(matchStrs.get(i))) { + continue; + } + + isEnd[i] = true; + endFlag = config.getRoundNumberMap().get(matchStrs.get(i)); + } + } + + if (endFlag == 1) { + Stack tempStack = new Stack<>(); + String oldSym = ""; + for (String matchStr : matchStrs) { + boolean isCardinal = config.getCardinalNumberMap().containsKey(matchStr); + boolean isOrdinal = config.getOrdinalNumberMap().containsKey(matchStr); + + if (isCardinal || isOrdinal) { + double matchValue = isCardinal ? + config.getCardinalNumberMap().get(matchStr) : + config.getOrdinalNumberMap().get(matchStr); + + //This is just for ordinal now. Not for fraction ever. + if (isOrdinal) { + double fracPart = config.getOrdinalNumberMap().get(matchStr); + if (!tempStack.empty()) { + double intPart = tempStack.pop(); + + // if intPart >= fracPart, it means it is an ordinal number + // it begins with an integer, ends with an ordinal + // e.g. ninety-ninth + if (intPart >= fracPart) { + tempStack.push(intPart + fracPart); + } else { + // another case of the type is ordinal + // e.g. three hundredth + while (!tempStack.empty()) { + intPart = intPart + tempStack.pop(); + } + tempStack.push(intPart * fracPart); + } + } else { + tempStack.push(fracPart); + } + } else if (config.getCardinalNumberMap().containsKey(matchStr)) { + if (oldSym.equalsIgnoreCase("-")) { + double sum = tempStack.pop() + matchValue; + tempStack.push(sum); + } else if (oldSym.equalsIgnoreCase(config.getWrittenIntegerSeparatorTexts().get(0)) || tempStack.size() < 2) { + tempStack.push(matchValue); + } else if (tempStack.size() >= 2) { + double sum = tempStack.pop() + matchValue; + sum = tempStack.pop() + sum; + tempStack.push(sum); + } + } + } else { + long complexValue = config.resolveCompositeNumber(matchStr); + if (complexValue != 0) { + tempStack.push((double)complexValue); + } + } + oldSym = matchStr; + } + + for (double stackValue : tempStack) { + tempValue += stackValue; + } + } else { + int lastIndex = 0; + double mulValue = 1; + double partValue = 1; + for (int i = 0; i < isEnd.length; i++) { + if (isEnd[i]) { + mulValue = config.getRoundNumberMap().get(matchStrs.get(i)); + partValue = 1; + + if (i != 0) { + partValue = getIntValue(matchStrs.subList(lastIndex, i)); + + } + + tempValue += mulValue * partValue; + lastIndex = i + 1; + } + } + + //Calculate the part like "thirty-one" + mulValue = 1; + if (lastIndex != isEnd.length) { + partValue = getIntValue(matchStrs.subList(lastIndex, isEnd.length)); + tempValue += mulValue * partValue; + } + } + + return tempValue; + } + + private double getPointValue(List matchStrs) { + double ret = 0; + String firstMatch = matchStrs.get(0); + + if (config.getCardinalNumberMap().containsKey(firstMatch) && config.getCardinalNumberMap().get(firstMatch) >= 10) { + String prefix = "0."; + int tempInt = getIntValue(matchStrs).intValue(); + String all = prefix + tempInt; + ret = Double.parseDouble(all); + } else { + double scale = 0.1; + for (String matchStr : matchStrs) { + ret += config.getCardinalNumberMap().get(matchStr) * scale; + scale *= 0.1; + } + } + + return ret; + } + + private List getMatches(String input) { + Matcher smatch = textNumberRegex.matcher(input); + List matchStrs = new ArrayList(); + + //Store all match str. + while (smatch.find()) { + String matchStr = smatch.group(); + matchStrs.add(matchStr); + } + + return matchStrs; + } + + //Test if big and combine with small. + //e.g. "hundred" can combine with "thirty" but "twenty" can't combine with "thirty". + private boolean isComposable(long big, long small) { + int baseNumber = small > 10 ? 100 : 10; + return big % baseNumber == 0 && big / baseNumber >= 1; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java new file mode 100644 index 000000000..6a2749422 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberParserConfiguration.java @@ -0,0 +1,171 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class BaseNumberParserConfiguration implements INumberParserConfiguration { + + private final String langMarker; + private final CultureInfo cultureInfo; + private final NumberOptions options; + + private final char nonDecimalSeparatorChar; + private final char decimalSeparatorChar; + private final String fractionMarkerToken; + private final String halfADozenText; + private final String wordSeparatorToken; + + private final List writtenDecimalSeparatorTexts; + private final List writtenGroupSeparatorTexts; + private final List writtenIntegerSeparatorTexts; + private final List writtenFractionSeparatorTexts; + + private final Map cardinalNumberMap; + private final Map ordinalNumberMap; + private final Map roundNumberMap; + private final Pattern halfADozenRegex; + private final Pattern digitalNumberRegex; + private final Pattern negativeNumberSignRegex; + private final Pattern fractionPrepositionRegex; + + private final boolean isCompoundNumberLanguage; + private final boolean isMultiDecimalSeparatorCulture; + + protected BaseNumberParserConfiguration(String langMarker, CultureInfo cultureInfo, boolean isCompoundNumberLanguage, boolean isMultiDecimalSeparatorCulture, + NumberOptions options, char nonDecimalSeparatorChar, char decimalSeparatorChar, + String fractionMarkerToken, String halfADozenText, String wordSeparatorToken, List writtenDecimalSeparatorTexts, List writtenGroupSeparatorTexts, + List writtenIntegerSeparatorTexts, List writtenFractionSeparatorTexts, Map cardinalNumberMap, Map ordinalNumberMap, + Map roundNumberMap, Pattern halfADozenRegex, Pattern digitalNumberRegex, Pattern negativeNumberSignRegex, Pattern fractionPrepositionRegex) { + + this.langMarker = langMarker; + this.cultureInfo = cultureInfo; + this.isCompoundNumberLanguage = isCompoundNumberLanguage; + this.isMultiDecimalSeparatorCulture = isMultiDecimalSeparatorCulture; + this.options = options; + this.nonDecimalSeparatorChar = nonDecimalSeparatorChar; + this.decimalSeparatorChar = decimalSeparatorChar; + this.fractionMarkerToken = fractionMarkerToken; + this.halfADozenText = halfADozenText; + this.wordSeparatorToken = wordSeparatorToken; + this.writtenDecimalSeparatorTexts = writtenDecimalSeparatorTexts; + this.writtenGroupSeparatorTexts = writtenGroupSeparatorTexts; + this.writtenIntegerSeparatorTexts = writtenIntegerSeparatorTexts; + this.writtenFractionSeparatorTexts = writtenFractionSeparatorTexts; + this.cardinalNumberMap = cardinalNumberMap; + this.ordinalNumberMap = ordinalNumberMap; + this.roundNumberMap = roundNumberMap; + this.halfADozenRegex = halfADozenRegex; + this.digitalNumberRegex = digitalNumberRegex; + this.negativeNumberSignRegex = negativeNumberSignRegex; + this.fractionPrepositionRegex = fractionPrepositionRegex; + } + + @Override + public Map getCardinalNumberMap() { + return this.cardinalNumberMap; + } + + @Override + public Map getOrdinalNumberMap() { + return this.ordinalNumberMap; + } + + @Override + public Map getRoundNumberMap() { + return this.roundNumberMap; + } + + @Override + public NumberOptions getOptions() { + return this.options; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public Pattern getDigitalNumberRegex() { + return this.digitalNumberRegex; + } + + @Override + public Pattern getFractionPrepositionRegex() { + return this.fractionPrepositionRegex; + } + + @Override + public String getFractionMarkerToken() { + return this.fractionMarkerToken; + } + + @Override + public Pattern getHalfADozenRegex() { + return this.halfADozenRegex; + } + + @Override + public String getHalfADozenText() { + return this.halfADozenText; + } + + @Override + public String getLangMarker() { + return this.langMarker; + } + + @Override + public char getNonDecimalSeparatorChar() { + return this.nonDecimalSeparatorChar; + } + + @Override + public char getDecimalSeparatorChar() { + return this.decimalSeparatorChar; + } + + @Override + public String getWordSeparatorToken() { + return this.wordSeparatorToken; + } + + @Override + public List getWrittenDecimalSeparatorTexts() { + return this.writtenDecimalSeparatorTexts; + } + + @Override + public List getWrittenGroupSeparatorTexts() { + return this.writtenGroupSeparatorTexts; + } + + @Override + public List getWrittenIntegerSeparatorTexts() { + return this.writtenIntegerSeparatorTexts; + } + + @Override + public List getWrittenFractionSeparatorTexts() { + return this.writtenFractionSeparatorTexts; + } + + @Override + public Pattern getNegativeNumberSignRegex() { + return this.negativeNumberSignRegex; + } + + @Override + public boolean isCompoundNumberLanguage() { + return this.isCompoundNumberLanguage; + } + + @Override + public boolean isMultiDecimalSeparatorCulture() { + return this.isMultiDecimalSeparatorCulture; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java new file mode 100644 index 000000000..890c0ddac --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BaseNumberRangeParser.java @@ -0,0 +1,224 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberRangeConstants; + +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +public class BaseNumberRangeParser implements IParser { + + protected final INumberRangeParserConfiguration config; + + public BaseNumberRangeParser(INumberRangeParserConfiguration config) { + this.config = config; + } + + @Override + public ParseResult parse(ExtractResult extractResult) { + + ParseResult ret = null; + + if (extractResult.getData() != null && !extractResult.getData().toString().isEmpty()) { + String type = extractResult.getData().toString(); + if (type.contains(NumberRangeConstants.TWONUM)) { + ret = parseNumberRangeWhichHasTwoNum(extractResult); + } else { + ret = parseNumberRangeWhichHasOneNum(extractResult); + } + } + + return ret; + } + + private ParseResult parseNumberRangeWhichHasTwoNum(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + List er = config.getNumberExtractor().extract(extractResult.getText()); + + // Valid extracted results for this type should have two numbers + if (er.size() != 2) { + er = config.getOrdinalExtractor().extract(extractResult.getText()); + + if (er.size() != 2) { + return result; + } + } + + List nums = er.stream().map(r -> { + Object value = config.getNumberParser().parse(r).getValue(); + return value == null ? 0 : (Double)value; + }).collect(Collectors.toList()); + + double startValue; + double endValue; + if (nums.get(0) < nums.get(1)) { + startValue = nums.get(0); + endValue = nums.get(1); + } else { + startValue = nums.get(1); + endValue = nums.get(0); + } + + String startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(startValue, config.getCultureInfo()) : String.valueOf(startValue); + String endValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(endValue, config.getCultureInfo()) : String.valueOf(endValue); + + char leftBracket; + char rightBracket; + + String type = (String)extractResult.getData(); + if (type.contains(NumberRangeConstants.TWONUMBETWEEN)) { + // between 20 and 30: (20,30) + leftBracket = NumberRangeConstants.LEFT_OPEN; + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } else if (type.contains(NumberRangeConstants.TWONUMTILL)) { + // 20~30: [20,30) + leftBracket = NumberRangeConstants.LEFT_CLOSED; + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } else { + // check whether it contains string like "more or equal", "less or equal", "at least", etc. + Matcher match = config.getMoreOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + if (!matches) { + match = config.getMoreOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + } else { + leftBracket = NumberRangeConstants.LEFT_OPEN; + } + + match = config.getLessOrEqual().matcher(extractResult.getText()); + matches = match.find(); + + if (!matches) { + match = config.getLessOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + } else { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } + } + + result.setValue(ImmutableMap.of( + "StartValue", startValue, + "EndValue", endValue)); + result.setResolutionStr(new StringBuilder() + .append(leftBracket) + .append(startValueStr) + .append(NumberRangeConstants.INTERVAL_SEPARATOR) + .append(endValueStr) + .append(rightBracket).toString()); + + return result; + } + + private ParseResult parseNumberRangeWhichHasOneNum(ExtractResult extractResult) { + + ParseResult result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + List er = config.getNumberExtractor().extract(extractResult.getText()); + + // Valid extracted results for this type should have one number + if (er.size() != 1) { + er = config.getOrdinalExtractor().extract(extractResult.getText()); + + if (er.size() != 1) { + return result; + } + } + + List nums = er.stream().map(r -> { + Object value = config.getNumberParser().parse(r).getValue(); + return value == null ? 0 : (Double)value; + }).collect(Collectors.toList()); + + char leftBracket; + char rightBracket; + String startValueStr = ""; + String endValueStr = ""; + + String type = (String)extractResult.getData(); + if (type.contains(NumberRangeConstants.MORE)) { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + + Matcher match = config.getMoreOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + + if (!matches) { + match = config.getMoreOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (!matches) { + match = config.getMoreOrEqualSeparate().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + } else { + leftBracket = NumberRangeConstants.LEFT_OPEN; + } + + startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + + result.setValue(ImmutableMap.of("StartValue", nums.get(0))); + } else if (type.contains(NumberRangeConstants.LESS)) { + leftBracket = NumberRangeConstants.LEFT_OPEN; + + Matcher match = config.getLessOrEqual().matcher(extractResult.getText()); + boolean matches = match.find(); + + if (!matches) { + match = config.getLessOrEqualSuffix().matcher(extractResult.getText()); + matches = match.find(); + } + + if (!matches) { + match = config.getLessOrEqualSeparate().matcher(extractResult.getText()); + matches = match.find(); + } + + if (matches) { + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + } else { + rightBracket = NumberRangeConstants.RIGHT_OPEN; + } + + endValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + + result.setValue(ImmutableMap.of("EndValue", nums.get(0))); + } else { + leftBracket = NumberRangeConstants.LEFT_CLOSED; + rightBracket = NumberRangeConstants.RIGHT_CLOSED; + + startValueStr = config.getCultureInfo() != null ? NumberFormatUtility.format(nums.get(0), config.getCultureInfo()) : nums.get(0).toString(); + endValueStr = startValueStr; + + result.setValue(ImmutableMap.of( + "StartValue", nums.get(0), + "EndValue", nums.get(0) + )); + } + + result.setResolutionStr(new StringBuilder() + .append(leftBracket) + .append(startValueStr) + .append(NumberRangeConstants.INTERVAL_SEPARATOR) + .append(endValueStr) + .append(rightBracket) + .toString()); + + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java new file mode 100644 index 000000000..6ef02249e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/BasePercentageParser.java @@ -0,0 +1,67 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.ParseResult; +import java.util.List; +import org.javatuples.Pair; + +public class BasePercentageParser extends BaseNumberParser { + public BasePercentageParser(INumberParserConfiguration config) { + super(config); + } + + @Override + @SuppressWarnings("unchecked") + public ParseResult parse(ExtractResult extractResult) { + String originText = extractResult.getText(); + ParseResult ret = null; + + // replace text & data from extended info + if (extractResult.getData() instanceof List) { + List> extendedData = (List>)extractResult.getData(); + if (extendedData.size() == 2) { + // for case like "2 out of 5". + String newText = extendedData.get(0).getValue0() + " " + config.getFractionMarkerToken() + " " + extendedData.get(1).getValue0(); + extractResult.setText(newText); + extractResult.setData("Frac" + config.getLangMarker()); + + ret = super.parse(extractResult); + ret.setValue((double)ret.getValue() * 100); + } else if (extendedData.size() == 1) { + // for case like "one third of". + extractResult.setText(extendedData.get(0).getValue0()); + extractResult.setData(extendedData.get(0).getValue1().getData()); + + ret = super.parse(extractResult); + + if (extractResult.getData().toString().startsWith("Frac")) { + ret.setValue((double)ret.getValue() * 100); + } + } + + String resolutionStr = config.getCultureInfo() != null ? + NumberFormatUtility.format(ret.getValue(), config.getCultureInfo()) + "%" : + ret.getValue() + "%"; + ret.setResolutionStr(resolutionStr); + } else { + // for case like "one percent" or "1%". + Pair extendedData = (Pair)extractResult.getData(); + extractResult.setText(extendedData.getValue0()); + extractResult.setData(extendedData.getValue1().getData()); + + ret = super.parse(extractResult); + + if (ret.getResolutionStr() != null && !ret.getResolutionStr().isEmpty()) { + if (!ret.getResolutionStr().trim().endsWith("%")) { + ret.setResolutionStr(ret.getResolutionStr().trim() + "%"); + } + } + } + + ret.setText(originText); + ret.setData(extractResult.getText()); + + return ret; + + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java new file mode 100644 index 000000000..627792a5e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/ICJKNumberParserConfiguration.java @@ -0,0 +1,57 @@ +package com.microsoft.recognizers.text.number.parsers; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface ICJKNumberParserConfiguration extends INumberParserConfiguration { + //region language dictionaries + + Map getZeroToNineMap(); + + Map getRoundNumberMapChar(); + + Map getFullToHalfMap(); + + Map getUnitMap(); + + Map getTratoSimMap(); + + //endregion + + //region language lists + + List getRoundDirectList(); + + List getTenChars(); + + //endregion + + //region language settings + + Pattern getFracSplitRegex(); + + Pattern getDigitNumRegex(); + + Pattern getSpeGetNumberRegex(); + + Pattern getPercentageRegex(); + + Pattern getPercentageNumRegex(); + + Pattern getPointRegex(); + + Pattern getDoubleAndRoundRegex(); + + Pattern getPairRegex(); + + Pattern getDozenRegex(); + + Pattern getRoundNumberIntegerRegex(); + + char getZeroChar(); + + char getPairChar(); + + //endregion +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java new file mode 100644 index 000000000..22d31b927 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberParserConfiguration.java @@ -0,0 +1,79 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface INumberParserConfiguration { + + //region language dictionaries + + Map getCardinalNumberMap(); + + Map getOrdinalNumberMap(); + + Map getRoundNumberMap(); + + //endregion + + //region language settings + + NumberOptions getOptions(); + + CultureInfo getCultureInfo(); + + Pattern getDigitalNumberRegex(); + + Pattern getFractionPrepositionRegex(); + + String getFractionMarkerToken(); + + Pattern getHalfADozenRegex(); + + String getHalfADozenText(); + + String getLangMarker(); + + char getNonDecimalSeparatorChar(); + + char getDecimalSeparatorChar(); + + String getWordSeparatorToken(); + + List getWrittenDecimalSeparatorTexts(); + + List getWrittenGroupSeparatorTexts(); + + List getWrittenIntegerSeparatorTexts(); + + List getWrittenFractionSeparatorTexts(); + + Pattern getNegativeNumberSignRegex(); + + boolean isCompoundNumberLanguage(); + + boolean isMultiDecimalSeparatorCulture(); + + //endregion + + /** + * Used when requiring to normalize a token to a valid expression supported by the ImmutableDictionaries (language dictionaries) + * + * @param tokens list of tokens to normalize + * @param context context of the call + * @return list of normalized tokens + */ + List normalizeTokenSet(List tokens, ParseResult context); + + /** + * Used when requiring to convert a String to a valid number supported by the language + * + * @param numberStr composite number + * @return value of the String + */ + long resolveCompositeNumber(String numberStr); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java new file mode 100644 index 000000000..9446a0b7c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/INumberRangeParserConfiguration.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.regex.Pattern; + +public interface INumberRangeParserConfiguration { + CultureInfo getCultureInfo(); + + IExtractor getNumberExtractor(); + + IExtractor getOrdinalExtractor(); + + IParser getNumberParser(); + + Pattern getMoreOrEqual(); + + Pattern getLessOrEqual(); + + Pattern getMoreOrEqualSuffix(); + + Pattern getLessOrEqualSuffix(); + + Pattern getMoreOrEqualSeparate(); + + Pattern getLessOrEqualSeparate(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java new file mode 100644 index 000000000..87c8dda11 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/parsers/NumberFormatUtility.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.number.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import java.math.BigDecimal; +import java.math.MathContext; +import java.math.RoundingMode; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; +import org.apache.commons.lang3.StringUtils; + +public final class NumberFormatUtility { + + private NumberFormatUtility() { + } + + private static final Map supportedCultures; + + static { + supportedCultures = new HashMap<>(); + supportedCultures.put(Culture.English, LongFormatType.DoubleNumCommaDot); + supportedCultures.put(Culture.Spanish, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.Portuguese, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.French, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.German, LongFormatType.DoubleNumDotComma); + supportedCultures.put(Culture.Chinese, null); + supportedCultures.put(Culture.Japanese, LongFormatType.DoubleNumDotComma); + } + + public static String format(Object value, CultureInfo culture) { + + Double doubleValue = (Double)value; + String result; + + // EXPONENTIAL_AT: [-5, 15] }); + // For small positive decimal places. E.g.: 0,000015 or 0,0000015 -> 1.5E-05 or 1.5E-06 + if (doubleValue > 0 && doubleValue != Math.round(doubleValue) && doubleValue < 1E-4) { + result = doubleValue.toString(); + } else { + BigDecimal bc = new BigDecimal(doubleValue, new MathContext(15, RoundingMode.HALF_EVEN)); + result = bc.toString(); + } + + result = result.replace('e', 'E'); + if (result.contains("E-")) { + String[] parts = result.split(Pattern.quote("E-")); + parts[0] = QueryProcessor.trimEnd(parts[0], ".0"); + parts[1] = StringUtils.leftPad(parts[1], 2, '0'); + result = String.join("E-", parts); + } + + if (result.contains("E+")) { + String[] parts = result.split(Pattern.quote("E+")); + parts[0] = QueryProcessor.trimEnd(parts[0], "0"); + result = String.join("E+", parts); + } + + if (result.contains(".")) { + result = QueryProcessor.trimEnd(result, "0"); + result = QueryProcessor.trimEnd(result, "."); + } + + if (supportedCultures.containsKey(culture.cultureCode)) { + LongFormatType longFormat = supportedCultures.get(culture.cultureCode); + if (longFormat != null) { + Character[] chars = result.chars().mapToObj(i -> (char)i) + .map(c -> changeMark(c, longFormat)) + .toArray(Character[]::new); + + StringBuilder sb = new StringBuilder(chars.length); + for (Character c : chars) { + sb.append(c.charValue()); + } + + result = sb.toString(); + } + } + + return result; + } + + private static Character changeMark(Character c, LongFormatType longFormat) { + if (c == '.') { + return longFormat.decimalsMark; + } else if (c == ',') { + return longFormat.thousandsMark; + } + + return c; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java new file mode 100644 index 000000000..25d9c3f43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/CardinalExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(PortugueseNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + // Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + // Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java new file mode 100644 index 000000000..31b97aae3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/DoubleExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(PortugueseNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java new file mode 100644 index 000000000..c396deae8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS) , "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracPor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS) , "FracPor"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracPor"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java new file mode 100644 index 000000000..f3edca18c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/IntegerExtractor.java @@ -0,0 +1,48 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + public IntegerExtractor() { + this(PortugueseNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithDozen2Suffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerPor"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerPor"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java new file mode 100644 index 000000000..eab23931c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/NumberExtractor.java @@ -0,0 +1,104 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import static com.microsoft.recognizers.text.number.NumberMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + HashMap builder = new HashMap<>(); + + // Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(PortugueseNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + // Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : PortugueseNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..089ea724b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/OrdinalExtractor.java @@ -0,0 +1,35 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + public OrdinalExtractor() { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric. OrdinalEnglishRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalPor"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java new file mode 100644 index 000000000..7f65e04c5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/extractors/PercentageExtractor.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.number.portuguese.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(PortugueseNumeric.NumberWithSuffixPercentage); + this.regexes = buildRegexes(builder); + } + +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java new file mode 100644 index 000000000..a159335b3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/portuguese/parsers/PortugueseNumberParserConfiguration.java @@ -0,0 +1,142 @@ +package com.microsoft.recognizers.text.number.portuguese.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.PortugueseNumeric; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class PortugueseNumberParserConfiguration extends BaseNumberParserConfiguration { + + public PortugueseNumberParserConfiguration() { + this(NumberOptions.None); + } + + public PortugueseNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.Portuguese), options); + } + + public PortugueseNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + super( + PortugueseNumeric.LangMarker, + cultureInfo, + PortugueseNumeric.CompoundNumberLanguage, + PortugueseNumeric.MultiDecimalSeparatorCulture, + options, + PortugueseNumeric.NonDecimalSeparatorChar, + PortugueseNumeric.DecimalSeparatorChar, + PortugueseNumeric.FractionMarkerToken, + PortugueseNumeric.HalfADozenText, + PortugueseNumeric.WordSeparatorToken, + PortugueseNumeric.WrittenDecimalSeparatorTexts, + PortugueseNumeric.WrittenGroupSeparatorTexts, + PortugueseNumeric.WrittenIntegerSeparatorTexts, + PortugueseNumeric.WrittenFractionSeparatorTexts, + PortugueseNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + PortugueseNumeric.RoundNumberMap, + + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(PortugueseNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + Map cardinalNumberMap = this.getCardinalNumberMap(); + Map ordinalNumberMap = this.getOrdinalNumberMap(); + + List result = new ArrayList<>(); + + for (String token : tokens) { + String tempWord = QueryProcessor.trimEnd(token, String.valueOf(PortugueseNumeric.PluralSuffix)); + if (ordinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } + + // ends with 'avo' or 'ava' + String finalTempWord = tempWord; + if (PortugueseNumeric.WrittenFractionSuffix.stream().anyMatch(suffix -> finalTempWord.endsWith(suffix))) { + String origTempWord = tempWord; + int newLength = origTempWord.length(); + tempWord = origTempWord.substring(0, newLength - 3); + + if (tempWord.isEmpty()) { + // Ignore avos in fractions. + continue; + } else if (cardinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } else { + tempWord = origTempWord.substring(0, newLength - 2); + if (cardinalNumberMap.containsKey(tempWord)) { + result.add(tempWord); + continue; + } + } + } + + result.add(token); + } + + return result; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + Map cardinalNumberMap = this.getCardinalNumberMap(); + Map ordinalNumberMap = this.getOrdinalNumberMap(); + if (ordinalNumberMap.containsKey(numberStr)) { + return ordinalNumberMap.get(numberStr); + } + + if (cardinalNumberMap.containsKey(numberStr)) { + return cardinalNumberMap.get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + String tmp = strBuilder.toString(); + if (cardinalNumberMap.containsKey(tmp) && cardinalNumberMap.get(tmp) > value) { + lastGoodChar = i; + value = cardinalNumberMap.get(tmp); + } + + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder builder = new ImmutableMap.Builder() + .putAll(PortugueseNumeric.OrdinalNumberMap); + + PortugueseNumeric.SuffixOrdinalMap.forEach((sufixKey, sufixValue) -> + PortugueseNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + builder.put(prefixKey + sufixKey, prefixValue * sufixValue))); + + return builder.build(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java new file mode 100644 index 000000000..bed1156a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/BaseNumbers.java @@ -0,0 +1,48 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import com.google.common.collect.ImmutableMap; + +public class BaseNumbers { + + public static final String NumberReplaceToken = "@builtin.num"; + + public static final String FractionNumberReplaceToken = "@builtin.num.fraction"; + + public static String IntegerRegexDefinition(String placeholder, String thousandsmark) { + return "(((? +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseNumeric { + + public static final String LangMarker = "Chi"; + + public static final Boolean CompoundNumberLanguage = true; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final Character DecimalSeparatorChar = '.'; + + public static final String FractionMarkerToken = ""; + + public static final Character NonDecimalSeparatorChar = ' '; + + public static final String HalfADozenText = ""; + + public static final String WordSeparatorToken = ""; + + public static final Character ZeroChar = '零'; + + public static final Character PairChar = '对'; + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMapChar = ImmutableMap.builder() + .put('十', 10L) + .put('百', 100L) + .put('千', 1000L) + .put('万', 10000L) + .put('亿', 100000000L) + .put('兆', 1000000000000L) + .put('拾', 10L) + .put('佰', 100L) + .put('仟', 1000L) + .put('萬', 10000L) + .put('億', 100000000L) + .build(); + + public static final ImmutableMap ZeroToNineMap = ImmutableMap.builder() + .put('零', 0D) + .put('一', 1D) + .put('二', 2D) + .put('三', 3D) + .put('四', 4D) + .put('五', 5D) + .put('六', 6D) + .put('七', 7D) + .put('八', 8D) + .put('九', 9D) + .put('〇', 0D) + .put('壹', 1D) + .put('贰', 2D) + .put('貳', 2D) + .put('叁', 3D) + .put('肆', 4D) + .put('伍', 5D) + .put('陆', 6D) + .put('陸', 6D) + .put('柒', 7D) + .put('捌', 8D) + .put('玖', 9D) + .put('0', 0D) + .put('1', 1D) + .put('2', 2D) + .put('3', 3D) + .put('4', 4D) + .put('5', 5D) + .put('6', 6D) + .put('7', 7D) + .put('8', 8D) + .put('9', 9D) + .put('0', 0D) + .put('1', 1D) + .put('2', 2D) + .put('3', 3D) + .put('4', 4D) + .put('5', 5D) + .put('6', 6D) + .put('7', 7D) + .put('8', 8D) + .put('9', 9D) + .put('半', 0.5D) + .put('两', 2D) + .put('兩', 2D) + .put('俩', 2D) + .put('倆', 2D) + .put('仨', 3D) + .build(); + + public static final ImmutableMap FullToHalfMap = ImmutableMap.builder() + .put('0', '0') + .put('1', '1') + .put('2', '2') + .put('3', '3') + .put('4', '4') + .put('5', '5') + .put('6', '6') + .put('7', '7') + .put('8', '8') + .put('9', '9') + .put('/', '/') + .put('-', '-') + .put(',', '\'') + .put('G', 'G') + .put('M', 'M') + .put('T', 'T') + .put('K', 'K') + .put('k', 'k') + .put('.', '.') + .build(); + + public static final ImmutableMap TratoSimMap = ImmutableMap.builder() + .put('佰', '百') + .put('點', '点') + .put('個', '个') + .put('幾', '几') + .put('對', '对') + .put('雙', '双') + .build(); + + public static final ImmutableMap UnitMap = ImmutableMap.builder() + .put("萬萬", "億") + .put("億萬", "兆") + .put("萬億", "兆") + .put("万万", "亿") + .put("万亿", "兆") + .put("亿万", "兆") + .put(" ", "") + .put("多", "") + .put("余", "") + .put("几", "") + .build(); + + public static final List RoundDirectList = Arrays.asList('亿', '兆', '億'); + + public static final List TenChars = Arrays.asList('十', '拾'); + + public static final String DigitalNumberRegex = "((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final String ZeroToNineFullHalfRegex = "[\\d1234567890]"; + + public static final String DigitNumRegex = "{ZeroToNineFullHalfRegex}+" + .replace("{ZeroToNineFullHalfRegex}", ZeroToNineFullHalfRegex); + + public static final String DozenRegex = ".*打$"; + + public static final String PercentageRegex = "(?<=(((?)"; + + public static final String LessRegex = "(小于|少于|低于|小於|少於|低於|不到|不足|<)"; + + public static final String EqualRegex = "(等于|等於|=)"; + + public static final String MoreOrEqual = "(({MoreRegex}\\s*(或|或者)?\\s*{EqualRegex})|(至少|最少){SpeicalCharBeforeNumber}?|不{LessRegex}|≥)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{SpeicalCharBeforeNumber}", SpeicalCharBeforeNumber); + + public static final String MoreOrEqualSuffix = "(或|或者)\\s*(以上|之上|更[大多高])"; + + public static final String LessOrEqual = "(({LessRegex}\\s*(或|或者)?\\s*{EqualRegex})|(至多|最多){SpeicalCharBeforeNumber}?|不{MoreRegex}|≤)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{SpeicalCharBeforeNumber}", SpeicalCharBeforeNumber); + + public static final String LessOrEqualSuffix = "(或|或者)\\s*(以下|之下|更[小少低])"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*(?((?!([并且而並的同時时]|([,,](?!\\d+))|。)).)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex); + + public static final String OneNumberRangeMoreRegex2 = "比\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*更?[大多高]"; + + public static final String OneNumberRangeMoreRegex3 = "(?((?!(([,,](?!\\d+))|。|[或者])).)+)\\s*(或|或者)?\\s*([多几余幾餘]|以上|之上|更[大多高])([万亿萬億]{0,2})"; + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*(?((?!([并且而並的同時时]|([,,](?!\\d+))|。)).)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex); + + public static final String OneNumberRangeLessRegex2 = "比\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*更?[小少低]"; + + public static final String OneNumberRangeLessRegex3 = "(?((?!(([,,](?!\\d+))|。|[或者])).)+)\\s*(或|或者)?\\s*(以下|之下|更[小少低])"; + + public static final String OneNumberRangeMoreSeparateRegex = "^[.]"; + + public static final String OneNumberRangeLessSeparateRegex = "^[.]"; + + public static final String OneNumberRangeEqualRegex = "{EqualRegex}\\s*(?((?!(([,,](?!\\d+))|。)).)+)" + .replace("{EqualRegex}", EqualRegex); + + public static final String TwoNumberRangeRegex1 = "((位于|在|位於)|(?=(\\d|\\+|\\-)))\\s*(?((?!(([,,](?!\\d+))|。)).)+)\\s*(和|与|與|{TillRegex})\\s*(?((?!(([,,](?!\\d+))|。))[^之])+)\\s*(之)?(间|間)" + .replace("{TillRegex}", TillRegex); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2}|{OneNumberRangeMoreRegex3})\\s*(且|(并|並)且?|而且|((的)?同時)|((的)?同时)|[,,])?\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2}|{OneNumberRangeLessRegex3})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeMoreRegex3}", OneNumberRangeMoreRegex3) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2) + .replace("{OneNumberRangeLessRegex3}", OneNumberRangeLessRegex3); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2}|{OneNumberRangeLessRegex3})\\s*(且|(并|並)且?|而且|((的)?同時)|((的)?同时)|[,,])?\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2}|{OneNumberRangeMoreRegex3})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeMoreRegex3}", OneNumberRangeMoreRegex3) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2) + .replace("{OneNumberRangeLessRegex3}", OneNumberRangeLessRegex3); + + public static final String TwoNumberRangeRegex4 = "(?((?!(([,,](?!\\d+))|。)).)+)\\s*{TillRegex}\\s*(?((?!(([,,](?!\\d+))|。)).)+)" + .replace("{TillRegex}", TillRegex); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("十", "十足") + .put("伍", "队伍") + .put("肆", "放肆|肆意|肆无忌惮") + .put("陆", "大陆|陆地|登陆|海陆") + .put("拾", "拾取|拾起|收拾|拾到|朝花夕拾") + .build(); + + public static final String AmbiguousFractionConnectorsRegex = "^[.]"; + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java new file mode 100644 index 000000000..5283e4c40 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/EnglishNumeric.java @@ -0,0 +1,504 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishNumeric { + + public static final String LangMarker = "Eng"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String RoundNumberIntegerRegex = "(?:hundred|thousand|million|billion|trillion|lakh|crore)"; + + public static final String ZeroToNineIntegerRegex = "(?:three|seven|eight|four|five|zero|nine|one|two|six)"; + + public static final String TwoToNineIntegerRegex = "(?:three|seven|eight|four|five|nine|two|six)"; + + public static final String NegativeNumberTermsRegex = "(?(minus|negative)\\s+)"; + + public static final String NegativeNumberSignRegex = "^{NegativeNumberTermsRegex}.*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String AnIntRegex = "(an?)(?=\\s)"; + + public static final String TenToNineteenIntegerRegex = "(?:seventeen|thirteen|fourteen|eighteen|nineteen|fifteen|sixteen|eleven|twelve|ten)"; + + public static final String TensNumberIntegerRegex = "(?:seventy|twenty|thirty|eighty|ninety|forty|fifty|sixty)"; + + public static final String SeparaIntRegex = "(?:(({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex})(\\s+{RoundNumberIntegerRegex})*))|(({AnIntRegex}(\\s+{RoundNumberIntegerRegex})+))" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex); + + public static final String AllIntRegex = "(?:((({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex}|{AnIntRegex})(\\s+{RoundNumberIntegerRegex})+)\\s+(and\\s+)?)*{SeparaIntRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String PlaceHolderPureNumber = "\\b"; + + public static final String PlaceHolderDefault = "\\D|\\b"; + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?(next|previous|current)\\s+one|(the\\s+second|next)\\s+to\\s+last|the\\s+one\\s+before\\s+the\\s+last(\\s+one)?|the\\s+last\\s+but\\s+one|(ante)?penultimate|last|next|previous|current)"; + + public static final String BasicOrdinalRegex = "({NumberOrdinalRegex}|{RelativeOrdinalRegex})" + .replace("{NumberOrdinalRegex}", NumberOrdinalRegex) + .replace("{RelativeOrdinalRegex}", RelativeOrdinalRegex); + + public static final String SuffixBasicOrdinalRegex = "(?:(((({TensNumberIntegerRegex}(\\s+(and\\s+)?|\\s*-\\s*){ZeroToNineIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex}|{AnIntRegex})(\\s+{RoundNumberIntegerRegex})+)\\s+(and\\s+)?)*({TensNumberIntegerRegex}(\\s+|\\s*-\\s*))?{BasicOrdinalRegex})" + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{BasicOrdinalRegex}", BasicOrdinalRegex); + + public static final String SuffixRoundNumberOrdinalRegex = "(?:({AllIntRegex}\\s+){RoundNumberOrdinalRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex); + + public static final String AllOrdinalRegex = "(?:{SuffixBasicOrdinalRegex}|{SuffixRoundNumberOrdinalRegex})" + .replace("{SuffixBasicOrdinalRegex}", SuffixBasicOrdinalRegex) + .replace("{SuffixRoundNumberOrdinalRegex}", SuffixRoundNumberOrdinalRegex); + + public static final String OrdinalSuffixRegex = "(?<=\\b)(?:(\\d*(1st|2nd|3rd|[4-90]th))|(1[1-2]th))(?=\\b)"; + + public static final String OrdinalNumericRegex = "(?<=\\b)(?:\\d{1,3}(\\s*,\\s*\\d{3})*\\s*th)(?=\\b)"; + + public static final String OrdinalRoundNumberRegex = "(?{RoundNumberIntegerRegex})$" + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionNounRegex = "(?<=\\b)({AllIntRegex}\\s+(and\\s+)?)?(({AllIntRegex})(\\s+|\\s*-\\s*)((({AllOrdinalRegex})|({RoundNumberOrdinalRegex}))s|halves|quarters)((\\s+of\\s+a)?\\s+{RoundNumberIntegerRegex})?|(half(\\s+a)?|quarter(\\s+of\\s+a)?)\\s+{RoundNumberIntegerRegex})(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionNounWithArticleRegex = "(?<=\\b)((({AllIntRegex}\\s+(and\\s+)?)?(an?|one)(\\s+|\\s*-\\s*)(?!\\bfirst\\b|\\bsecond\\b)(({AllOrdinalRegex})|({RoundNumberOrdinalRegex})|(half|quarter)(((\\s+of)?\\s+a)?\\s+{RoundNumberIntegerRegex})?))|(half))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String FractionPrepositionRegex = "(?({AllIntRegex})|((?in|out\\s+of))\\s+(?({AllIntRegex})|(\\d+)(?![\\.,]))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String FractionPrepositionWithinPercentModeRegex = "(?({AllIntRegex})|((?({AllIntRegex})|(\\d+)(?![\\.,]))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+point){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((?and)"; + + public static final String NumberWithSuffixPercentage = "(?)"; + + public static final String LessRegex = "(?:(less|lower|smaller|fewer)(\\s+than)?|below|under|(?|=)<)"; + + public static final String EqualRegex = "(equal(s|ing)?(\\s+(to|than))?|(?)=)"; + + public static final String MoreOrEqualPrefix = "((no\\s+{LessRegex})|(at\\s+least))" + .replace("{LessRegex}", LessRegex); + + public static final String MoreOrEqual = "(?:({MoreRegex}\\s+(or)?\\s+{EqualRegex})|({EqualRegex}\\s+(or)?\\s+{MoreRegex})|{MoreOrEqualPrefix}(\\s+(or)?\\s+{EqualRegex})?|({EqualRegex}\\s+(or)?\\s+)?{MoreOrEqualPrefix}|>\\s*=|≥)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{MoreOrEqualPrefix}", MoreOrEqualPrefix); + + public static final String MoreOrEqualSuffix = "((and|or)\\s+(((more|greater|higher|larger|bigger)((?!\\s+than)|(\\s+than(?!((\\s+or\\s+equal\\s+to)?\\s*\\d+)))))|((over|above)(?!\\s+than))))"; + + public static final String LessOrEqualPrefix = "((no\\s+{MoreRegex})|(at\\s+most)|(up\\s+to))" + .replace("{MoreRegex}", MoreRegex); + + public static final String LessOrEqual = "(({LessRegex}\\s+(or)?\\s+{EqualRegex})|({EqualRegex}\\s+(or)?\\s+{LessRegex})|{LessOrEqualPrefix}(\\s+(or)?\\s+{EqualRegex})?|({EqualRegex}\\s+(or)?\\s+)?{LessOrEqualPrefix}|<\\s*=|≤)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{LessOrEqualPrefix}", LessOrEqualPrefix); + + public static final String LessOrEqualSuffix = "((and|or)\\s+(less|lower|smaller|fewer)((?!\\s+than)|(\\s+than(?!(\\s*\\d+)))))"; + + public static final String NumberSplitMark = "(?![,.](?!\\d+))(?!\\s*\\b(and\\s+({LessRegex}|{MoreRegex})|but|or|to)\\b)" + .replace("{MoreRegex}", MoreRegex) + .replace("{LessRegex}", LessRegex); + + public static final String MoreRegexNoNumberSucceed = "((bigger|greater|more|higher|larger)((?!\\s+than)|\\s+(than(?!(\\s*\\d+))))|(above|over)(?!(\\s*\\d+)))"; + + public static final String LessRegexNoNumberSucceed = "((less|lower|smaller|fewer)((?!\\s+than)|\\s+(than(?!(\\s*\\d+))))|(below|under)(?!(\\s*\\d+)))"; + + public static final String EqualRegexNoNumberSucceed = "(equal(s|ing)?((?!\\s+(to|than))|(\\s+(to|than)(?!(\\s*\\d+)))))"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreRegex1LB = "(?({NumberSplitMark}.)+)\\s*{MoreOrEqualSuffix}" + .replace("{MoreOrEqualSuffix}", MoreOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){MoreRegexNoNumberSucceed})|({MoreRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{MoreRegexNoNumberSucceed}", MoreRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1LB = "(?({NumberSplitMark}.)+)\\s*{LessOrEqualSuffix}" + .replace("{LessOrEqualSuffix}", LessOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){LessRegexNoNumberSucceed})|({LessRegex}\\s+(?({NumberSplitMark}.)+)(\\s+or\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{LessRegexNoNumberSucceed}", LessRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeEqualRegex = "(?({NumberSplitMark}.)+)" + .replace("{EqualRegex}", EqualRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex1 = "between\\s*(the\\s+)?(?({NumberSplitMark}.)+)\\s*and\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})\\s*(and|but|,)\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})\\s*(and|but|,)\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex4 = "(from\\s+)?(?({NumberSplitMark}(?!\\bfrom\\b).)+)\\s*{TillRegex}\\s*(the\\s+)?(?({NumberSplitMark}.)+)" + .replace("{TillRegex}", TillRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String AmbiguousFractionConnectorsRegex = "(\\bin\\b)"; + + public static final Character DecimalSeparatorChar = '.'; + + public static final String FractionMarkerToken = "over"; + + public static final Character NonDecimalSeparatorChar = ','; + + public static final String HalfADozenText = "six"; + + public static final String WordSeparatorToken = "and"; + + public static final List WrittenDecimalSeparatorTexts = Arrays.asList("point"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("and"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("and"); + + public static final String HalfADozenRegex = "half\\s+a\\s+dozen"; + + public static final String DigitalNumberRegex = "((?<=\\b)(hundred|thousand|[mb]illion|trillion|lakh|crore|dozen(s)?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("a", 1L) + .put("zero", 0L) + .put("an", 1L) + .put("one", 1L) + .put("two", 2L) + .put("three", 3L) + .put("four", 4L) + .put("five", 5L) + .put("six", 6L) + .put("seven", 7L) + .put("eight", 8L) + .put("nine", 9L) + .put("ten", 10L) + .put("eleven", 11L) + .put("twelve", 12L) + .put("dozen", 12L) + .put("dozens", 12L) + .put("thirteen", 13L) + .put("fourteen", 14L) + .put("fifteen", 15L) + .put("sixteen", 16L) + .put("seventeen", 17L) + .put("eighteen", 18L) + .put("nineteen", 19L) + .put("twenty", 20L) + .put("thirty", 30L) + .put("forty", 40L) + .put("fifty", 50L) + .put("sixty", 60L) + .put("seventy", 70L) + .put("eighty", 80L) + .put("ninety", 90L) + .put("hundred", 100L) + .put("thousand", 1000L) + .put("million", 1000000L) + .put("billion", 1000000000L) + .put("trillion", 1000000000000L) + .put("lakh", 100000L) + .put("crore", 10000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("first", 1L) + .put("second", 2L) + .put("secondary", 2L) + .put("half", 2L) + .put("third", 3L) + .put("fourth", 4L) + .put("quarter", 4L) + .put("fifth", 5L) + .put("sixth", 6L) + .put("seventh", 7L) + .put("eighth", 8L) + .put("ninth", 9L) + .put("tenth", 10L) + .put("eleventh", 11L) + .put("twelfth", 12L) + .put("thirteenth", 13L) + .put("fourteenth", 14L) + .put("fifteenth", 15L) + .put("sixteenth", 16L) + .put("seventeenth", 17L) + .put("eighteenth", 18L) + .put("nineteenth", 19L) + .put("twentieth", 20L) + .put("thirtieth", 30L) + .put("fortieth", 40L) + .put("fiftieth", 50L) + .put("sixtieth", 60L) + .put("seventieth", 70L) + .put("eightieth", 80L) + .put("ninetieth", 90L) + .put("hundredth", 100L) + .put("thousandth", 1000L) + .put("millionth", 1000000L) + .put("billionth", 1000000000L) + .put("trillionth", 1000000000000L) + .put("firsts", 1L) + .put("halves", 2L) + .put("thirds", 3L) + .put("fourths", 4L) + .put("quarters", 4L) + .put("fifths", 5L) + .put("sixths", 6L) + .put("sevenths", 7L) + .put("eighths", 8L) + .put("ninths", 9L) + .put("tenths", 10L) + .put("elevenths", 11L) + .put("twelfths", 12L) + .put("thirteenths", 13L) + .put("fourteenths", 14L) + .put("fifteenths", 15L) + .put("sixteenths", 16L) + .put("seventeenths", 17L) + .put("eighteenths", 18L) + .put("nineteenths", 19L) + .put("twentieths", 20L) + .put("thirtieths", 30L) + .put("fortieths", 40L) + .put("fiftieths", 50L) + .put("sixtieths", 60L) + .put("seventieths", 70L) + .put("eightieths", 80L) + .put("ninetieths", 90L) + .put("hundredths", 100L) + .put("thousandths", 1000L) + .put("millionths", 1000000L) + .put("billionths", 1000000000L) + .put("trillionths", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("hundred", 100L) + .put("thousand", 1000L) + .put("million", 1000000L) + .put("billion", 1000000000L) + .put("trillion", 1000000000000L) + .put("lakh", 100000L) + .put("crore", 10000000L) + .put("hundredth", 100L) + .put("thousandth", 1000L) + .put("millionth", 1000000L) + .put("billionth", 1000000000L) + .put("trillionth", 1000000000000L) + .put("hundredths", 100L) + .put("thousandths", 1000L) + .put("millionths", 1000000L) + .put("billionths", 1000000000L) + .put("trillionths", 1000000000000L) + .put("dozen", 12L) + .put("dozens", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("mm", 1000000L) + .put("mil", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bone\\b", "\\b(the|this|that|which)\\s+(one)\\b") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("last", "0") + .put("next one", "1") + .put("current", "0") + .put("current one", "0") + .put("previous one", "-1") + .put("the second to last", "-1") + .put("the one before the last one", "-1") + .put("the one before the last", "-1") + .put("next to last", "-1") + .put("penultimate", "-1") + .put("the last but one", "-1") + .put("antepenultimate", "-2") + .put("next", "1") + .put("previous", "-1") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("last", "end") + .put("next one", "current") + .put("previous one", "current") + .put("current", "current") + .put("current one", "current") + .put("the second to last", "end") + .put("the one before the last one", "end") + .put("the one before the last", "end") + .put("next to last", "end") + .put("penultimate", "end") + .put("the last but one", "end") + .put("antepenultimate", "end") + .put("next", "current") + .put("previous", "current") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java new file mode 100644 index 000000000..bbc5ae533 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/FrenchNumeric.java @@ -0,0 +1,508 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchNumeric { + + public static final String LangMarker = "Fre"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String RoundNumberIntegerRegex = "(cent|mille|millions?|milliards?|billions?)"; + + public static final String ZeroToNineIntegerRegex = "(une?|deux|trois|quatre|cinq|six|sept|huit|neuf)"; + + public static final String TenToNineteenIntegerRegex = "((seize|quinze|quatorze|treize|douze|onze)|dix(\\Wneuf|\\Whuit|\\Wsept)?)"; + + public static final String TensNumberIntegerRegex = "(quatre\\Wvingt(s|\\Wdix)?|soixante\\Wdix|vingt|trente|quarante|cinquante|soixante|septante|octante|huitante|nonante)"; + + public static final String DigitsNumberRegex = "\\d|\\d{1,3}(\\.\\d{3})"; + + public static final String NegativeNumberTermsRegex = "^[.]"; + + public static final String NegativeNumberSignRegex = "^({NegativeNumberTermsRegex}\\s+).*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String HundredsNumberIntegerRegex = "(({ZeroToNineIntegerRegex}(\\s+cent))|cent|((\\s+cent\\s)+{TensNumberIntegerRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex); + + public static final String BelowHundredsRegex = "(({TenToNineteenIntegerRegex}|({TensNumberIntegerRegex}((-|(\\s+et)?\\s+)({TenToNineteenIntegerRegex}|{ZeroToNineIntegerRegex}))?))|{ZeroToNineIntegerRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex); + + public static final String BelowThousandsRegex = "(({HundredsNumberIntegerRegex}(\\s+{BelowHundredsRegex})?|{BelowHundredsRegex}|{TenToNineteenIntegerRegex})|cent\\s+{TenToNineteenIntegerRegex})" + .replace("{HundredsNumberIntegerRegex}", HundredsNumberIntegerRegex) + .replace("{BelowHundredsRegex}", BelowHundredsRegex) + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex); + + public static final String SupportThousandsRegex = "(({BelowThousandsRegex}|{BelowHundredsRegex})\\s+{RoundNumberIntegerRegex}(\\s+{RoundNumberIntegerRegex})?)" + .replace("{BelowThousandsRegex}", BelowThousandsRegex) + .replace("{BelowHundredsRegex}", BelowHundredsRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex); + + public static final String SeparaIntRegex = "({SupportThousandsRegex}(\\s+{SupportThousandsRegex})*(\\s+{BelowThousandsRegex})?|{BelowThousandsRegex})" + .replace("{SupportThousandsRegex}", SupportThousandsRegex) + .replace("{BelowThousandsRegex}", BelowThousandsRegex); + + public static final String AllIntRegex = "({SeparaIntRegex}|mille(\\s+{BelowThousandsRegex})?)" + .replace("{SeparaIntRegex}", SeparaIntRegex) + .replace("{BelowThousandsRegex}", BelowThousandsRegex); + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "({AllIntRegex}(\\s+(virgule|point)){AllPointRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static String DoubleDecimalPointRegex(String placeholder) { + return "(((? WrittenDecimalSeparatorTexts = Arrays.asList("virgule"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("point", "points"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("et", "-"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("et", "sur"); + + public static final String HalfADozenRegex = "(?<=\\b)demie?\\s+douzaine"; + + public static final String DigitalNumberRegex = "((?<=\\b)(cent|mille|millions?|milliards?|billions?|douzaines?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final String AmbiguousFractionConnectorsRegex = "^[.]"; + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("zéro", 0L) + .put("zero", 0L) + .put("un", 1L) + .put("une", 1L) + .put("deux", 2L) + .put("trois", 3L) + .put("quatre", 4L) + .put("cinq", 5L) + .put("six", 6L) + .put("sept", 7L) + .put("huit", 8L) + .put("neuf", 9L) + .put("dix", 10L) + .put("onze", 11L) + .put("douze", 12L) + .put("douzaine", 12L) + .put("douzaines", 12L) + .put("treize", 13L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("seize", 16L) + .put("dix-sept", 17L) + .put("dix-huit", 18L) + .put("dix-huir", 18L) + .put("dix-neuf", 19L) + .put("vingt", 20L) + .put("trente", 30L) + .put("quarante", 40L) + .put("cinquante", 50L) + .put("soixante", 60L) + .put("soixante-dix", 70L) + .put("septante", 70L) + .put("quatre-vingts", 80L) + .put("quatre-vingt", 80L) + .put("quatre vingts", 80L) + .put("quatre vingt", 80L) + .put("quatre-vingt-dix", 90L) + .put("quatre-vingt dix", 90L) + .put("quatre vingt dix", 90L) + .put("quatre-vingts-dix", 90L) + .put("quatre-vingts-onze", 91L) + .put("quatre-vingt-onze", 91L) + .put("quatre-vingts-douze", 92L) + .put("quatre-vingt-douze", 92L) + .put("quatre-vingts-treize", 93L) + .put("quatre-vingt-treize", 93L) + .put("quatre-vingts-quatorze", 94L) + .put("quatre-vingt-quatorze", 94L) + .put("quatre-vingts-quinze", 95L) + .put("quatre-vingt-quinze", 95L) + .put("quatre-vingts-seize", 96L) + .put("quatre-vingt-seize", 96L) + .put("huitante", 80L) + .put("octante", 80L) + .put("nonante", 90L) + .put("cent", 100L) + .put("mille", 1000L) + .put("un million", 1000000L) + .put("million", 1000000L) + .put("millions", 1000000L) + .put("un milliard", 1000000000L) + .put("milliard", 1000000000L) + .put("milliards", 1000000000L) + .put("un mille milliards", 1000000000000L) + .put("un billion", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("premier", 1L) + .put("première", 1L) + .put("premiere", 1L) + .put("unième", 1L) + .put("unieme", 1L) + .put("deuxième", 2L) + .put("deuxieme", 2L) + .put("second", 2L) + .put("seconde", 2L) + .put("troisième", 3L) + .put("demi", 2L) + .put("demie", 2L) + .put("tiers", 3L) + .put("tierce", 3L) + .put("quart", 4L) + .put("quarts", 4L) + .put("troisieme", 3L) + .put("quatrième", 4L) + .put("quatrieme", 4L) + .put("cinquième", 5L) + .put("cinquieme", 5L) + .put("sixième", 6L) + .put("sixieme", 6L) + .put("septième", 7L) + .put("septieme", 7L) + .put("huitième", 8L) + .put("huitieme", 8L) + .put("huirième", 8L) + .put("huirieme", 8L) + .put("neuvième", 9L) + .put("neuvieme", 9L) + .put("dixième", 10L) + .put("dixieme", 10L) + .put("dizième", 10L) + .put("dizieme", 10L) + .put("onzième", 11L) + .put("onzieme", 11L) + .put("douzième", 12L) + .put("douzieme", 12L) + .put("treizième", 13L) + .put("treizieme", 13L) + .put("quatorzième", 14L) + .put("quatorzieme", 14L) + .put("quinzième", 15L) + .put("quinzieme", 15L) + .put("seizième", 16L) + .put("seizieme", 16L) + .put("dix-septième", 17L) + .put("dix-septieme", 17L) + .put("dix-huitième", 18L) + .put("dix-huitieme", 18L) + .put("dix-huirième", 18L) + .put("dix-huirieme", 18L) + .put("dix-neuvième", 19L) + .put("dix-neuvieme", 19L) + .put("vingtième", 20L) + .put("vingtieme", 20L) + .put("trentième", 30L) + .put("trentieme", 30L) + .put("quarantième", 40L) + .put("quarantieme", 40L) + .put("cinquantième", 50L) + .put("cinquantieme", 50L) + .put("soixantième", 60L) + .put("soixantieme", 60L) + .put("soixante-dixième", 70L) + .put("soixante-dixieme", 70L) + .put("septantième", 70L) + .put("septantieme", 70L) + .put("quatre-vingtième", 80L) + .put("quatre-vingtieme", 80L) + .put("huitantième", 80L) + .put("huitantieme", 80L) + .put("octantième", 80L) + .put("octantieme", 80L) + .put("quatre-vingt-dixième", 90L) + .put("quatre-vingt-dixieme", 90L) + .put("nonantième", 90L) + .put("nonantieme", 90L) + .put("centième", 100L) + .put("centieme", 100L) + .put("millième", 1000L) + .put("millieme", 1000L) + .put("millionième", 1000000L) + .put("millionieme", 1000000L) + .put("milliardième", 1000000000L) + .put("milliardieme", 1000000000L) + .put("billionieme", 1000000000000L) + .put("billionième", 1000000000000L) + .put("trillionième", 1000000000000000000L) + .put("trillionieme", 1000000000000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("deux", 2L) + .put("trois", 3L) + .put("quatre", 4L) + .put("cinq", 5L) + .put("six", 6L) + .put("sept", 7L) + .put("huit", 8L) + .put("neuf", 9L) + .put("dix", 10L) + .put("onze", 11L) + .put("douze", 12L) + .put("treize", 13L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("seize", 16L) + .put("dix sept", 17L) + .put("dix-sept", 17L) + .put("dix-huit", 18L) + .put("dix huit", 18L) + .put("dix-neuf", 19L) + .put("dix neuf", 19L) + .put("vingt", 20L) + .put("vingt-et-un", 21L) + .put("vingt et un", 21L) + .put("vingt-deux", 21L) + .put("vingt deux", 22L) + .put("vingt-trois", 23L) + .put("vingt trois", 23L) + .put("vingt-quatre", 24L) + .put("vingt quatre", 24L) + .put("vingt-cinq", 25L) + .put("vingt cinq", 25L) + .put("vingt-six", 26L) + .put("vingt six", 26L) + .put("vingt-sept", 27L) + .put("vingt sept", 27L) + .put("vingt-huit", 28L) + .put("vingt huit", 28L) + .put("vingt-neuf", 29L) + .put("vingt neuf", 29L) + .put("trente", 30L) + .put("quarante", 40L) + .put("cinquante", 50L) + .put("soixante", 60L) + .put("soixante-dix", 70L) + .put("soixante dix", 70L) + .put("septante", 70L) + .put("quatre-vingt", 80L) + .put("quatre vingt", 80L) + .put("huitante", 80L) + .put("octante", 80L) + .put("nonante", 90L) + .put("quatre vingt dix", 90L) + .put("quatre-vingt-dix", 90L) + .put("cent", 100L) + .put("deux cent", 200L) + .put("trois cents", 300L) + .put("quatre cents", 400L) + .put("cinq cent", 500L) + .put("six cent", 600L) + .put("sept cent", 700L) + .put("huit cent", 800L) + .put("neuf cent", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("millième", 1000L) + .put("million", 1000000L) + .put("milliardième", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("cent", 100L) + .put("mille", 1000L) + .put("million", 1000000L) + .put("millions", 1000000L) + .put("milliard", 1000000000L) + .put("milliards", 1000000000L) + .put("billion", 1000000000000L) + .put("billions", 1000000000000L) + .put("centieme", 100L) + .put("centième", 100L) + .put("millieme", 1000L) + .put("millième", 1000L) + .put("millionième", 1000000L) + .put("millionieme", 1000000L) + .put("milliardième", 1000000000L) + .put("milliardieme", 1000000000L) + .put("billionième", 1000000000000L) + .put("billionieme", 1000000000000L) + .put("centiemes", 100L) + .put("centièmes", 100L) + .put("millièmes", 1000L) + .put("milliemes", 1000L) + .put("millionièmes", 1000000L) + .put("millioniemes", 1000000L) + .put("milliardièmes", 1000000000L) + .put("milliardiemes", 1000000000L) + .put("billionièmes", 1000000000000L) + .put("billioniemes", 1000000000000L) + .put("douzaine", 12L) + .put("douzaines", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java new file mode 100644 index 000000000..f8cb70dd2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/GermanNumeric.java @@ -0,0 +1,512 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class GermanNumeric { + + public static final String LangMarker = "Ger"; + + public static final Boolean CompoundNumberLanguage = true; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final String ZeroToNineIntegerRegex = "(drei|sieben|acht|vier|fuenf|fünf|null|neun|eins|(ein(?!($|\\.|,|!|\\?)))|eine|einer|einen|zwei|zwo|sechs)"; + + public static final String RoundNumberIntegerRegex = "(hundert|einhundert|tausend|(\\s*million\\s*)|(\\s*millionen\\s*)|(\\s*mio\\s*)|(\\s*milliarde\\s*)|(\\s*milliarden\\s*)|(\\s*mrd\\s*)|(\\s*billion\\s*)|(\\s*billionen\\s*))"; + + public static final String AnIntRegex = "(eine|ein)(?=\\s)"; + + public static final String TenToNineteenIntegerRegex = "(siebzehn|dreizehn|vierzehn|achtzehn|neunzehn|fuenfzehn|sechzehn|elf|zwoelf|zwölf|zehn)"; + + public static final String TensNumberIntegerRegex = "(siebzig|zwanzig|dreißig|achtzig|neunzig|vierzig|fuenfzig|fünfzig|sechzig)"; + + public static final String NegativeNumberTermsRegex = "^[.]"; + + public static final String NegativeNumberSignRegex = "^({NegativeNumberTermsRegex}\\s+).*" + .replace("{NegativeNumberTermsRegex}", NegativeNumberTermsRegex); + + public static final String SeparaIntRegex = "((({TenToNineteenIntegerRegex}|({ZeroToNineIntegerRegex}und{TensNumberIntegerRegex})|{TensNumberIntegerRegex}|{ZeroToNineIntegerRegex})(\\s*{RoundNumberIntegerRegex})*))|(({AnIntRegex}(\\s*{RoundNumberIntegerRegex})+))" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex); + + public static final String AllIntRegex = "(((({TenToNineteenIntegerRegex}|({ZeroToNineIntegerRegex}und{TensNumberIntegerRegex})|{TensNumberIntegerRegex}|({ZeroToNineIntegerRegex}|{AnIntRegex}))?(\\s*{RoundNumberIntegerRegex})))*{SeparaIntRegex})" + .replace("{TenToNineteenIntegerRegex}", TenToNineteenIntegerRegex) + .replace("{TensNumberIntegerRegex}", TensNumberIntegerRegex) + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AnIntRegex}", AnIntRegex) + .replace("{RoundNumberIntegerRegex}", RoundNumberIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String PlaceHolderPureNumber = "\\b"; + + public static final String PlaceHolderDefault = "\\D|\\b"; + + public static String NumbersWithPlaceHolder(String placeholder) { + return "(((?anderthalb|einundhalb)|(?dreiviertel))"; + + public static final String FractionHalfRegex = "(einhalb)$"; + + public static final List OneHalfTokens = Arrays.asList("ein", "halb"); + + public static final String FractionNounRegex = "(?<=\\b)(({AllIntRegex})(\\s*|\\s*-\\s*)((({AllOrdinalRegex})|({RoundNumberOrdinalRegex}))|halb(er|e|es)?|hälfte)|{FractionUnitsRegex})(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{FractionUnitsRegex}", FractionUnitsRegex); + + public static final String FractionNounWithArticleRegex = "(?<=\\b)(({AllIntRegex}\\s+(und\\s+)?)?eine?(\\s+|\\s*-\\s*)({AllOrdinalRegex}|{RoundNumberOrdinalRegex}|{FractionUnitsRegex}|({AllIntRegex}ein)?(halb(er|e|es)?|hälfte))|{AllIntRegex}ein(halb))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllOrdinalRegex}", AllOrdinalRegex) + .replace("{RoundNumberOrdinalRegex}", RoundNumberOrdinalRegex) + .replace("{FractionUnitsRegex}", FractionUnitsRegex); + + public static final String FractionPrepositionRegex = "(?({AllIntRegex})|((?({AllIntRegex})|(\\d+)(?!\\.))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s*{ZeroToNineIntegerRegex})+|(\\s*{SeparaIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{SeparaIntRegex}", SeparaIntRegex); + + public static final String AllFloatRegex = "({AllIntRegex}(\\s*komma\\s*){AllPointRegex})" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((? WrittenDecimalSeparatorTexts = Arrays.asList("komma"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punkt"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("und"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("durch"); + + public static final String HalfADozenRegex = "ein\\s+halbes\\s+dutzend"; + + public static final String DigitalNumberRegex = "((?<=\\b)(hundert|tausend|million(en)?|mio|milliarde(n)?|mrd|billion(en)?|dutzend(e)?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("ein", 1L) + .put("null", 0L) + .put("eine", 1L) + .put("eins", 1L) + .put("einer", 1L) + .put("einen", 1L) + .put("beiden", 2L) + .put("zwei", 2L) + .put("zwo", 2L) + .put("drei", 3L) + .put("vier", 4L) + .put("fünf", 5L) + .put("fuenf", 5L) + .put("sechs", 6L) + .put("sieben", 7L) + .put("acht", 8L) + .put("neun", 9L) + .put("zehn", 10L) + .put("elf", 11L) + .put("zwölf", 12L) + .put("zwoelf", 12L) + .put("dutzend", 12L) + .put("dreizehn", 13L) + .put("vierzehn", 14L) + .put("fünfzehn", 15L) + .put("fuenfzehn", 15L) + .put("sechzehn", 16L) + .put("siebzehn", 17L) + .put("achtzehn", 18L) + .put("neunzehn", 19L) + .put("zwanzig", 20L) + .put("dreißig", 30L) + .put("vierzig", 40L) + .put("fünfzig", 50L) + .put("fuenfzig", 50L) + .put("sechzig", 60L) + .put("siebzig", 70L) + .put("achtzig", 80L) + .put("neunzig", 90L) + .put("hundert", 100L) + .put("tausend", 1000L) + .put("million", 1000000L) + .put("mio", 1000000L) + .put("millionen", 1000000L) + .put("milliard", 100000000L) + .put("milliarde", 1000000000L) + .put("mrd", 1000000000L) + .put("milliarden", 1000000000L) + .put("billion", 1000000000000L) + .put("billionen", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("zuerst", 1L) + .put("erst", 1L) + .put("erster", 1L) + .put("erste", 1L) + .put("erstes", 1L) + .put("ersten", 1L) + .put("zweit", 2L) + .put("zweiter", 2L) + .put("zweite", 2L) + .put("zweites", 2L) + .put("zweiten", 2L) + .put("halb", 2L) + .put("halbe", 2L) + .put("halbes", 2L) + .put("hälfte", 2L) + .put("haelfte", 2L) + .put("dritt", 3L) + .put("dritter", 3L) + .put("dritte", 3L) + .put("drittes", 3L) + .put("dritten", 3L) + .put("drittel", 3L) + .put("viert", 4L) + .put("vierter", 4L) + .put("vierte", 4L) + .put("viertes", 4L) + .put("vierten", 4L) + .put("viertel", 4L) + .put("fünft", 5L) + .put("fünfter", 5L) + .put("fünfte", 5L) + .put("fünftes", 5L) + .put("fünften", 5L) + .put("fuenft", 5L) + .put("fuenfter", 5L) + .put("fuenfte", 5L) + .put("fuenftes", 5L) + .put("fuenften", 5L) + .put("fünftel", 5L) + .put("fuenftel", 5L) + .put("sechst", 6L) + .put("sechster", 6L) + .put("sechste", 6L) + .put("sechstes", 6L) + .put("sechsten", 6L) + .put("sechstel", 6L) + .put("siebt", 7L) + .put("siebter", 7L) + .put("siebte", 7L) + .put("siebtes", 7L) + .put("siebten", 7L) + .put("siebtel", 7L) + .put("acht", 8L) + .put("achter", 8L) + .put("achte", 8L) + .put("achtes", 8L) + .put("achten", 8L) + .put("achtel", 8L) + .put("neunt", 9L) + .put("neunter", 9L) + .put("neunte", 9L) + .put("neuntes", 9L) + .put("neunten", 9L) + .put("neuntel", 9L) + .put("zehnt", 10L) + .put("zehnter", 10L) + .put("zehnte", 10L) + .put("zehntes", 10L) + .put("zehnten", 10L) + .put("zehntel", 10L) + .put("elft", 11L) + .put("elfter", 11L) + .put("elfte", 11L) + .put("elftes", 11L) + .put("elften", 11L) + .put("elftel", 11L) + .put("zwölft", 12L) + .put("zwölfter", 12L) + .put("zwölfte", 12L) + .put("zwölftes", 12L) + .put("zwölften", 12L) + .put("zwoelft", 12L) + .put("zwoelfter", 12L) + .put("zwoelfte", 12L) + .put("zwoelftes", 12L) + .put("zwoelften", 12L) + .put("zwölftel", 12L) + .put("zwoelftel", 12L) + .put("dreizehnt", 13L) + .put("dreizehnter", 13L) + .put("dreizehnte", 13L) + .put("dreizehntes", 13L) + .put("dreizehnten", 13L) + .put("dreizehntel", 13L) + .put("vierzehnt", 14L) + .put("vierzehnter", 14L) + .put("vierzehnte", 14L) + .put("vierzehntes", 14L) + .put("vierzehnten", 14L) + .put("vierzehntel", 14L) + .put("fünfzehnt", 15L) + .put("fünfzehnter", 15L) + .put("fünfzehnte", 15L) + .put("fünfzehntes", 15L) + .put("fünfzehnten", 15L) + .put("fünfzehntel", 15L) + .put("fuenfzehnt", 15L) + .put("fuenfzehnter", 15L) + .put("fuenfzehnte", 15L) + .put("fuenfzehntes", 15L) + .put("fuenfzehnten", 15L) + .put("fuenfzehntel", 15L) + .put("sechzehnt", 16L) + .put("sechzehnter", 16L) + .put("sechzehnte", 16L) + .put("sechzehntes", 16L) + .put("sechzehnten", 16L) + .put("sechzehntel", 16L) + .put("siebzehnt", 17L) + .put("siebzehnter", 17L) + .put("siebzehnte", 17L) + .put("siebzehntes", 17L) + .put("siebzehnten", 17L) + .put("siebzehntel", 17L) + .put("achtzehnt", 18L) + .put("achtzehnter", 18L) + .put("achtzehnte", 18L) + .put("achtzehntes", 18L) + .put("achtzehnten", 18L) + .put("achtzehntel", 18L) + .put("neunzehnt", 19L) + .put("neunzehnter", 19L) + .put("neunzehnte", 19L) + .put("neunzehntes", 19L) + .put("neunzehnten", 19L) + .put("neunzehntel", 19L) + .put("zwanzigst", 20L) + .put("zwanzigster", 20L) + .put("zwanzigste", 20L) + .put("zwanzigstes", 20L) + .put("zwanzigsten", 20L) + .put("zwangtigstel", 20L) + .put("dreißigst", 30L) + .put("dreißigster", 30L) + .put("dreißigste", 30L) + .put("dreißigstes", 30L) + .put("dreißigsten", 30L) + .put("dreißigstel", 30L) + .put("vierzigst", 40L) + .put("vierzigster", 40L) + .put("vierzigste", 40L) + .put("vierzigstes", 40L) + .put("vierzigsten", 40L) + .put("vierzigstel", 40L) + .put("fünfzigst", 50L) + .put("fünfzigster", 50L) + .put("fünfzigste", 50L) + .put("fünfzigsten", 50L) + .put("fünfzigstes", 50L) + .put("fünfzigstel", 50L) + .put("fuenfzigst", 50L) + .put("fuenfzigster", 50L) + .put("fuenfzigste", 50L) + .put("fuenfzigstes", 50L) + .put("fuenfzigsten", 50L) + .put("fuenfzigstel", 50L) + .put("sechzigst", 60L) + .put("sechzigster", 60L) + .put("sechzigste", 60L) + .put("sechzigstes", 60L) + .put("sechzigsten", 60L) + .put("sechzigstel", 60L) + .put("siebzigst", 70L) + .put("siebzigster", 70L) + .put("siebzigste", 70L) + .put("siebzigstes", 70L) + .put("siebzigsten", 70L) + .put("siebzigstel", 70L) + .put("achtzigst", 80L) + .put("achtzigster", 80L) + .put("achtzigste", 80L) + .put("achtzigstes", 80L) + .put("achtzigsten", 80L) + .put("achtzigstel", 80L) + .put("neunzigst", 90L) + .put("neunzigster", 90L) + .put("neunzigste", 90L) + .put("neunzigstes", 90L) + .put("neunzigsten", 90L) + .put("neunzigstel", 90L) + .put("hundertst", 100L) + .put("hundertster", 100L) + .put("hundertste", 100L) + .put("hundertstes", 100L) + .put("hundertsten", 100L) + .put("hundertstel", 100L) + .put("tausendst", 1000L) + .put("tausendster", 1000L) + .put("tausendste", 1000L) + .put("tausendstes", 1000L) + .put("tausendsten", 1000L) + .put("tausendstel", 1000L) + .put("millionst", 1000000L) + .put("millionster", 1000000L) + .put("millionste", 1000000L) + .put("millionstes", 1000000L) + .put("millionsten", 1000000L) + .put("millionstel", 1000000L) + .put("milliardster", 1000000000L) + .put("milliardste", 1000000000L) + .put("milliardstes", 1000000000L) + .put("milliardsten", 1000000000L) + .put("milliardstel", 1000000000L) + .put("billionster", 1000000000000L) + .put("billionste", 1000000000000L) + .put("billionstes", 1000000000000L) + .put("billionsten", 1000000000000L) + .put("billionstel", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("hundert", 100L) + .put("tausend", 1000L) + .put("million", 1000000L) + .put("millionen", 1000000L) + .put("mio", 1000000L) + .put("milliard", 1000000000L) + .put("milliarde", 1000000000L) + .put("milliarden", 1000000000L) + .put("mrd", 1000000000L) + .put("billion", 1000000000000L) + .put("billionen", 1000000000000L) + .put("hundertstel", 100L) + .put("tausendstel", 1000L) + .put("millionstel", 1000000L) + .put("milliardstel", 1000000000L) + .put("billionstel", 1000000000000L) + .put("hundredths", 100L) + .put("dutzend", 12L) + .put("dutzende", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java new file mode 100644 index 000000000..3d0329efd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/PortugueseNumeric.java @@ -0,0 +1,536 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseNumeric { + + public static final String LangMarker = "Por"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = false; + + public static final String HundredsNumberIntegerRegex = "(quatrocent[ao]s|trezent[ao]s|seiscent[ao]s|setecent[ao]s|oitocent[ao]s|novecent[ao]s|duzent[ao]s|quinhent[ao]s|cem|(?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+(vírgula|virgula|e|ponto)){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static final String DoubleWithMultiplierRegex = "(((? WrittenDecimalSeparatorTexts = Arrays.asList("virgula", "vírgula"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("ponto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("e"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("com"); + + public static final List WrittenFractionSuffix = Arrays.asList("avo", "ava"); + + public static final Character PluralSuffix = 's'; + + public static final String HalfADozenRegex = "meia\\s+d[uú]zia"; + + public static final String DigitalNumberRegex = "((?<=\\b)(mil|cem|milh[oõ]es|milh[aã]o|bilh[oõ]es|bilh[aã]o|trilh[oõ]es|trilh[aã]o|milhares|centena|centenas|dezena|dezenas?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("zero", 0L) + .put("hum", 1L) + .put("um", 1L) + .put("uma", 1L) + .put("dois", 2L) + .put("duas", 2L) + .put("meia", 2L) + .put("meio", 2L) + .put("tres", 3L) + .put("três", 3L) + .put("quatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("sete", 7L) + .put("oito", 8L) + .put("nove", 9L) + .put("dez", 10L) + .put("dezena", 10L) + .put("déz", 10L) + .put("onze", 11L) + .put("doze", 12L) + .put("dúzia", 12L) + .put("duzia", 12L) + .put("dúzias", 12L) + .put("duzias", 12L) + .put("treze", 13L) + .put("catorze", 14L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("dezesseis", 16L) + .put("dezasseis", 16L) + .put("dezessete", 17L) + .put("dezassete", 17L) + .put("dezoito", 18L) + .put("dezenove", 19L) + .put("dezanove", 19L) + .put("vinte", 20L) + .put("trinta", 30L) + .put("quarenta", 40L) + .put("cinquenta", 50L) + .put("cincoenta", 50L) + .put("sessenta", 60L) + .put("setenta", 70L) + .put("oitenta", 80L) + .put("noventa", 90L) + .put("cem", 100L) + .put("cento", 100L) + .put("duzentos", 200L) + .put("duzentas", 200L) + .put("trezentos", 300L) + .put("trezentas", 300L) + .put("quatrocentos", 400L) + .put("quatrocentas", 400L) + .put("quinhentos", 500L) + .put("quinhentas", 500L) + .put("seiscentos", 600L) + .put("seiscentas", 600L) + .put("setecentos", 700L) + .put("setecentas", 700L) + .put("oitocentos", 800L) + .put("oitocentas", 800L) + .put("novecentos", 900L) + .put("novecentas", 900L) + .put("mil", 1000L) + .put("milhão", 1000000L) + .put("milhao", 1000000L) + .put("milhões", 1000000L) + .put("milhoes", 1000000L) + .put("bilhão", 1000000000L) + .put("bilhao", 1000000000L) + .put("bilhões", 1000000000L) + .put("bilhoes", 1000000000L) + .put("trilhão", 1000000000000L) + .put("trilhao", 1000000000000L) + .put("trilhões", 1000000000000L) + .put("trilhoes", 1000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("primeiro", 1L) + .put("primeira", 1L) + .put("segundo", 2L) + .put("segunda", 2L) + .put("terceiro", 3L) + .put("terceira", 3L) + .put("quarto", 4L) + .put("quarta", 4L) + .put("quinto", 5L) + .put("quinta", 5L) + .put("sexto", 6L) + .put("sexta", 6L) + .put("sétimo", 7L) + .put("setimo", 7L) + .put("sétima", 7L) + .put("setima", 7L) + .put("oitavo", 8L) + .put("oitava", 8L) + .put("nono", 9L) + .put("nona", 9L) + .put("décimo", 10L) + .put("decimo", 10L) + .put("décima", 10L) + .put("decima", 10L) + .put("undécimo", 11L) + .put("undecimo", 11L) + .put("undécima", 11L) + .put("undecima", 11L) + .put("duodécimo", 11L) + .put("duodecimo", 11L) + .put("duodécima", 11L) + .put("duodecima", 11L) + .put("vigésimo", 20L) + .put("vigesimo", 20L) + .put("vigésima", 20L) + .put("vigesima", 20L) + .put("trigésimo", 30L) + .put("trigesimo", 30L) + .put("trigésima", 30L) + .put("trigesima", 30L) + .put("quadragésimo", 40L) + .put("quadragesimo", 40L) + .put("quadragésima", 40L) + .put("quadragesima", 40L) + .put("quinquagésimo", 50L) + .put("quinquagesimo", 50L) + .put("quinquagésima", 50L) + .put("quinquagesima", 50L) + .put("sexagésimo", 60L) + .put("sexagesimo", 60L) + .put("sexagésima", 60L) + .put("sexagesima", 60L) + .put("septuagésimo", 70L) + .put("septuagesimo", 70L) + .put("septuagésima", 70L) + .put("septuagesima", 70L) + .put("setuagésimo", 70L) + .put("setuagesimo", 70L) + .put("setuagésima", 70L) + .put("setuagesima", 70L) + .put("octogésimo", 80L) + .put("octogesimo", 80L) + .put("octogésima", 80L) + .put("octogesima", 80L) + .put("nonagésimo", 90L) + .put("nonagesimo", 90L) + .put("nonagésima", 90L) + .put("nonagesima", 90L) + .put("centesimo", 100L) + .put("centésimo", 100L) + .put("centesima", 100L) + .put("centésima", 100L) + .put("ducentésimo", 200L) + .put("ducentesimo", 200L) + .put("ducentésima", 200L) + .put("ducentesima", 200L) + .put("tricentésimo", 300L) + .put("tricentesimo", 300L) + .put("tricentésima", 300L) + .put("tricentesima", 300L) + .put("trecentésimo", 300L) + .put("trecentesimo", 300L) + .put("trecentésima", 300L) + .put("trecentesima", 300L) + .put("quadringentésimo", 400L) + .put("quadringentesimo", 400L) + .put("quadringentésima", 400L) + .put("quadringentesima", 400L) + .put("quingentésimo", 500L) + .put("quingentesimo", 500L) + .put("quingentésima", 500L) + .put("quingentesima", 500L) + .put("sexcentésimo", 600L) + .put("sexcentesimo", 600L) + .put("sexcentésima", 600L) + .put("sexcentesima", 600L) + .put("seiscentésimo", 600L) + .put("seiscentesimo", 600L) + .put("seiscentésima", 600L) + .put("seiscentesima", 600L) + .put("septingentésimo", 700L) + .put("septingentesimo", 700L) + .put("septingentésima", 700L) + .put("septingentesima", 700L) + .put("setingentésimo", 700L) + .put("setingentesimo", 700L) + .put("setingentésima", 700L) + .put("setingentesima", 700L) + .put("octingentésimo", 800L) + .put("octingentesimo", 800L) + .put("octingentésima", 800L) + .put("octingentesima", 800L) + .put("noningentésimo", 900L) + .put("noningentesimo", 900L) + .put("noningentésima", 900L) + .put("noningentesima", 900L) + .put("nongentésimo", 900L) + .put("nongentesimo", 900L) + .put("nongentésima", 900L) + .put("nongentesima", 900L) + .put("milésimo", 1000L) + .put("milesimo", 1000L) + .put("milésima", 1000L) + .put("milesima", 1000L) + .put("milionésimo", 1000000L) + .put("milionesimo", 1000000L) + .put("milionésima", 1000000L) + .put("milionesima", 1000000L) + .put("bilionésimo", 1000000000L) + .put("bilionesimo", 1000000000L) + .put("bilionésima", 1000000000L) + .put("bilionesima", 1000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("hum", 1L) + .put("um", 1L) + .put("dois", 2L) + .put("tres", 3L) + .put("três", 3L) + .put("quatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("sete", 7L) + .put("oito", 8L) + .put("nove", 9L) + .put("dez", 10L) + .put("onze", 11L) + .put("doze", 12L) + .put("treze", 13L) + .put("catorze", 14L) + .put("quatorze", 14L) + .put("quinze", 15L) + .put("dezesseis", 16L) + .put("dezasseis", 16L) + .put("dezessete", 17L) + .put("dezassete", 17L) + .put("dezoito", 18L) + .put("dezenove", 19L) + .put("dezanove", 19L) + .put("vinte", 20L) + .put("trinta", 30L) + .put("quarenta", 40L) + .put("cinquenta", 50L) + .put("cincoenta", 50L) + .put("sessenta", 60L) + .put("setenta", 70L) + .put("oitenta", 80L) + .put("noventa", 90L) + .put("cem", 100L) + .put("duzentos", 200L) + .put("trezentos", 300L) + .put("quatrocentos", 400L) + .put("quinhentos", 500L) + .put("seiscentos", 600L) + .put("setecentos", 700L) + .put("oitocentos", 800L) + .put("novecentos", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("milesimo", 1000L) + .put("milionesimo", 1000000L) + .put("bilionesimo", 1000000000L) + .put("trilionesimo", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("mil", 1000L) + .put("milesimo", 1000L) + .put("milhão", 1000000L) + .put("milhao", 1000000L) + .put("milhões", 1000000L) + .put("milhoes", 1000000L) + .put("milionésimo", 1000000L) + .put("milionesimo", 1000000L) + .put("bilhão", 1000000000L) + .put("bilhao", 1000000000L) + .put("bilhões", 1000000000L) + .put("bilhoes", 1000000000L) + .put("bilionésimo", 1000000000L) + .put("bilionesimo", 1000000000L) + .put("trilhão", 1000000000000L) + .put("trilhao", 1000000000000L) + .put("trilhões", 1000000000000L) + .put("trilhoes", 1000000000000L) + .put("trilionésimo", 1000000000000L) + .put("trilionesimo", 1000000000000L) + .put("dezena", 10L) + .put("dezenas", 10L) + .put("dúzia", 12L) + .put("duzia", 12L) + .put("dúzias", 12L) + .put("duzias", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java new file mode 100644 index 000000000..c4940e4a2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/resources/SpanishNumeric.java @@ -0,0 +1,629 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.number.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishNumeric { + + public static final String LangMarker = "Spa"; + + public static final Boolean CompoundNumberLanguage = false; + + public static final Boolean MultiDecimalSeparatorCulture = true; + + public static final String HundredsNumberIntegerRegex = "(cuatrocient[ao]s|trescient[ao]s|seiscient[ao]s|setecient[ao]s|ochocient[ao]s|novecient[ao]s|doscient[ao]s|quinient[ao]s|(?(?({AllIntRegex})|((?({AllIntRegex})|((\\d+)(?!\\.)))(?=\\b)" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{BaseNumbers.CommonCurrencySymbol}", BaseNumbers.CommonCurrencySymbol); + + public static final String AllPointRegex = "((\\s+{ZeroToNineIntegerRegex})+|(\\s+{AllIntRegex}))" + .replace("{ZeroToNineIntegerRegex}", ZeroToNineIntegerRegex) + .replace("{AllIntRegex}", AllIntRegex); + + public static final String AllFloatRegex = "{AllIntRegex}(\\s+(coma|con)){AllPointRegex}" + .replace("{AllIntRegex}", AllIntRegex) + .replace("{AllPointRegex}", AllPointRegex); + + public static String DoubleDecimalPointRegex(String placeholder) { + return "(((?)"; + + public static final String LessRegex = "((meno(s|r(es)?)|inferior(es)?|por\\s+debajo)((\\s+(que|del?|al?)|(?=\\s+o\\b)))|más\\s+baj[oa]\\s+que|(?|=)<)"; + + public static final String EqualRegex = "((igual(es)?|equivalente(s)?|equivalen?)(\\s+(al?|que|del?))?|(?)=)"; + + public static final String MoreOrEqualPrefix = "((no\\s+{LessRegex})|(por\\s+lo\\s+menos|como\\s+m[íi]nimo|al\\s+menos))" + .replace("{LessRegex}", LessRegex); + + public static final String MoreOrEqual = "(({MoreRegex}\\s+(o)?\\s+{EqualRegex})|({EqualRegex}\\s+(o|y)\\s+{MoreRegex})|{MoreOrEqualPrefix}(\\s+(o)\\s+{EqualRegex})?|({EqualRegex}\\s+(o)\\s+)?{MoreOrEqualPrefix}|>\\s*=)" + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{MoreOrEqualPrefix}", MoreOrEqualPrefix); + + public static final String MoreOrEqualSuffix = "((\\b(y|o)\\b\\s+(m[áa]s|mayor(es)?|superior(es)?)((?!\\s+(alt[oa]|baj[oa]|que|del?|al?))|(\\s+(que|del?|al?)(?!(\\s*\\d+)))))|como\\s+m[íi]nimo|por\\s+lo\\s+menos|al\\s+menos)\\b"; + + public static final String LessOrEqualPrefix = "((no\\s+{MoreRegex})|(como\\s+(m[aá]ximo|mucho)))" + .replace("{MoreRegex}", MoreRegex); + + public static final String LessOrEqual = "(({LessRegex}\\s+(o)?\\s+{EqualRegex})|({EqualRegex}\\s+(o)?\\s+{LessRegex})|{LessOrEqualPrefix}(\\s+(o)?\\s+{EqualRegex})?|({EqualRegex}\\s+(o)?\\s+)?{LessOrEqualPrefix}|<\\s*=)" + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{LessOrEqualPrefix}", LessOrEqualPrefix); + + public static final String LessOrEqualSuffix = "((\\b(y|o)\\b\\s+(meno(s|r(es)?|inferior(es)?))((?!\\s+(alt[oa]|baj[oa]|que|del?|al?))|(\\s+(que|del?|al?)(?!(\\s*\\d+)))))|como\\s+m[áa]ximo)\\b"; + + public static final String NumberSplitMark = "(?![,.](?!\\d+))(?!\\s*\\b(((y|e)\\s+)?({LessRegex}|{MoreRegex}|{EqualRegex}|no|de)|pero|o|a)\\b)" + .replace("{LessRegex}", LessRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegex}", EqualRegex); + + public static final String MoreRegexNoNumberSucceed = "(\\b(m[áa]s|mayor(es)?|superior(es)?)((?!\\s+(que|del?|al?))|\\s+((que|del?)(?!(\\s*\\d+))))|(por encima)(?!(\\s*\\d+)))\\b"; + + public static final String LessRegexNoNumberSucceed = "(\\b(meno(s|r(es)?)|inferior(es)?)((?!\\s+(que|del?|al?))|\\s+((que|del?|al?)(?!(\\s*\\d+))))|(por debajo)(?!(\\s*\\d+)))\\b"; + + public static final String EqualRegexNoNumberSucceed = "(\\b(igual(es)?|equivalentes?|equivalen?)((?!\\s+(al?|que|del?))|(\\s+(al?|que|del?)(?!(\\s*\\d+)))))\\b"; + + public static final String OneNumberRangeMoreRegex1 = "({MoreOrEqual}|{MoreRegex})\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{MoreOrEqual}", MoreOrEqual) + .replace("{MoreRegex}", MoreRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreRegex1LB = "(?({NumberSplitMark}.)+)\\s*{MoreOrEqualSuffix}" + .replace("{MoreOrEqualSuffix}", MoreOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeMoreSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){MoreRegexNoNumberSucceed})|({MoreRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{MoreRegex}", MoreRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{MoreRegexNoNumberSucceed}", MoreRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1 = "({LessOrEqual}|{LessRegex})\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{LessOrEqual}", LessOrEqual) + .replace("{LessRegex}", LessRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessRegex1LB = "(?({NumberSplitMark}.)+)\\s*{LessOrEqualSuffix}" + .replace("{LessOrEqualSuffix}", LessOrEqualSuffix) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeLessSeparateRegex = "({EqualRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){LessRegexNoNumberSucceed})|({LessRegex}\\s+(?({NumberSplitMark}.)+)(\\s+o\\s+){EqualRegexNoNumberSucceed})" + .replace("{EqualRegex}", EqualRegex) + .replace("{LessRegex}", LessRegex) + .replace("{EqualRegexNoNumberSucceed}", EqualRegexNoNumberSucceed) + .replace("{LessRegexNoNumberSucceed}", LessRegexNoNumberSucceed) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String OneNumberRangeEqualRegex = "{EqualRegex}\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{EqualRegex}", EqualRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex1 = "\\bentre\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)\\s*y\\s*((el|las?|los)\\s+)?(?({NumberSplitMark}.)+)" + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String TwoNumberRangeRegex2 = "({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})\\s*(\\by\\b|\\be\\b|pero|,)\\s*({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex3 = "({OneNumberRangeLessRegex1}|{OneNumberRangeLessRegex2})\\s*(\\by\\b|\\be\\b|pero|,)\\s*({OneNumberRangeMoreRegex1}|{OneNumberRangeMoreRegex2})" + .replace("{OneNumberRangeMoreRegex1}", OneNumberRangeMoreRegex1) + .replace("{OneNumberRangeMoreRegex2}", OneNumberRangeMoreRegex2) + .replace("{OneNumberRangeLessRegex1}", OneNumberRangeLessRegex1) + .replace("{OneNumberRangeLessRegex2}", OneNumberRangeLessRegex2); + + public static final String TwoNumberRangeRegex4 = "(\\bde(sde)?\\s+)?(\\b(el|las?|los)\\s+)?\\b(?!\\s+)(?({NumberSplitMark}(?!\\b(entre|de(sde)?|es)\\b).)+)\\b\\s*{TillRegex}\\s*((el|las?|los)\\s+)?\\b(?!\\s+)(?({NumberSplitMark}.)+)\\b" + .replace("{TillRegex}", TillRegex) + .replace("{NumberSplitMark}", NumberSplitMark); + + public static final String AmbiguousFractionConnectorsRegex = "(\\b(en|de)\\b)"; + + public static final Character DecimalSeparatorChar = ','; + + public static final String FractionMarkerToken = "sobre"; + + public static final Character NonDecimalSeparatorChar = '.'; + + public static final String HalfADozenText = "seis"; + + public static final String WordSeparatorToken = "y"; + + public static final List WrittenDecimalSeparatorTexts = Arrays.asList("coma", "con"); + + public static final List WrittenGroupSeparatorTexts = Arrays.asList("punto"); + + public static final List WrittenIntegerSeparatorTexts = Arrays.asList("y"); + + public static final List WrittenFractionSeparatorTexts = Arrays.asList("con"); + + public static final String HalfADozenRegex = "media\\s+docena"; + + public static final String DigitalNumberRegex = "((?<=\\b)(mil(l[oó]n(es)?)?|bill[oó]n(es)?|trill[oó]n(es)?|docenas?)(?=\\b))|((?<=(\\d|\\b)){BaseNumbers.MultiplierLookupRegex}(?=\\b))" + .replace("{BaseNumbers.MultiplierLookupRegex}", BaseNumbers.MultiplierLookupRegex); + + public static final ImmutableMap CardinalNumberMap = ImmutableMap.builder() + .put("cero", 0L) + .put("un", 1L) + .put("una", 1L) + .put("uno", 1L) + .put("dos", 2L) + .put("tres", 3L) + .put("cuatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("siete", 7L) + .put("ocho", 8L) + .put("nueve", 9L) + .put("diez", 10L) + .put("once", 11L) + .put("doce", 12L) + .put("docena", 12L) + .put("docenas", 12L) + .put("trece", 13L) + .put("catorce", 14L) + .put("quince", 15L) + .put("dieciseis", 16L) + .put("dieciséis", 16L) + .put("diecisiete", 17L) + .put("dieciocho", 18L) + .put("diecinueve", 19L) + .put("veinte", 20L) + .put("ventiuna", 21L) + .put("ventiuno", 21L) + .put("veintiun", 21L) + .put("veintiún", 21L) + .put("veintiuno", 21L) + .put("veintiuna", 21L) + .put("veintidos", 22L) + .put("veintidós", 22L) + .put("veintitres", 23L) + .put("veintitrés", 23L) + .put("veinticuatro", 24L) + .put("veinticinco", 25L) + .put("veintiseis", 26L) + .put("veintiséis", 26L) + .put("veintisiete", 27L) + .put("veintiocho", 28L) + .put("veintinueve", 29L) + .put("treinta", 30L) + .put("cuarenta", 40L) + .put("cincuenta", 50L) + .put("sesenta", 60L) + .put("setenta", 70L) + .put("ochenta", 80L) + .put("noventa", 90L) + .put("cien", 100L) + .put("ciento", 100L) + .put("doscientas", 200L) + .put("doscientos", 200L) + .put("trescientas", 300L) + .put("trescientos", 300L) + .put("cuatrocientas", 400L) + .put("cuatrocientos", 400L) + .put("quinientas", 500L) + .put("quinientos", 500L) + .put("seiscientas", 600L) + .put("seiscientos", 600L) + .put("setecientas", 700L) + .put("setecientos", 700L) + .put("ochocientas", 800L) + .put("ochocientos", 800L) + .put("novecientas", 900L) + .put("novecientos", 900L) + .put("mil", 1000L) + .put("millon", 1000000L) + .put("millón", 1000000L) + .put("millones", 1000000L) + .put("billon", 1000000000000L) + .put("billón", 1000000000000L) + .put("billones", 1000000000000L) + .put("trillon", 1000000000000000000L) + .put("trillón", 1000000000000000000L) + .put("trillones", 1000000000000000000L) + .build(); + + public static final ImmutableMap OrdinalNumberMap = ImmutableMap.builder() + .put("primero", 1L) + .put("primera", 1L) + .put("primer", 1L) + .put("segundo", 2L) + .put("segunda", 2L) + .put("medio", 2L) + .put("media", 2L) + .put("tercero", 3L) + .put("tercera", 3L) + .put("tercer", 3L) + .put("tercio", 3L) + .put("cuarto", 4L) + .put("cuarta", 4L) + .put("quinto", 5L) + .put("quinta", 5L) + .put("sexto", 6L) + .put("sexta", 6L) + .put("septimo", 7L) + .put("septima", 7L) + .put("séptimo", 7L) + .put("séptima", 7L) + .put("octavo", 8L) + .put("octava", 8L) + .put("noveno", 9L) + .put("novena", 9L) + .put("decimo", 10L) + .put("décimo", 10L) + .put("decima", 10L) + .put("décima", 10L) + .put("undecimo", 11L) + .put("undecima", 11L) + .put("undécimo", 11L) + .put("undécima", 11L) + .put("duodecimo", 12L) + .put("duodecima", 12L) + .put("duodécimo", 12L) + .put("duodécima", 12L) + .put("decimotercero", 13L) + .put("decimotercera", 13L) + .put("decimocuarto", 14L) + .put("decimocuarta", 14L) + .put("decimoquinto", 15L) + .put("decimoquinta", 15L) + .put("decimosexto", 16L) + .put("decimosexta", 16L) + .put("decimoseptimo", 17L) + .put("decimoseptima", 17L) + .put("decimoctavo", 18L) + .put("decimoctava", 18L) + .put("decimonoveno", 19L) + .put("decimonovena", 19L) + .put("vigesimo", 20L) + .put("vigesima", 20L) + .put("vigésimo", 20L) + .put("vigésima", 20L) + .put("trigesimo", 30L) + .put("trigesima", 30L) + .put("trigésimo", 30L) + .put("trigésima", 30L) + .put("cuadragesimo", 40L) + .put("cuadragesima", 40L) + .put("cuadragésimo", 40L) + .put("cuadragésima", 40L) + .put("quincuagesimo", 50L) + .put("quincuagesima", 50L) + .put("quincuagésimo", 50L) + .put("quincuagésima", 50L) + .put("sexagesimo", 60L) + .put("sexagesima", 60L) + .put("sexagésimo", 60L) + .put("sexagésima", 60L) + .put("septuagesimo", 70L) + .put("septuagesima", 70L) + .put("septuagésimo", 70L) + .put("septuagésima", 70L) + .put("octogesimo", 80L) + .put("octogesima", 80L) + .put("octogésimo", 80L) + .put("octogésima", 80L) + .put("nonagesimo", 90L) + .put("nonagesima", 90L) + .put("nonagésimo", 90L) + .put("nonagésima", 90L) + .put("centesimo", 100L) + .put("centesima", 100L) + .put("centésimo", 100L) + .put("centésima", 100L) + .put("ducentesimo", 200L) + .put("ducentesima", 200L) + .put("ducentésimo", 200L) + .put("ducentésima", 200L) + .put("tricentesimo", 300L) + .put("tricentesima", 300L) + .put("tricentésimo", 300L) + .put("tricentésima", 300L) + .put("cuadringentesimo", 400L) + .put("cuadringentesima", 400L) + .put("cuadringentésimo", 400L) + .put("cuadringentésima", 400L) + .put("quingentesimo", 500L) + .put("quingentesima", 500L) + .put("quingentésimo", 500L) + .put("quingentésima", 500L) + .put("sexcentesimo", 600L) + .put("sexcentesima", 600L) + .put("sexcentésimo", 600L) + .put("sexcentésima", 600L) + .put("septingentesimo", 700L) + .put("septingentesima", 700L) + .put("septingentésimo", 700L) + .put("septingentésima", 700L) + .put("octingentesimo", 800L) + .put("octingentesima", 800L) + .put("octingentésimo", 800L) + .put("octingentésima", 800L) + .put("noningentesimo", 900L) + .put("noningentesima", 900L) + .put("noningentésimo", 900L) + .put("noningentésima", 900L) + .put("milesimo", 1000L) + .put("milesima", 1000L) + .put("milésimo", 1000L) + .put("milésima", 1000L) + .put("millonesimo", 1000000L) + .put("millonesima", 1000000L) + .put("millonésimo", 1000000L) + .put("millonésima", 1000000L) + .put("billonesimo", 1000000000000L) + .put("billonesima", 1000000000000L) + .put("billonésimo", 1000000000000L) + .put("billonésima", 1000000000000L) + .build(); + + public static final ImmutableMap PrefixCardinalMap = ImmutableMap.builder() + .put("dos", 2L) + .put("tres", 3L) + .put("cuatro", 4L) + .put("cinco", 5L) + .put("seis", 6L) + .put("siete", 7L) + .put("ocho", 8L) + .put("nueve", 9L) + .put("diez", 10L) + .put("once", 11L) + .put("doce", 12L) + .put("trece", 13L) + .put("catorce", 14L) + .put("quince", 15L) + .put("dieciseis", 16L) + .put("dieciséis", 16L) + .put("diecisiete", 17L) + .put("dieciocho", 18L) + .put("diecinueve", 19L) + .put("veinte", 20L) + .put("ventiuna", 21L) + .put("veintiun", 21L) + .put("veintiún", 21L) + .put("veintidos", 22L) + .put("veintitres", 23L) + .put("veinticuatro", 24L) + .put("veinticinco", 25L) + .put("veintiseis", 26L) + .put("veintisiete", 27L) + .put("veintiocho", 28L) + .put("veintinueve", 29L) + .put("treinta", 30L) + .put("cuarenta", 40L) + .put("cincuenta", 50L) + .put("sesenta", 60L) + .put("setenta", 70L) + .put("ochenta", 80L) + .put("noventa", 90L) + .put("cien", 100L) + .put("doscientos", 200L) + .put("trescientos", 300L) + .put("cuatrocientos", 400L) + .put("quinientos", 500L) + .put("seiscientos", 600L) + .put("setecientos", 700L) + .put("ochocientos", 800L) + .put("novecientos", 900L) + .build(); + + public static final ImmutableMap SuffixOrdinalMap = ImmutableMap.builder() + .put("milesimo", 1000L) + .put("millonesimo", 1000000L) + .put("billonesimo", 1000000000000L) + .build(); + + public static final ImmutableMap RoundNumberMap = ImmutableMap.builder() + .put("mil", 1000L) + .put("milesimo", 1000L) + .put("millon", 1000000L) + .put("millón", 1000000L) + .put("millones", 1000000L) + .put("millonesimo", 1000000L) + .put("billon", 1000000000000L) + .put("billón", 1000000000000L) + .put("billones", 1000000000000L) + .put("billonesimo", 1000000000000L) + .put("trillon", 1000000000000000000L) + .put("trillón", 1000000000000000000L) + .put("trillones", 1000000000000000000L) + .put("trillonesimo", 1000000000000000000L) + .put("docena", 12L) + .put("docenas", 12L) + .put("k", 1000L) + .put("m", 1000000L) + .put("g", 1000000000L) + .put("b", 1000000000L) + .put("t", 1000000000000L) + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("^[.]", "") + .build(); + + public static final ImmutableMap RelativeReferenceOffsetMap = ImmutableMap.builder() + .put("", "") + .build(); + + public static final ImmutableMap RelativeReferenceRelativeToMap = ImmutableMap.builder() + .put("", "") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java new file mode 100644 index 000000000..661fc05f5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/CardinalExtractor.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class CardinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_CARDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static CardinalExtractor getInstance() { + return getInstance(SpanishNumeric.PlaceHolderDefault); + } + + public static CardinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + CardinalExtractor instance = new CardinalExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + private CardinalExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + //Add Integer Regexes + IntegerExtractor intExtract = new IntegerExtractor(placeholder); + builder.putAll(intExtract.getRegexes()); + + //Add Double Regexes + DoubleExtractor douExtract = new DoubleExtractor(placeholder); + builder.putAll(douExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java new file mode 100644 index 000000000..f088ab627 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/DoubleExtractor.java @@ -0,0 +1,47 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class DoubleExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_DOUBLE; + } + + public DoubleExtractor() { + this(SpanishNumeric.PlaceHolderDefault); + } + + public DoubleExtractor(String placeholder) { + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleDecimalPointRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithoutIntegralRegex(placeholder), Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithMultiplierRegex), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleWithRoundNumber, Pattern.UNICODE_CHARACTER_CLASS), "DoubleNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleAllFloatRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoubleSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DoubleCaretExponentialNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "DoublePow"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumDotComma, placeholder), "DoubleNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.DoubleNumNoBreakSpaceComma, placeholder), "DoubleNum"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java new file mode 100644 index 000000000..0b319868c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/FractionExtractor.java @@ -0,0 +1,42 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public class FractionExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_FRACTION; + } + + public FractionExtractor(NumberMode mode) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNotationRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNotationWithSpacesRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionNounWithArticleRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + if (mode != NumberMode.Unit) { + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS), "FracSpa"); + } + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java new file mode 100644 index 000000000..3ee5f10ad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/IntegerExtractor.java @@ -0,0 +1,64 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.LongFormatType; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class IntegerExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_INTEGER; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static IntegerExtractor getInstance() { + return getInstance(SpanishNumeric.PlaceHolderDefault); + } + + public static IntegerExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + IntegerExtractor instance = new IntegerExtractor(placeholder); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + public IntegerExtractor() { + this(SpanishNumeric.PlaceHolderDefault); + } + + public IntegerExtractor(String placeholder) { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithPlaceHolder(placeholder), Pattern.UNICODE_CHARACTER_CLASS) , "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithSuffix), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumDot, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumBlank, placeholder), "IntegerNum"); + builder.put(generateLongFormatNumberRegexes(LongFormatType.IntegerNumNoBreakSpace, placeholder), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.RoundNumberIntegerRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NumbersWithDozenSuffix, Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.AllIntRegexWithLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerSpa"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.AllIntRegexWithDozenSuffixLocks, Pattern.UNICODE_CHARACTER_CLASS), "IntegerSpa"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java new file mode 100644 index 000000000..1eb2385dc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/NumberExtractor.java @@ -0,0 +1,104 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import static com.microsoft.recognizers.text.number.NumberMode.Default; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.BaseNumbers; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public class NumberExtractor extends BaseNumberExtractor { + + private final Map regexes; + private final Map ambiguityFiltersDict; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected Map getAmbiguityFiltersDict() { + return this.ambiguityFiltersDict; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM; + } + + private static final ConcurrentHashMap, NumberExtractor> instances = new ConcurrentHashMap<>(); + + public static NumberExtractor getInstance(NumberOptions options) { + return getInstance(NumberMode.Default, options); + } + + public static NumberExtractor getInstance(NumberMode mode) { + return getInstance(mode, NumberOptions.None); + } + + public static NumberExtractor getInstance() { + return getInstance(NumberMode.Default, NumberOptions.None); + } + + public static NumberExtractor getInstance(NumberMode mode, NumberOptions options) { + Pair key = Pair.with(mode, options); + if (!instances.containsKey(key)) { + NumberExtractor instance = new NumberExtractor(mode, options); + instances.put(key, instance); + } + + return instances.get(key); + } + + private NumberExtractor(NumberMode mode, NumberOptions options) { + HashMap builder = new HashMap<>(); + + //Add Cardinal + CardinalExtractor cardExtract = null; + switch (mode) { + case PureNumber: + cardExtract = CardinalExtractor.getInstance(SpanishNumeric.PlaceHolderPureNumber); + break; + case Currency: + builder.put(Pattern.compile(BaseNumbers.CurrencyRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS), "IntegerNum"); + break; + case Default: + break; + default: + break; + } + + if (cardExtract == null) { + cardExtract = CardinalExtractor.getInstance(); + } + + builder.putAll(cardExtract.getRegexes()); + + //Add Fraction + FractionExtractor fracExtract = new FractionExtractor(mode); + builder.putAll(fracExtract.getRegexes()); + + this.regexes = Collections.unmodifiableMap(builder); + + HashMap ambiguityFiltersDict = new HashMap<>(); + if (mode != NumberMode.Unit) { + for (Map.Entry pair : SpanishNumeric.AmbiguityFiltersDict.entrySet()) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + this.ambiguityFiltersDict = ambiguityFiltersDict; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java new file mode 100644 index 000000000..dd6003699 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/OrdinalExtractor.java @@ -0,0 +1,52 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.Constants; +import com.microsoft.recognizers.text.number.extractors.BaseNumberExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.regex.Pattern; + +public class OrdinalExtractor extends BaseNumberExtractor { + + private final Map regexes; + + @Override + protected Map getRegexes() { + return this.regexes; + } + + @Override + protected String getExtractType() { + return Constants.SYS_NUM_ORDINAL; + } + + private static final ConcurrentHashMap instances = new ConcurrentHashMap<>(); + + public static OrdinalExtractor getInstance() { + return getInstance(""); + } + + private static OrdinalExtractor getInstance(String placeholder) { + if (!instances.containsKey(placeholder)) { + OrdinalExtractor instance = new OrdinalExtractor(); + instances.put(placeholder, instance); + } + + return instances.get(placeholder); + } + + public OrdinalExtractor() { + + HashMap builder = new HashMap<>(); + + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.OrdinalSuffixRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalNum"); + builder.put(RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.OrdinalNounRegex, Pattern.UNICODE_CHARACTER_CLASS), "OrdinalSpa"); + + this.regexes = Collections.unmodifiableMap(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java new file mode 100644 index 000000000..ed3f5e5ab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/extractors/PercentageExtractor.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.number.spanish.extractors; + +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.extractors.BasePercentageExtractor; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class PercentageExtractor extends BasePercentageExtractor { + + private final NumberOptions options; + private final Set regexes; + + @Override + protected NumberOptions getOptions() { + return this.options; + } + + @Override + protected Set getRegexes() { + return this.regexes; + } + + public PercentageExtractor() { + this(NumberOptions.None); + } + + public PercentageExtractor(NumberOptions options) { + super(NumberExtractor.getInstance(options)); + this.options = options; + + Set builder = new HashSet<>(); + builder.add(SpanishNumeric.NumberWithPrefixPercentage); + this.regexes = buildRegexes(builder); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java new file mode 100644 index 000000000..38ced7f6b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/number/spanish/parsers/SpanishNumberParserConfiguration.java @@ -0,0 +1,129 @@ +package com.microsoft.recognizers.text.number.spanish.parsers; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.number.NumberOptions; +import com.microsoft.recognizers.text.number.parsers.BaseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.resources.SpanishNumeric; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class SpanishNumberParserConfiguration extends BaseNumberParserConfiguration { + + public SpanishNumberParserConfiguration() { + this(NumberOptions.None); + } + + public SpanishNumberParserConfiguration(NumberOptions options) { + this(new CultureInfo(Culture.Spanish), options); + } + + public SpanishNumberParserConfiguration(CultureInfo cultureInfo, NumberOptions options) { + + super( + SpanishNumeric.LangMarker, + cultureInfo, + SpanishNumeric.CompoundNumberLanguage, + SpanishNumeric.MultiDecimalSeparatorCulture, + options, + SpanishNumeric.NonDecimalSeparatorChar, + SpanishNumeric.DecimalSeparatorChar, + SpanishNumeric.FractionMarkerToken, + SpanishNumeric.HalfADozenText, + SpanishNumeric.WordSeparatorToken, + SpanishNumeric.WrittenDecimalSeparatorTexts, + SpanishNumeric.WrittenGroupSeparatorTexts, + SpanishNumeric.WrittenIntegerSeparatorTexts, + SpanishNumeric.WrittenFractionSeparatorTexts, + SpanishNumeric.CardinalNumberMap, + buildOrdinalNumberMap(), + SpanishNumeric.RoundNumberMap, + + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.HalfADozenRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.DigitalNumberRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.NegativeNumberSignRegex, Pattern.UNICODE_CHARACTER_CLASS), + RegExpUtility.getSafeLookbehindRegExp(SpanishNumeric.FractionPrepositionRegex, Pattern.UNICODE_CHARACTER_CLASS)); + } + + @Override + public List normalizeTokenSet(List tokens, ParseResult context) { + + List result = new ArrayList<>(); + + for (String token : tokens) { + String tempWord = QueryProcessor.trimEnd(token, "s"); + if (this.getOrdinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } + + if (tempWord.endsWith("avo") || tempWord.endsWith("ava")) { + String origTempWord = tempWord; + int newLength = origTempWord.length(); + tempWord = origTempWord.substring(0, newLength - 3); + if (this.getCardinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } else { + tempWord = origTempWord.substring(0, newLength - 2); + if (this.getCardinalNumberMap().containsKey(tempWord)) { + result.add(tempWord); + continue; + } + } + } + + result.add(token); + } + + return result; + } + + @Override + public long resolveCompositeNumber(String numberStr) { + if (this.getOrdinalNumberMap().containsKey(numberStr)) { + return this.getOrdinalNumberMap().get(numberStr); + } + + if (this.getCardinalNumberMap().containsKey(numberStr)) { + return this.getCardinalNumberMap().get(numberStr); + } + + long value = 0; + long finalValue = 0; + StringBuilder strBuilder = new StringBuilder(); + int lastGoodChar = 0; + for (int i = 0; i < numberStr.length(); i++) { + strBuilder.append(numberStr.charAt(i)); + if (this.getCardinalNumberMap().containsKey(strBuilder.toString()) && this.getCardinalNumberMap().get(strBuilder.toString()) > value) { + lastGoodChar = i; + value = this.getCardinalNumberMap().get(strBuilder.toString()); + } + if ((i + 1) == numberStr.length()) { + finalValue += value; + strBuilder = new StringBuilder(); + i = lastGoodChar++; + value = 0; + } + } + return finalValue; + } + + private static Map buildOrdinalNumberMap() { + ImmutableMap.Builder ordinalNumberMapBuilder = new ImmutableMap.Builder() + .putAll(SpanishNumeric.OrdinalNumberMap); + SpanishNumeric.SuffixOrdinalMap.forEach((sufixKey, sufixValue) -> + SpanishNumeric.PrefixCardinalMap.forEach((prefixKey, prefixValue) -> + ordinalNumberMapBuilder.put(prefixKey + sufixKey, prefixValue * sufixValue))); + + return ordinalNumberMapBuilder.build(); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java new file mode 100644 index 000000000..1ebe407e2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/Constants.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit; + +public class Constants { + public static final String SYS_UNIT_DIMENSION = "builtin.unit.dimension"; + public static final String SYS_UNIT = "builtin.unit"; + public static final String SYS_UNIT_AGE = "builtin.unit.age"; + public static final String SYS_UNIT_AREA = "builtin.unit.area"; + public static final String SYS_UNIT_CURRENCY = "builtin.unit.currency"; + public static final String SYS_UNIT_LENGTH = "builtin.unit.length"; + public static final String SYS_UNIT_SPEED = "builtin.unit.speed"; + public static final String SYS_UNIT_TEMPERATURE = "builtin.unit.temperature"; + public static final String SYS_UNIT_VOLUME = "builtin.unit.volume"; + public static final String SYS_UNIT_WEIGHT = "builtin.unit.weight"; + public static final String SYS_NUM = "builtin.num"; + + // For currencies without ISO codes, we use internal values prefixed by '_'. + // These values should never be present in parse output. + public static final String FAKE_ISO_CODE_PREFIX = "_"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java new file mode 100644 index 000000000..5cb560399 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitOptions.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.numberwithunit; + +public enum NumberWithUnitOptions { + None(0); + + private final int value; + + NumberWithUnitOptions(int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java new file mode 100644 index 000000000..44d5caf93 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/NumberWithUnitRecognizer.java @@ -0,0 +1,256 @@ +package com.microsoft.recognizers.text.numberwithunit; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.Recognizer; +import com.microsoft.recognizers.text.numberwithunit.extractors.BaseMergedUnitExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.NumberWithUnitExtractor; +import com.microsoft.recognizers.text.numberwithunit.models.AgeModel; +import com.microsoft.recognizers.text.numberwithunit.models.CurrencyModel; +import com.microsoft.recognizers.text.numberwithunit.models.DimensionModel; +import com.microsoft.recognizers.text.numberwithunit.models.TemperatureModel; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseMergedUnitParser; +import com.microsoft.recognizers.text.numberwithunit.parsers.NumberWithUnitParser; + +import java.util.List; +import java.util.function.Function; + +public class NumberWithUnitRecognizer extends Recognizer { + + public NumberWithUnitRecognizer() { + this(null, NumberWithUnitOptions.None, true); + } + + public NumberWithUnitRecognizer(String culture) { + this(culture, NumberWithUnitOptions.None, false); + } + + public NumberWithUnitRecognizer(NumberWithUnitOptions options) { + this(null, options, true); + } + + public NumberWithUnitRecognizer(NumberWithUnitOptions options, boolean lazyInitialization) { + this(null, options, lazyInitialization); + } + + public NumberWithUnitRecognizer(String culture, NumberWithUnitOptions options, boolean lazyInitialization) { + super(culture, options, lazyInitialization); + } + + public CurrencyModel getCurrencyModel() { + return getCurrencyModel(null, true); + } + + public CurrencyModel getCurrencyModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(CurrencyModel.class, culture, fallbackToDefaultCulture); + } + + public TemperatureModel getTemperatureModel() { + return getTemperatureModel(null, true); + } + + public TemperatureModel getTemperatureModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(TemperatureModel.class, culture, fallbackToDefaultCulture); + } + + public AgeModel getAgeModel() { + return getAgeModel(null, true); + } + + public AgeModel getAgeModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(AgeModel.class, culture, fallbackToDefaultCulture); + } + + public DimensionModel getDimensionModel() { + return getDimensionModel(null, true); + } + + public DimensionModel getDimensionModel(String culture, boolean fallbackToDefaultCulture) { + return getModel(DimensionModel.class, culture, fallbackToDefaultCulture); + } + + //region Helper methods for less verbosity + public static List recognizeCurrency(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeCurrency(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, true), query, options); + } + + public static List recognizeCurrency(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getCurrencyModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeTemperature(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeTemperature(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, true), query, options); + } + + public static List recognizeTemperature(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getTemperatureModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeAge(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeAge(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, true), query, options); + } + + public static List recognizeAge(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getAgeModel(culture, fallbackToDefaultCulture), query, options); + } + + public static List recognizeDimension(String query, String culture) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, true), query, NumberWithUnitOptions.None); + } + + public static List recognizeDimension(String query, String culture, NumberWithUnitOptions options) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, true), query, options); + } + + public static List recognizeDimension(String query, String culture, NumberWithUnitOptions options, boolean fallbackToDefaultCulture) { + return recognizeByModel(recognizer -> recognizer.getDimensionModel(culture, fallbackToDefaultCulture), query, options); + } + //endregion + + private static List recognizeByModel(Function getModelFun, String query, NumberWithUnitOptions options) { + NumberWithUnitRecognizer recognizer = new NumberWithUnitRecognizer(options); + IModel model = getModelFun.apply(recognizer); + return model.parse(query); + } + + @Override + protected void initializeConfiguration() { + + //region English + registerModel(CurrencyModel.class, Culture.English, (options) -> + new CurrencyModel(ImmutableMap.of( + new BaseMergedUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration()), + new BaseMergedUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.English, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.English, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.English, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.AgeParserConfiguration())))); + //endregion + + //region Spanish + registerModel(CurrencyModel.class, Culture.Spanish, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Spanish, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Spanish, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Spanish, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.spanish.parsers.AgeParserConfiguration())))); + //endregion + + //region Portuguese + registerModel(CurrencyModel.class, Culture.Portuguese, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Portuguese, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Portuguese, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Portuguese, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.portuguese.parsers.AgeParserConfiguration())))); + //endregion + + //region French + registerModel(CurrencyModel.class, Culture.French, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.French, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.French, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.French, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.french.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.french.parsers.AgeParserConfiguration())))); + //endregion + + //region German + registerModel(CurrencyModel.class, Culture.German, (options) -> + new CurrencyModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.German, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.German, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.German, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.german.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.german.parsers.AgeParserConfiguration())))); + //endregion + + + //region Chinese + registerModel(CurrencyModel.class, Culture.Chinese, (options) -> + new CurrencyModel(ImmutableMap.of( + new BaseMergedUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.CurrencyExtractorConfiguration()), + new BaseMergedUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.CurrencyParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.CurrencyParserConfiguration())))); + registerModel(TemperatureModel.class, Culture.Chinese, (options) -> + new TemperatureModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.TemperatureParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.TemperatureParserConfiguration())))); + registerModel(DimensionModel.class, Culture.Chinese, (options) -> + new DimensionModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.DimensionParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.DimensionParserConfiguration())))); + registerModel(AgeModel.class, Culture.Chinese, (options) -> + new AgeModel(ImmutableMap.of( + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.chinese.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.chinese.parsers.AgeParserConfiguration()), + new NumberWithUnitExtractor(new com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration()), + new NumberWithUnitParser(new com.microsoft.recognizers.text.numberwithunit.english.parsers.AgeParserConfiguration())))); + //endregion + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..722dc26f7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.AgeAmbiguousValues; + } + + public static Map AgeSuffixList = ChineseNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..fa27f0291 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/ChineseNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,112 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.chinese.ChineseNumberExtractorMode; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class ChineseNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + private final Pattern halfUnitRegex = Pattern.compile(ChineseNumericWithUnit.HalfUnitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected ChineseNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = new NumberExtractor(ChineseNumberExtractorMode.ExtractAll); + this.compoundUnitConnectorRegex = + Pattern.compile(ChineseNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(ChineseNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return ChineseNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return ChineseNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ChineseNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public Pattern getHalfUnitRegex() { + return Pattern.compile(ChineseNumericWithUnit.HalfUnitRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + // Expand Chinese phrase to the `half` patterns when it follows closely origin phrase. + if (halfUnitRegex != null) { + Match[] match = RegExpUtility.getMatches(halfUnitRegex, source); + if (match.length > 0) { + List res = new ArrayList<>(); + for (ExtractResult er : result) { + int start = er.getStart(); + int length = er.getLength(); + List matchSuffix = new ArrayList<>(); + for (Match mr : match) { + if (mr.index == (start + length)) { + ExtractResult m = new ExtractResult(mr.index, mr.length, mr.value, numbers.get(0).getType(), numbers.get(0).getData()); + matchSuffix.add(m); + } + } + if (matchSuffix.size() == 1) { + ExtractResult mr = matchSuffix.get(0); + er.setStart(er.getLength() + mr.getLength()); + er.setText(er.getText() + mr.getText()); + List tmp = new ArrayList<>(); + tmp.add((ExtractResult)er.getData()); + tmp.add(mr); + er.setData(tmp); + } + res.add(er); + } + result = res; + } + } + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..2c6263440 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.CurrencyAmbiguousValues; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = ChineseNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = ChineseNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..af30286c3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.DimensionAmbiguousValues; + } + + public static Map DimensionSuffixList = ChineseNumericWithUnit.DimensionSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..b5e265aad --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,58 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends ChineseNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return TemperaturePrefixList; + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return ChineseNumericWithUnit.TemperatureAmbiguousValues; + } + + public static Map TemperatureSuffixList = ChineseNumericWithUnit.TemperatureSuffixList; + + public static Map TemperaturePrefixList = ChineseNumericWithUnit.TemperaturePrefixList; + + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..1d6088fc5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..01783b66c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/ChineseNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.chinese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.chinese.parsers.ChineseNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public class ChineseNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IExtractor internalNumberExtractor; + private final IParser internalNumberParser; + private final String connectorToken; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return this.connectorToken; + } + + public ChineseNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = new NumberExtractor(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new ChineseNumberParserConfiguration()); + this.connectorToken = ""; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..3d2bf08e7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,32 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.CurrencyExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.ChineseNumericWithUnit; + +import java.util.Map; + +public class CurrencyParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return ChineseNumericWithUnit.CurrencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return ChineseNumericWithUnit.FractionalUnitNameToCodeMap; + } + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..0e9011fa4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..1e9a0ed62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/chinese/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.chinese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.chinese.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends ChineseNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Chinese)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..b48a2d12c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,45 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return AgePrefixList; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = EnglishNumericWithUnit.AgeSuffixList; + + public static Map AgePrefixList = EnglishNumericWithUnit.AgePrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..255969e3e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = EnglishNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..f4b00fa87 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = EnglishNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = EnglishNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..bd13a1589 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(EnglishNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..de4892583 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/EnglishNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class EnglishNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected EnglishNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(EnglishNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(EnglishNumericWithUnit.AmbiguityFiltersDict); + + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return EnglishNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return EnglishNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ""; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..cd1c8fd7d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = EnglishNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..3045d7042 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = EnglishNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..ae04773d5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousTemperatureUnitList; + } + + public static Map TemperatureSuffixList = new HashMap(EnglishNumericWithUnit.TemperatureSuffixList); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5e6c50543 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = EnglishNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..37d96067a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.english.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends EnglishNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return EnglishNumericWithUnit.AmbiguousWeightUnitList; + } + + public static Map WeightSuffixList = EnglishNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..83fd17503 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + this.bindDictionary(AgeExtractorConfiguration.AgePrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..1ab718900 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..4448e9f5f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,32 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.CurrencyExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; + +import java.util.Map; + +public class CurrencyParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return EnglishNumericWithUnit.CurrencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return EnglishNumericWithUnit.FractionalUnitNameToCodeMap; + } + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..e0df165af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..9b915d91d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/EnglishNumberWithUnitParserConfiguration.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.english.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.english.parsers.EnglishNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public abstract class EnglishNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return ""; + } + + public EnglishNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new EnglishNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..dc93b9db7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..567c1deba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..ed4ee83c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..6515fecbc --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..605661faa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/english/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.english.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.english.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends EnglishNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.English)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java new file mode 100644 index 000000000..07882ddb4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/BaseMergedUnitExtractor.java @@ -0,0 +1,185 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.google.common.collect.Lists; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.numberwithunit.Constants; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.regex.Matcher; +import java.util.stream.Collectors; + +public class BaseMergedUnitExtractor implements IExtractor { + + private final INumberWithUnitExtractorConfiguration config; + + public BaseMergedUnitExtractor(INumberWithUnitExtractorConfiguration config) { + this.config = config; + } + + @Override + public List extract(String source) { + // Only merge currency's compound units for now. + if (config.getExtractType().equals(Constants.SYS_UNIT_CURRENCY)) { + return mergeCompoundUnits(source); + } else { + return new NumberWithUnitExtractor(config).extract(source); + } + } + + @SuppressWarnings("unchecked") + private List mergeCompoundUnits(String source) { + List ers = new NumberWithUnitExtractor(config).extract(source); + mergePureNumber(source, ers); + + if (ers.size() == 0) { + return ers; + } + + List result = new ArrayList<>(); + int[] groups = new int[ers.size()]; + groups[0] = 0; + for (int idx = 0; idx < ers.size() - 1; idx++) { + if (!ers.get(idx).getType().equals(ers.get(idx + 1).getType()) && + !ers.get(idx).getType().equals(Constants.SYS_NUM) && + !ers.get(idx + 1).getType().equals(Constants.SYS_NUM)) { + continue; + } + + if (ers.get(idx).getData() instanceof ExtractResult && !((ExtractResult)ers.get(idx).getData()).getData().toString().startsWith("Integer")) { + groups[idx + 1] = groups[idx] + 1; + continue; + } + + int middleBegin = ers.get(idx).getStart() + ers.get(idx).getLength(); + int middleEnd = ers.get(idx + 1).getStart(); + + String middleStr = source.substring(middleBegin, middleEnd).trim().toLowerCase(); + + // Separated by whitespace + if (middleStr.isEmpty()) { + groups[idx + 1] = groups[idx]; + continue; + } + + // Separated by connectors + Matcher match = config.getCompoundUnitConnectorRegex().matcher(middleStr); + if (match.find() && match.start() == 0 && (match.end() - match.start()) == middleStr.length()) { + groups[idx + 1] = groups[idx]; + } else { + groups[idx + 1] = groups[idx] + 1; + } + } + + for (int idx = 0; idx < ers.size(); idx++) { + if (idx == 0 || groups[idx] != groups[idx - 1]) { + ExtractResult tmpExtractResult = ers.get(idx); + tmpExtractResult.setData(Lists.newArrayList( + new ExtractResult( + tmpExtractResult.getStart(), + tmpExtractResult.getLength(), + tmpExtractResult.getText(), + tmpExtractResult.getType(), + tmpExtractResult.getData()))); + + ers.set(idx, tmpExtractResult); + result.add(tmpExtractResult); + } + + // Reduce extract results in same group + if (idx + 1 < ers.size() && groups[idx + 1] == groups[idx]) { + int group = groups[idx]; + + int periodBegin = result.get(group).getStart(); + int periodEnd = ers.get(idx + 1).getStart() + ers.get(idx + 1).getLength(); + + ExtractResult r = result.get(group); + + List data = (List)r.getData(); + data.add(ers.get(idx + 1)); + r.setLength(periodEnd - periodBegin); + r.setText(source.substring(periodBegin, periodEnd)); + r.setType(Constants.SYS_UNIT_CURRENCY); + r.setData(data); + + result.set(group, r); + } + } + + for (int idx = 0; idx < result.size(); idx++) { + if (result.get(idx).getData() instanceof List) { + List innerData = (List)result.get(idx).getData(); + if (innerData.size() == 1) { + result.set(idx, innerData.get(0)); + } + } + } + + result = result.stream().filter(o -> !o.getType().equals(Constants.SYS_NUM)) + .collect(Collectors.toList()); + + return result; + } + + private void mergePureNumber(String source, List ers) { + + List numErs = config.getUnitNumExtractor().extract(source); + List unitNumbers = new ArrayList<>(); + for (int i = 0, j = 0; i < numErs.size(); i++) { + boolean hasBehindExtraction = false; + while (j < ers.size() && ers.get(j).getStart() + ers.get(j).getLength() < numErs.get(i).getStart()) { + hasBehindExtraction = true; + j++; + } + + if (!hasBehindExtraction) { + continue; + } + + int middleBegin = ers.get(j - 1).getStart() + ers.get(j - 1).getLength(); + int middleEnd = numErs.get(i).getStart(); + + String middleStr = source.substring(middleBegin, middleEnd).trim().toLowerCase(); + + // Separated by whitespace + if (middleStr.isEmpty()) { + unitNumbers.add(numErs.get(i)); + continue; + } + + // Separated by connectors + Matcher match = config.getCompoundUnitConnectorRegex().matcher(middleStr); + if (match.find()) { + int start = match.start(); + int end = match.end(); + int length = end - start; + + if (start == 0 && length == middleStr.length()) { + unitNumbers.add(numErs.get(i)); + } + } + } + + for (ExtractResult extractResult : unitNumbers) { + boolean overlap = false; + for (ExtractResult er : ers) { + if (er.getStart() <= extractResult.getStart() && er.getStart() + er.getLength() >= extractResult.getStart()) { + overlap = true; + } + } + + if (!overlap) { + ers.add(extractResult); + } + } + + Collections.sort(ers, (Comparator)(xo, yo) -> { + ExtractResult x = (ExtractResult)xo; + ExtractResult y = (ExtractResult)yo; + return x.getStart() - y.getStart(); + }); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..45d80dd7d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/INumberWithUnitExtractorConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public interface INumberWithUnitExtractorConfiguration { + + Map getSuffixList(); + + Map getPrefixList(); + + List getAmbiguousUnitList(); + + String getExtractType(); + + CultureInfo getCultureInfo(); + + IExtractor getUnitNumExtractor(); + + String getBuildPrefix(); + + String getBuildSuffix(); + + String getConnectorToken(); + + Pattern getCompoundUnitConnectorRegex(); + + Pattern getAmbiguousUnitNumberMultiplierRegex(); + + Map getAmbiguityFiltersDict(); + + List expandHalfSuffix(String source, List result, List numbers); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java new file mode 100644 index 000000000..e3a9e3b62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/extractors/NumberWithUnitExtractor.java @@ -0,0 +1,427 @@ +package com.microsoft.recognizers.text.numberwithunit.extractors; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.PrefixUnitResult; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.utilities.StringComparer; +import com.microsoft.recognizers.text.utilities.Match; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import com.microsoft.recognizers.text.utilities.RegExpUtility; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.regex.MatchResult; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class NumberWithUnitExtractor implements IExtractor { + + private final INumberWithUnitExtractorConfiguration config; + + private final Set suffixRegexes; + private final Set prefixRegexes; + + private final Pattern separateRegex; + private final Pattern singleCharUnitRegex = Pattern.compile(BaseUnits.SingleCharUnitRegex, Pattern.UNICODE_CHARACTER_CLASS); + + private final int maxPrefixMatchLen; + + private final List separators = Arrays.asList("|"); + + public NumberWithUnitExtractor(INumberWithUnitExtractorConfiguration config) { + this.config = config; + + if (config.getSuffixList() != null && config.getSuffixList().size() > 0) { + suffixRegexes = buildRegexFromSet(this.config.getSuffixList().values()); + } else { + suffixRegexes = new HashSet<>(); + } + + int tempMaxPrefixMatchLen = 0; + if (this.config.getPrefixList() != null && this.config.getPrefixList().size() > 0) { + for (String preMatch : this.config.getPrefixList().values()) { + List matchList = QueryProcessor.split(preMatch, separators); + for (String match : matchList) { + tempMaxPrefixMatchLen = tempMaxPrefixMatchLen >= match.length() ? tempMaxPrefixMatchLen : match.length(); + } + } + + // 2 is the maximum length of spaces. + tempMaxPrefixMatchLen += 2; + maxPrefixMatchLen = tempMaxPrefixMatchLen; + prefixRegexes = buildRegexFromSet(this.config.getPrefixList().values()); + } else { + maxPrefixMatchLen = 0; + prefixRegexes = new HashSet<>(); + } + + separateRegex = buildSeparateRegexFromSet(); + } + + @Override + public List extract(String source) { + List result = new ArrayList<>(); + + if (!preCheckStr(source)) { + return result; + } + + Map mappingPrefix = new HashMap(); + boolean[] matched = new boolean[source.length()]; + Arrays.fill(matched, false); + List numbers = this.config.getUnitNumExtractor().extract(source); + int sourceLen = source.length(); + + List prefixMatch = new ArrayList(); + List suffixMatch = new ArrayList(); + + for (Pattern regex : prefixRegexes) { + Matcher match = regex.matcher(source); + if (match.find()) { + prefixMatch.add(match); + } + } + + for (Pattern regex : suffixRegexes) { + Matcher match = regex.matcher(source); + if (match.find()) { + suffixMatch.add(match); + } + } + + if (numbers.size() > 0 && this.config.getExtractType() == Constants.SYS_UNIT_CURRENCY && prefixMatch.size() > 0 && suffixMatch.size() > 0) { + + for (ExtractResult number : numbers) { + int start = number.getStart(); + int length = number.getLength(); + Boolean numberPrefix = false; + Boolean numberSuffix = false; + + for (Matcher match : prefixMatch) { + if (match.end() == start) { + numberPrefix = true; + } + } + + for (Matcher match : suffixMatch) { + if (start + length == match.start()) { + numberSuffix = true; + } + } + + if (numberPrefix && numberSuffix && number.getText().contains(",")) { + int commaIndex = start + number.getText().indexOf(","); + source = source.substring(0, commaIndex) + " " + source.substring(commaIndex + 1); + } + } + numbers = this.config.getUnitNumExtractor().extract(source); + } + + /* Special case for cases where number multipliers clash with unit */ + Pattern ambiguousMultiplierRegex = this.config.getAmbiguousUnitNumberMultiplierRegex(); + if (ambiguousMultiplierRegex != null) { + for (int i = 0; i < numbers.size(); i++) { + ExtractResult number = numbers.get(i); + + Match[] matches = RegExpUtility.getMatches(ambiguousMultiplierRegex, number.getText()); + if (matches.length == 1) { + int newLength = number.getLength() - matches[0].length; + numbers.set(i, new ExtractResult(number.getStart(), newLength, number.getText().substring(0, newLength), + number.getType(), number.getData())); + } + } + } + + /* Mix prefix and numbers, make up a prefix-number combination */ + if (maxPrefixMatchLen != 0) { + for (ExtractResult number : numbers) { + if (number.getStart() == null || number.getLength() == null) { + continue; + } + + int maxFindPref = Math.min(maxPrefixMatchLen, number.getStart()); + if (maxFindPref == 0) { + continue; + } + + /* Scan from left to right , find the longest match */ + String leftStr = source.substring(number.getStart() - maxFindPref, number.getStart()); + int lastIndex = leftStr.length(); + + MatchResult bestMatch = null; + for (Pattern regex : prefixRegexes) { + Matcher match = regex.matcher(leftStr); + while (match.find()) { + if (leftStr.substring(match.start(), lastIndex).trim().equals(match.group())) { + if (bestMatch == null || bestMatch.start() >= match.start()) { + bestMatch = match.toMatchResult(); + } + } + } + } + + if (bestMatch != null) { + int offset = lastIndex - bestMatch.start(); + String unitStr = leftStr.substring(bestMatch.start(), lastIndex); + mappingPrefix.put(number.getStart(), new PrefixUnitResult(offset, unitStr)); + } + } + } + + for (ExtractResult number : numbers) { + if (number.getStart() == null || number.getLength() == null) { + continue; + } + + int start = number.getStart(); + int length = number.getLength(); + int maxFindLen = sourceLen - start - length; + + PrefixUnitResult prefixUnit = null; + if (mappingPrefix.containsKey(start)) { + prefixUnit = mappingPrefix.get(start); + } + + if (maxFindLen > 0) { + String rightSub = source.substring(start + length, start + length + maxFindLen); + List unitMatch = suffixRegexes.stream().map(p -> p.matcher(rightSub)).collect(Collectors.toList()); + + int maxlen = 0; + for (int i = 0; i < unitMatch.size(); i++) { + Matcher m = unitMatch.get(i); + while (m.find()) { + int endpos = m.end(); + if (m.start() >= 0) { + String midStr = rightSub.substring(0, Math.min(m.start(), rightSub.length())); + if (maxlen < endpos && (midStr.trim().isEmpty() || midStr.trim().equalsIgnoreCase(this.config.getConnectorToken()))) { + maxlen = endpos; + } + } + } + } + + if (maxlen != 0) { + for (int i = 0; i < length + maxlen; i++) { + matched[i + start] = true; + } + + String substr = source.substring(start, start + length + maxlen); + ExtractResult er = new ExtractResult(start, length + maxlen, substr, this.config.getExtractType(), null); + + if (prefixUnit != null) { + er.setStart(er.getStart() - prefixUnit.offset); + er.setLength(er.getLength() + prefixUnit.offset); + er.setText(prefixUnit.unitStr + er.getText()); + } + + /* Relative position will be used in Parser */ + number.setStart(start - er.getStart()); + er.setData(number); + result.add(er); + + continue; + } + } + + if (prefixUnit != null) { + ExtractResult er = new ExtractResult( + number.getStart() - prefixUnit.offset, + number.getLength() + prefixUnit.offset, + prefixUnit.unitStr + number.getText(), + this.config.getExtractType(), + null); + + /* Relative position will be used in Parser */ + number.setStart(start - er.getStart()); + er.setData(number); + result.add(er); + } + } + + // Extract Separate unit + if (separateRegex != null) { + extractSeparateUnits(source, result); + } + + // Remove common ambiguous cases + result = filterAmbiguity(result, source); + + // Expand Chinese phrase to the `half` patterns when it follows closely origin phrase. + result = this.config.expandHalfSuffix(source, result, numbers); + + return result; + } + + private List filterAmbiguity(List extractResults, String input) { + + if (this.config.getAmbiguityFiltersDict() != null) { + + for (Map.Entry pair : this.config.getAmbiguityFiltersDict().entrySet()) { + + final Pattern key = pair.getKey(); + final Pattern value = pair.getValue(); + + for (ExtractResult extractResult : extractResults) { + Optional keyMatch = Arrays.stream(RegExpUtility.getMatches(key, extractResult.getText())).findFirst(); + if (keyMatch.isPresent()) { + final Match[] matches = RegExpUtility.getMatches(value, input); + extractResults = extractResults.stream() + .filter(er -> Arrays.stream(matches).noneMatch(m -> m.index < er.getStart() + er.getLength() && m.index + m.length > er.getStart())) + .collect(Collectors.toCollection(ArrayList::new)); + } + } + } + } + + // Filter single-char units if not exact match + extractResults = extractResults.stream().filter(er -> !(er.getLength() != input.length() && Pattern.matches(singleCharUnitRegex.toString(), er.getText()))) + .collect(Collectors.toCollection(ArrayList::new)); + + return extractResults; + } + + public void extractSeparateUnits(String source, List numDependResults) { + //Default is false + boolean[] matchResult = new boolean[source.length()]; + Arrays.fill(matchResult, false); + + for (ExtractResult numDependResult : numDependResults) { + int start = numDependResult.getStart(); + int i = 0; + do { + matchResult[start + i++] = true; + } while (i < numDependResult.getLength()); + } + + //Extract all SeparateUnits, then merge it with numDependResults + Matcher matcher = separateRegex.matcher(source); + while (matcher.find()) { + + + int start = matcher.start(); + int end = matcher.end(); + int length = end - start; + + int i = 0; + while (i < length && !matchResult[start + i]) { + i++; + } + + if (i == length) { + //Mark as extracted + for (int j = 0; j < i; j++) { + matchResult[j] = true; + } + + numDependResults.add(new ExtractResult( + start, + length, + matcher.group(), + this.config.getExtractType(), + null)); + } + } + } + + protected boolean preCheckStr(String str) { + return str != null && !str.isEmpty(); + } + + protected Set buildRegexFromSet(Collection values) { + return buildRegexFromSet(values, false); + } + + protected Set buildRegexFromSet(Collection collection, boolean ignoreCase) { + + Set regexes = new HashSet<>(); + for (String regexString : collection) { + List regexTokens = new ArrayList<>(); + for (String token : QueryProcessor.split(regexString, Arrays.asList("|"))) { + regexTokens.add(Pattern.quote(token)); + } + + String pattern = String.format( + "%s(%s)%s", + this.config.getBuildPrefix(), + String.join("|", regexTokens), + this.config.getBuildSuffix()); + + int options = Pattern.UNICODE_CHARACTER_CLASS | (ignoreCase ? Pattern.CASE_INSENSITIVE : 0); + + Pattern regex = Pattern.compile(pattern, options); + regexes.add(regex); + } + + return regexes; + } + + protected Pattern buildSeparateRegexFromSet() { + return buildSeparateRegexFromSet(false); + } + + protected Pattern buildSeparateRegexFromSet(boolean ignoreCase) { + + Set separateWords = new HashSet<>(); + if (config.getPrefixList() != null && config.getPrefixList().size() > 0) { + for (String addWord : config.getPrefixList().values()) { + + for (String word : QueryProcessor.split(addWord, separators)) { + if (validateUnit(word)) { + separateWords.add(word); + } + } + } + } + + if (config.getSuffixList() != null && config.getSuffixList().size() > 0) { + for (String addWord : config.getSuffixList().values()) { + for (String word : QueryProcessor.split(addWord, separators)) { + if (validateUnit(word)) { + separateWords.add(word); + } + } + } + } + + if (config.getAmbiguousUnitList() != null && config.getAmbiguousUnitList().size() > 0) { + List abandonWords = config.getAmbiguousUnitList(); + for (String abandonWord : abandonWords) { + if (separateWords.contains(abandonWord)) { + separateWords.remove(abandonWord); + } + } + } + + //Sort separateWords using descending length. + List regexTokens = separateWords.stream().map(s -> Pattern.quote(s)).collect(Collectors.toList()); + if (regexTokens.size() == 0) { + return null; + } + + Collections.sort(regexTokens, new StringComparer()); + String pattern = String.format( + "%s(%s)%s", + this.config.getBuildPrefix(), + String.join("|", regexTokens), + this.config.getBuildSuffix()); + int options = Pattern.UNICODE_CHARACTER_CLASS | (ignoreCase ? Pattern.CASE_INSENSITIVE : 0); + + Pattern regex = Pattern.compile(pattern, options); + return regex; + } + + public boolean validateUnit(String source) { + return !source.startsWith("-"); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..1dcf822d6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AgeSuffixList = FrenchNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..eb86278aa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = FrenchNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..0f3fa303b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = FrenchNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = FrenchNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..d88234dab --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(FrenchNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..6f7a5b683 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/FrenchNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,77 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.french.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.EnglishNumericWithUnit; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class FrenchNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected FrenchNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(FrenchNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(FrenchNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return FrenchNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return FrenchNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return FrenchNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..13e4a6cb1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = FrenchNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..6ac2915c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = FrenchNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..a30c3b9be --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(FrenchNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..8adcf5b53 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = FrenchNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..78b36f3f3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.french.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends FrenchNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return FrenchNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = FrenchNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..333b88e7b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..ae7faf78a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..aeaf95771 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..021e63240 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..5aa1cd97a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/FrenchNumberWithUnitParserConfiguration.java @@ -0,0 +1,38 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.french.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.french.parsers.FrenchNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.FrenchNumericWithUnit; + +public class FrenchNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return FrenchNumericWithUnit.ConnectorToken; + } + + public FrenchNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new FrenchNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..880c7f2f1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..eee7acf80 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..6bc4d8a03 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,23 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } + + @Override + public String getConnectorToken() { + return null; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..e5cb6130c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..9bd27c939 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/french/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.french.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.french.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends FrenchNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.French)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..102c5afd7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = GermanNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..6f4fd5364 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = GermanNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..1dd4a2bb9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = GermanNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = GermanNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..979b3c25f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(GermanNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..d066d80b6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/GermanNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,75 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.german.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class GermanNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected GermanNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit); + this.compoundUnitConnectorRegex = + Pattern.compile(GermanNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(GermanNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return GermanNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return GermanNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return ""; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..1e0a7e5d2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = GermanNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..7d6284cd0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = GermanNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..90f7a6dcf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousTemperatureUnitList; + } + + public static Map TemperatureSuffixList = new HashMap(GermanNumericWithUnit.TemperatureSuffixList); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5c42ed9ca --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousVolumeUnitList; + } + + public static Map VolumeSuffixList = GermanNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..3a6be60a8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.german.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.GermanNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends GermanNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return GermanNumericWithUnit.AmbiguousWeightUnitList; + } + + public static Map WeightSuffixList = GermanNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..1e9689c65 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AgeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..5f64c2d0a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..2bb68c241 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends GermanNumberWithUnitParserConfiguration { + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..78ec43eff --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..203f80197 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/GermanNumberWithUnitParserConfiguration.java @@ -0,0 +1,37 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.german.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.german.parsers.GermanNumberParserConfiguration; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; + +public abstract class GermanNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return ""; + } + + public GermanNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new GermanNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..635c6e7ce --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..de3e44700 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..c1714bdd4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..7e653cffa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..58b78955d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/german/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.german.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.german.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends GermanNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.German)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java new file mode 100644 index 000000000..9d0c67455 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AbstractNumberWithUnitModel.java @@ -0,0 +1,103 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IModel; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.ResolutionKey; +import com.microsoft.recognizers.text.utilities.QueryProcessor; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.Collectors; + +public abstract class AbstractNumberWithUnitModel implements IModel { + + private final Map extractorParserMap; + + public abstract String getModelTypeName(); + + protected Map getExtractorParserMap() { + return this.extractorParserMap; + } + + protected AbstractNumberWithUnitModel(Map extractorParserMap) { + this.extractorParserMap = extractorParserMap; + } + + @SuppressWarnings("unchecked") + public List parse(String query) { + + // Pre-process the query + query = QueryProcessor.preprocess(query, true); + + List extractionResults = new ArrayList(); + + try { + for (Map.Entry kv : extractorParserMap.entrySet()) { + IExtractor extractor = kv.getKey(); + IParser parser = kv.getValue(); + + List extractedResults = extractor.extract(query); + + List parsedResults = new ArrayList(); + + for (ExtractResult result : extractedResults) { + ParseResult parseResult = parser.parse(result); + if (parseResult.getValue() instanceof List) { + parsedResults.addAll((List)parseResult.getValue()); + } else { + parsedResults.add(parseResult); + } + } + + List modelResults = parsedResults.stream().map(o -> { + + SortedMap resolutionValues = new TreeMap(); + if (o.getValue() instanceof UnitValue) { + resolutionValues.put(ResolutionKey.Value, ((UnitValue)o.getValue()).number); + resolutionValues.put(ResolutionKey.Unit, ((UnitValue)o.getValue()).unit); + } else if (o.getValue() instanceof CurrencyUnitValue) { + resolutionValues.put(ResolutionKey.Value, ((CurrencyUnitValue)o.getValue()).number); + resolutionValues.put(ResolutionKey.Unit, ((CurrencyUnitValue)o.getValue()).unit); + resolutionValues.put(ResolutionKey.IsoCurrency, ((CurrencyUnitValue)o.getValue()).isoCurrency); + } else { + resolutionValues.put(ResolutionKey.Value, (String)o.getValue()); + } + + return new ModelResult( + o.getText(), + o.getStart(), + o.getStart() + o.getLength() - 1, + getModelTypeName(), + resolutionValues); + }).collect(Collectors.toList()); + + + for (ModelResult result : modelResults) { + boolean badd = true; + + for (ModelResult extractionResult : extractionResults) { + if (extractionResult.start == result.start && extractionResult.end == result.end) { + badd = false; + } + } + + if (badd) { + extractionResults.add(result); + } + } + } + } catch (Exception ex) { + // Nothing to do. Exceptions in parse should not break users of recognizers. + // No result. + ex.printStackTrace(); + } + + return extractionResults; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java new file mode 100644 index 000000000..c2cf881fe --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/AgeModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class AgeModel extends AbstractNumberWithUnitModel { + + public AgeModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "age"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java new file mode 100644 index 000000000..01f7e35fe --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class CurrencyModel extends AbstractNumberWithUnitModel { + + public CurrencyModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "currency"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java new file mode 100644 index 000000000..2907b2185 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/CurrencyUnitValue.java @@ -0,0 +1,14 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class CurrencyUnitValue { + + public final String number; + public final String unit; + public final String isoCurrency; + + public CurrencyUnitValue(String number, String unit, String isoCurrency) { + this.number = number; + this.unit = unit; + this.isoCurrency = isoCurrency; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java new file mode 100644 index 000000000..f4ccf4d53 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/DimensionModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class DimensionModel extends AbstractNumberWithUnitModel { + + public DimensionModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "dimension"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java new file mode 100644 index 000000000..5b6a8d6f4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/PrefixUnitResult.java @@ -0,0 +1,11 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class PrefixUnitResult { + public final int offset; + public final String unitStr; + + public PrefixUnitResult(int offset, String unitStr) { + this.offset = offset; + this.unitStr = unitStr; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java new file mode 100644 index 000000000..c41a10126 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/TemperatureModel.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public class TemperatureModel extends AbstractNumberWithUnitModel { + + public TemperatureModel(Map extractorParserMap) { + super(extractorParserMap); + } + + @Override + public String getModelTypeName() { + return "temperature"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java new file mode 100644 index 000000000..b443c3e49 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/models/UnitValue.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.numberwithunit.models; + +public class UnitValue { + + public final String number; + public final String unit; + + public UnitValue(String number, String unit) { + this.unit = unit; + this.number = number; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java new file mode 100644 index 000000000..3fc489686 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseCurrencyParser.java @@ -0,0 +1,188 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.CurrencyUnitValue; +import com.microsoft.recognizers.text.numberwithunit.models.UnitValue; +import com.microsoft.recognizers.text.numberwithunit.utilities.DictionaryUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + + +public class BaseCurrencyParser implements IParser { + + private final BaseNumberWithUnitParserConfiguration config; + private final NumberWithUnitParser numberWithUnitParser; + + public BaseCurrencyParser(BaseNumberWithUnitParserConfiguration config) { + this.config = config; + this.numberWithUnitParser = new NumberWithUnitParser(config); + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + ParseResult pr; + if (extResult.getData() instanceof List) { + return mergeCompoundUnit(extResult); + } else { + pr = numberWithUnitParser.parse(extResult); + UnitValue value = (UnitValue)pr.getValue(); + + String mainUnitIsoCode = null; + if (value != null && config.getCurrencyNameToIsoCodeMap().containsKey(value.unit)) { + mainUnitIsoCode = config.getCurrencyNameToIsoCodeMap().get(value.unit); + } + + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty() || mainUnitIsoCode.startsWith(Constants.FAKE_ISO_CODE_PREFIX)) { + pr.setValue(new UnitValue(value.number, value.unit)); + } else { + pr.setValue(new CurrencyUnitValue(value.number, value.unit, mainUnitIsoCode)); + } + return pr; + } + } + + @SuppressWarnings("unchecked") + private ParseResult mergeCompoundUnit(ExtractResult compoundResult) { + List results = new ArrayList<>(); + List compoundUnit = (List)compoundResult.getData(); + + int count = 0; + ParseResult result = null; + // Make the default numberValue a constant to check if there is no Value. + double numberValue = Double.MIN_VALUE; + String mainUnitValue = ""; + String mainUnitIsoCode = ""; + String fractionUnitsString = ""; + + for (int idx = 0; idx < compoundUnit.size(); idx++) { + ExtractResult extractResult = compoundUnit.get(idx); + ParseResult parseResult = numberWithUnitParser.parse(extractResult); + Optional parseResultValue = Optional.ofNullable(parseResult.getValue() instanceof UnitValue ? (UnitValue)parseResult.getValue() : null); + String unitValue = parseResultValue.isPresent() ? parseResultValue.get().unit : ""; + + // Process a new group + if (count == 0) { + if (!extractResult.getType().equals(Constants.SYS_UNIT_CURRENCY)) { + continue; + } + + // Initialize a new result + result = new ParseResult(extractResult.getStart(), extractResult.getLength(), extractResult.getText(), extractResult.getType(), null, null, null); + + mainUnitValue = unitValue; + if (parseResultValue.isPresent() && parseResultValue.get().number != null) { + numberValue = Double.parseDouble(parseResultValue.get().number); + } + + result.setResolutionStr(parseResult.getResolutionStr()); + mainUnitIsoCode = config.getCurrencyNameToIsoCodeMap().containsKey(unitValue) ? config.getCurrencyNameToIsoCodeMap().get(unitValue) : mainUnitIsoCode; + + // If the main unit can't be recognized, finish process this group. + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty()) { + result.setValue(new UnitValue(getNumberValue(numberValue), mainUnitValue)); + results.add(result); + result = null; + continue; + } + + fractionUnitsString = config.getCurrencyFractionMapping().containsKey(mainUnitIsoCode) ? + config.getCurrencyFractionMapping().get(mainUnitIsoCode) : + fractionUnitsString; + } else { + + // Match pure number as fraction unit. + if (extractResult.getType().equals(Constants.SYS_NUM)) { + numberValue += (double)parseResult.getValue() * (1.0 / 100); + result.setLength(parseResult.getStart() + parseResult.getLength() - result.getStart()); + result.setResolutionStr(result.getResolutionStr() + " " + parseResult.getResolutionStr()); + count++; + continue; + } + + String fractionUnitCode = config.getCurrencyFractionCodeList().containsKey(unitValue) ? + config.getCurrencyFractionCodeList().get(unitValue) : + null; + + String unit = parseResultValue.isPresent() ? parseResultValue.get().unit : null; + Optional fractionNumValue = Optional.ofNullable(config.getCurrencyFractionNumMap().containsKey(unit) ? + config.getCurrencyFractionNumMap().get(unit) : + null); + + if (fractionUnitCode != null && !fractionUnitCode.isEmpty() && fractionNumValue.isPresent() && fractionNumValue.get() != 0 && + checkUnitsStringContains(fractionUnitCode, fractionUnitsString)) { + numberValue += Double.parseDouble(parseResultValue.get().number) * (1.0 / fractionNumValue.get()); + result.setLength(parseResult.getStart() + parseResult.getLength() - result.getStart()); + result.setResolutionStr(result.getResolutionStr() + " " + parseResult.getResolutionStr()); + } else { + // If the fraction unit doesn't match the main unit, finish process this group. + if (result != null) { + result = createCurrencyResult(result, mainUnitIsoCode, numberValue, mainUnitValue); + results.add(result); + result = null; + } + + count = 0; + idx -= 1; + numberValue = Double.MIN_VALUE; + continue; + } + } + + count++; + } + + if (result != null) { + result = createCurrencyResult(result, mainUnitIsoCode, numberValue, mainUnitValue); + results.add(result); + } + + resolveText(results, compoundResult.getText(), compoundResult.getStart()); + + return new ParseResult(null, null, null, null, null, results, null); + } + + private boolean checkUnitsStringContains(String fractionUnitCode, String fractionUnitsString) { + Map unitsMap = new HashMap(); + DictionaryUtils.bindUnitsString(unitsMap, "", fractionUnitsString); + return unitsMap.containsKey(fractionUnitCode); + } + + private void resolveText(List prs, String source, int bias) { + for (ParseResult parseResult : prs) { + if (parseResult.getStart() != null && parseResult.getLength() != null) { + int start = parseResult.getStart() - bias; + parseResult.setText(source.substring(start, start + parseResult.getLength())); + prs.set(prs.indexOf(parseResult), parseResult); + } + } + } + + private String getNumberValue(double numberValue) { + if (numberValue == Double.MIN_VALUE) { + return null; + } else { + java.text.NumberFormat numberFormat = java.text.NumberFormat.getInstance(); + numberFormat.setMinimumFractionDigits(0); + numberFormat.setGroupingUsed(false); + return numberFormat.format(numberValue); + } + } + + private ParseResult createCurrencyResult(ParseResult result, String mainUnitIsoCode, Double numberValue, String mainUnitValue) { + if (mainUnitIsoCode == null || mainUnitIsoCode.isEmpty() || + mainUnitIsoCode.startsWith(Constants.FAKE_ISO_CODE_PREFIX)) { + result.setValue(new UnitValue(getNumberValue(numberValue), mainUnitValue)); + } else { + result.setValue(new CurrencyUnitValue(getNumberValue(numberValue), mainUnitValue, mainUnitIsoCode)); + } + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java new file mode 100644 index 000000000..bbadb603e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseMergedUnitParser.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; + +public class BaseMergedUnitParser implements IParser { + + protected final BaseNumberWithUnitParserConfiguration config; + private final NumberWithUnitParser numberWithUnitParser; + + public BaseMergedUnitParser(BaseNumberWithUnitParserConfiguration config) { + this.config = config; + this.numberWithUnitParser = new NumberWithUnitParser(config); + } + + @Override + public ParseResult parse(ExtractResult extResult) { + // For now only currency model recognizes compound units. + if (extResult.getType().equals(Constants.SYS_UNIT_CURRENCY)) { + return new BaseCurrencyParser(config).parse(extResult); + } else { + return numberWithUnitParser.parse(extResult); + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..84fe89e8f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/BaseNumberWithUnitParserConfiguration.java @@ -0,0 +1,72 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseCurrency; +import com.microsoft.recognizers.text.numberwithunit.utilities.DictionaryUtils; + +import java.util.HashMap; +import java.util.Map; + +public abstract class BaseNumberWithUnitParserConfiguration implements INumberWithUnitParserConfiguration { + private final Map unitMap; + private final Map currencyFractionNumMap; + private final Map currencyFractionMapping; + private final CultureInfo cultureInfo; + private final Map currencyNameToIsoCodeMap; + private final Map currencyFractionCodeList; + + @Override + public Map getUnitMap() { + return this.unitMap; + } + + @Override + public Map getCurrencyFractionNumMap() { + return this.currencyFractionNumMap; + } + + @Override + public Map getCurrencyFractionMapping() { + return this.currencyFractionMapping; + } + + @Override + public Map getCurrencyNameToIsoCodeMap() { + return this.currencyNameToIsoCodeMap; + } + + @Override + public Map getCurrencyFractionCodeList() { + return this.currencyFractionCodeList; + } + + @Override + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + @Override + public abstract IParser getInternalNumberParser(); + + @Override + public abstract IExtractor getInternalNumberExtractor(); + + @Override + public abstract String getConnectorToken(); + + protected BaseNumberWithUnitParserConfiguration(CultureInfo ci) { + this.cultureInfo = ci; + this.unitMap = new HashMap<>(); + this.currencyFractionNumMap = BaseCurrency.CurrencyFractionalRatios; + this.currencyFractionMapping = BaseCurrency.CurrencyFractionMapping; + this.currencyNameToIsoCodeMap = new HashMap<>(); + this.currencyFractionCodeList = new HashMap<>(); + } + + @Override + public void bindDictionary(Map dictionary) { + DictionaryUtils.bindDictionary(dictionary, getUnitMap()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..11829a00e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/INumberWithUnitParserConfiguration.java @@ -0,0 +1,30 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; + +import java.util.Map; + +public interface INumberWithUnitParserConfiguration { + + Map getUnitMap(); + + Map getCurrencyFractionNumMap(); + + Map getCurrencyFractionMapping(); + + Map getCurrencyNameToIsoCodeMap(); + + Map getCurrencyFractionCodeList(); + + CultureInfo getCultureInfo(); + + IParser getInternalNumberParser(); + + IExtractor getInternalNumberExtractor(); + + String getConnectorToken(); + + void bindDictionary(Map dictionary); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java new file mode 100644 index 000000000..a57e16410 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/parsers/NumberWithUnitParser.java @@ -0,0 +1,143 @@ +package com.microsoft.recognizers.text.numberwithunit.parsers; + +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.ParseResult; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.models.UnitValue; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class NumberWithUnitParser implements IParser { + + protected final INumberWithUnitParserConfiguration config; + + public NumberWithUnitParser(INumberWithUnitParserConfiguration config) { + this.config = config; + } + + @Override + public ParseResult parse(ExtractResult extResult) { + + Map unitMap = this.config.getUnitMap(); + String connectorToken = this.config.getConnectorToken(); + ParseResult ret = new ParseResult(extResult); + ExtractResult numberResult; + ExtractResult halfResult; + + if (extResult.getData() instanceof ExtractResult) { + numberResult = (ExtractResult)extResult.getData(); + halfResult = null; + } else if (extResult.getType().equals(Constants.SYS_NUM)) { + ret.setValue(config.getInternalNumberParser().parse(extResult).getValue()); + return ret; + } else if (extResult.getData() instanceof List) { + Object data = extResult.getData(); + List dataList = new ArrayList(); + for (Object d : (List)data) { + dataList.add((ExtractResult)d); + } + if (dataList.size() >= 2) { + numberResult = dataList.get(0); + halfResult = dataList.get(1); + } else { + numberResult = dataList.get(0); + halfResult = null; + } + } else { + // if there is no unitResult, means there is just unit + numberResult = new ExtractResult(-1, 0, null, null, null); + halfResult = null; + } + + // key contains units + String key = extResult.getText(); + StringBuilder unitKeyBuild = new StringBuilder(); + List unitKeys = new ArrayList<>(); + + for (int i = 0; i <= key.length(); i++) { + + if (i == key.length()) { + if (unitKeyBuild.length() != 0) { + addIfNotContained(unitKeys, unitKeyBuild.toString().trim()); + } + } else if (i == numberResult.getStart()) { + // numberResult.start is a relative position + if (unitKeyBuild.length() != 0) { + addIfNotContained(unitKeys, unitKeyBuild.toString().trim()); + unitKeyBuild = new StringBuilder(); + } + i = numberResult.getStart() + numberResult.getLength() - 1; + } else { + unitKeyBuild.append(key.charAt(i)); + } + } + + /* Unit type depends on last unit in suffix.*/ + String lastUnit = unitKeys.get(unitKeys.size() - 1); + if (halfResult != null) { + lastUnit = lastUnit.substring(0, lastUnit.length() - halfResult.getText().length()); + } + String normalizedLastUnit = lastUnit.toLowerCase(); + + if (connectorToken != null && !connectorToken.isEmpty() && normalizedLastUnit.startsWith(connectorToken)) { + normalizedLastUnit = normalizedLastUnit.substring(connectorToken.length()).trim(); + lastUnit = lastUnit.substring(connectorToken.length()).trim(); + } + + if (key != null && !key.isEmpty() && unitMap != null) { + + String unitValue = null; + + if (unitMap.containsKey(lastUnit)) { + unitValue = unitMap.get(lastUnit); + } else if (unitMap.containsKey(normalizedLastUnit)) { + unitValue = unitMap.get(normalizedLastUnit); + } + + if (unitValue != null) { + + ParseResult numValue = numberResult.getText() == null || numberResult.getText().isEmpty() ? + null : + this.config.getInternalNumberParser().parse(numberResult); + String resolutionStr = numValue != null ? numValue.getResolutionStr() : null; + + if (halfResult != null) { + ParseResult halfValue = this.config.getInternalNumberParser().parse(halfResult); + String tmp = halfValue != null ? halfValue.getResolutionStr() : null; + if (tmp != null) { + resolutionStr += tmp.substring(1, tmp.length()); + } + } + + ret.setValue(new UnitValue(resolutionStr, unitValue)); + ret.setResolutionStr(String.format("%s %s", resolutionStr != null ? resolutionStr : "", unitValue).trim()); + } + } + + if (ret != null) { + ret.setText(ret.getText().toLowerCase(Locale.ROOT)); + } + + return ret; + } + + private void addIfNotContained(List unitKeys, String unit) { + + boolean add = true; + for (String unitKey : unitKeys) { + if (unitKey.contains(unit)) { + add = false; + break; + } + } + + if (add) { + unitKeys.add(unit); + } + } + +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..f18980218 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = PortugueseNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..d05d18356 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = PortugueseNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..e1d089d77 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = PortugueseNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = PortugueseNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..b3522d05e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(PortugueseNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..918f68a43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = PortugueseNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..81c194a77 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/PortugueseNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class PortugueseNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected PortugueseNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit);; + this.compoundUnitConnectorRegex = + Pattern.compile(PortugueseNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(PortugueseNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return PortugueseNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return PortugueseNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return PortugueseNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..28859340d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = PortugueseNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..fc3acdfa2 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(PortugueseNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..cd30a3c05 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map VolumeSuffixList = PortugueseNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..1e55a2cfb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends PortugueseNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return PortugueseNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = PortugueseNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..eed9702c9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..7332b5b3c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..6b8df0ede --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..fc4c04d29 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..66fe22b0e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..295dca7d3 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/PortugueseNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.portuguese.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.portuguese.parsers.PortugueseNumberParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.PortugueseNumericWithUnit; + +public class PortugueseNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return PortugueseNumericWithUnit.ConnectorToken; + } + + public PortugueseNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(NumberMode.Default); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new PortugueseNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..3134499f5 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..7f3b46c5c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..373a2480a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..ddce7b5b6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/portuguese/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.portuguese.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.portuguese.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends PortugueseNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.Portuguese)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java new file mode 100644 index 000000000..55a95d4a6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseCurrency.java @@ -0,0 +1,281 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class BaseCurrency { + + public static final ImmutableMap CurrencyFractionMapping = ImmutableMap.builder() + .put("CNY", "FEN|JIAO") + .put("__D", "CENT") + .put("RUB", "KOPEK") + .put("AFN", "PUL") + .put("EUR", "CENT|KWARTJE|DUBBELTJE|STUIVER") + .put("ALL", "QINDARKE") + .put("_ALP", "PENNY") + .put("GBP", "PENNY") + .put("_GGP", "PENNY") + .put("DZD", "SANTEEM") + .put("AOA", "CENTIMO") + .put("ARS", "CENTAVO") + .put("AMD", "LUMA") + .put("AWG", "CENT") + .put("_AP", "PENNY") + .put("SHP", "PENNY") + .put("AUD", "CENT") + .put("AZN", "QƏPIK") + .put("BSD", "CENT") + .put("BHD", "FILS") + .put("BDT", "POISHA") + .put("BBD", "CENT") + .put("BYN", "KAPYEYKA") + .put("BZD", "CENT") + .put("XOF", "CENTIME") + .put("BMD", "CENT") + .put("BTN", "CHETRUM") + .put("INR", "PAISA") + .put("BOB", "CENTAVO") + .put("USD", "CENT") + .put("BAM", "FENING") + .put("BWP", "THEBE") + .put("BRL", "CENTAVO") + .put("_BD", "CENT") + .put("BND", "SEN") + .put("SGD", "CENT") + .put("BGN", "STOTINKA") + .put("BIF", "CENTIME") + .put("KHR", "SEN") + .put("XAF", "CENTIME") + .put("CAD", "CENT") + .put("CVE", "CENTAVO") + .put("KYD", "CENT") + .put("CLP", "CENTAVO") + .put("COP", "CENTAVO") + .put("KMF", "CENTIME") + .put("CDF", "CENTIME") + .put("NZD", "CENT") + .put("_CKD", "CENT") + .put("CRC", "CENTIMO") + .put("HRK", "LIPA") + .put("CUC", "CENTAVO") + .put("CUP", "CENTAVO") + .put("CZK", "HALER") + .put("DKK", "ØRE") + .put("DJF", "CENTIME") + .put("DOP", "CENTAVO") + .put("EGP", "PIASTRE") + .put("ERN", "CENT") + .put("ETB", "SANTIM") + .put("FKP", "PENNY") + .put("_FOK", "OYRA") + .put("FJD", "CENT") + .put("XPF", "CENTIME") + .put("GMD", "BUTUT") + .put("GEL", "TETRI") + .put("GHS", "PESEWA") + .put("GIP", "PENNY") + .put("GTQ", "CENTAVO") + .put("GNF", "CENTIME") + .put("GYD", "CENT") + .put("HTG", "CENTIME") + .put("HNL", "CENTAVO") + .put("HKD", "CENT") + .put("HUF", "FILLER") + .put("ISK", "EYRIR") + .put("IDR", "SEN") + .put("IRR", "DINAR") + .put("IQD", "FILS") + .put("IMP", "PENNY") + .put("ILS", "AGORA") + .put("JMD", "CENT") + .put("JPY", "SEN") + .put("JEP", "PENNY") + .put("JOD", "PIASTRE") + .put("KZT", "TIIN") + .put("KES", "CENT") + .put("_KID", "CENT") + .put("KPW", "CHON") + .put("KRW", "JEON") + .put("KWD", "FILS") + .put("KGS", "TYIYN") + .put("LAK", "ATT") + .put("LBP", "PIASTRE") + .put("LSL", "SENTE") + .put("ZAR", "CENT") + .put("LRD", "CENT") + .put("LYD", "DIRHAM") + .put("CHF", "RAPPEN") + .put("MOP", "AVO") + .put("MKD", "DENI") + .put("MGA", "IRAIMBILANJA") + .put("MWK", "TAMBALA") + .put("MYR", "SEN") + .put("MVR", "LAARI") + .put("MRO", "KHOUMS") + .put("MUR", "CENT") + .put("MXN", "CENTAVO") + .put("_MD", "CENT") + .put("MDL", "BAN") + .put("MNT", "MONGO") + .put("MAD", "CENTIME") + .put("MZN", "CENTAVO") + .put("MMK", "PYA") + .put("NAD", "CENT") + .put("_ND", "CENT") + .put("NPR", "PAISA") + .put("NIO", "CENTAVO") + .put("NGN", "KOBO") + .put("_NID", "CENT") + .put("TRY", "KURUS") + .put("NOK", "ØRE") + .put("OMR", "BAISA") + .put("PKR", "PAISA") + .put("_PD", "CENT") + .put("PAB", "CENTESIMO") + .put("PGK", "TOEA") + .put("PYG", "CENTIMO") + .put("PEN", "CENTIMO") + .put("_PND", "CENT") + .put("PLN", "GROSZ") + .put("QAR", "DIRHAM") + .put("RON", "BAN") + .put("RWF", "CENTIME") + .put("WST", "SENE") + .put("STD", "CENTIMO") + .put("SAR", "HALALA") + .put("RSD", "PARA") + .put("SCR", "CENT") + .put("SLL", "CENT") + .put("SBD", "CENT") + .put("SOS", "CENT") + .put("_SS", "CENT") + .put("_SP", "PENNY") + .put("SSP", "PIASTRE") + .put("LKR", "CENT") + .put("SDG", "PIASTRE") + .put("SRD", "CENT") + .put("SZL", "CENT") + .put("SEK", "ORE") + .put("SYP", "PIASTRE") + .put("TWD", "CENT") + .put("TJS", "DIRAM") + .put("TZS", "CENT") + .put("THB", "SATANG") + .put("PRB", "KOPEK") + .put("TTD", "CENT") + .put("_TP", "PENNY") + .put("TND", "MILLIME") + .put("TMT", "TENNESI") + .put("TVD", "CENT") + .put("UGX", "CENT") + .put("UAH", "KOPIYKA") + .put("AED", "FILS") + .put("UYU", "CENTESIMO") + .put("VEF", "CENTIMO") + .put("YER", "FILS") + .put("ZMW", "NGWEE") + .build(); + + public static final ImmutableMap CurrencyFractionalRatios = ImmutableMap.builder() + .put("Kopek", 100L) + .put("Pul", 100L) + .put("Cent", 100L) + .put("Qindarkë", 100L) + .put("Penny", 100L) + .put("Santeem", 100L) + .put("Cêntimo", 100L) + .put("Centavo", 100L) + .put("Luma", 100L) + .put("Qəpik", 100L) + .put("Fils", 1000L) + .put("Poisha", 100L) + .put("Kapyeyka", 100L) + .put("Centime", 100L) + .put("Chetrum", 100L) + .put("Paisa", 100L) + .put("Fening", 100L) + .put("Thebe", 100L) + .put("Sen", 100L) + .put("Stotinka", 100L) + .put("Jiao", 10L) + .put("Fen", 100L) + .put("Céntimo", 100L) + .put("Lipa", 100L) + .put("Haléř", 100L) + .put("Øre", 100L) + .put("Piastre", 100L) + .put("Santim", 100L) + .put("Oyra", 100L) + .put("Butut", 100L) + .put("Tetri", 100L) + .put("Pesewa", 100L) + .put("Fillér", 100L) + .put("Eyrir", 100L) + .put("Dinar", 100L) + .put("Agora", 100L) + .put("Tïın", 100L) + .put("Chon", 100L) + .put("Jeon", 100L) + .put("Tyiyn", 100L) + .put("Att", 100L) + .put("Sente", 100L) + .put("Dirham", 1000L) + .put("Rappen", 100L) + .put("Avo", 100L) + .put("Deni", 100L) + .put("Iraimbilanja", 5L) + .put("Tambala", 100L) + .put("Laari", 100L) + .put("Khoums", 5L) + .put("Ban", 100L) + .put("Möngö", 100L) + .put("Pya", 100L) + .put("Kobo", 100L) + .put("Kuruş", 100L) + .put("Baisa", 1000L) + .put("Centésimo", 100L) + .put("Toea", 100L) + .put("Sentimo", 100L) + .put("Grosz", 100L) + .put("Sene", 100L) + .put("Halala", 100L) + .put("Para", 100L) + .put("Öre", 100L) + .put("Diram", 100L) + .put("Satang", 100L) + .put("Seniti", 100L) + .put("Millime", 1000L) + .put("Tennesi", 100L) + .put("Kopiyka", 100L) + .put("Tiyin", 100L) + .put("Hào", 10L) + .put("Ngwee", 100L) + .put("Kwartje", 4L) + .put("Dubbeltje", 10L) + .put("Stuiver", 20L) + .build(); + + public static final ImmutableMap NonStandardFractionalSubunits = ImmutableMap.builder() + .put("JOD", 1000L) + .put("KWD", 1000L) + .put("BHD", 1000L) + .put("OMR", 1000L) + .put("YDD", 1000L) + .put("TND", 1000L) + .put("MRO", 5L) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java new file mode 100644 index 000000000..fddeaad76 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/BaseUnits.java @@ -0,0 +1,32 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +public class BaseUnits { + + public static final String HourRegex = "(?00|01|02|03|04|05|06|07|08|09|0|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|1|2|3|4|5|6|7|8|9)(h)?"; + + public static final String MinuteRegex = "(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)(?!\\d)"; + + public static final String SecondRegex = "(?00|01|02|03|04|05|06|07|08|09|10|11|12|13|14|15|16|17|18|19|20|21|22|23|24|25|26|27|28|29|30|31|32|33|34|35|36|37|38|39|40|41|42|43|44|45|46|47|48|49|50|51|52|53|54|55|56|57|58|59|0|1|2|3|4|5|6|7|8|9)"; + + public static final String PmNonUnitRegex = "({HourRegex}\\s*:\\s*{MinuteRegex}(\\s*:\\s*{SecondRegex})?\\s*pm)" + .replace("{HourRegex}", HourRegex) + .replace("{MinuteRegex}", MinuteRegex) + .replace("{SecondRegex}", SecondRegex); + + public static final String AmbiguousTimeTerm = "pm"; + + public static final String AmbiguousUnitNumberMultiplierRegex = "(\\s([Kk]|mil))"; + + public static final String SingleCharUnitRegex = "^\\b(c|f|g|k|l|m|s)(\\s*\\.|\\b)$"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java new file mode 100644 index 000000000..d4086e071 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/ChineseNumericWithUnit.java @@ -0,0 +1,639 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class ChineseNumericWithUnit { + + public static final List AgeAmbiguousValues = Arrays.asList("岁"); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "岁|周岁") + .put("Month", "个月大|月大") + .put("Week", "周大") + .put("Day", "天大") + .build(); + + public static final String BuildPrefix = ""; + + public static final String BuildSuffix = ""; + + public static final String ConnectorToken = ""; + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Afghan afghani", "阿富汗尼") + .put("Pul", "普尔") + .put("Euro", "欧元") + .put("Cent", "美分") + .put("Albanian lek", "阿尔巴尼亚列克|列克") + .put("Angolan kwanza", "安哥拉宽扎|宽扎") + .put("Armenian dram", "亚美尼亚德拉姆") + .put("Aruban florin", "阿鲁巴弗罗林|阿鲁巴币") + .put("Bangladeshi taka", "塔卡|孟加拉塔卡") + .put("Paisa", "派萨|帕萨") + .put("Bhutanese ngultrum", "不丹努尔特鲁姆|不丹努扎姆|努扎姆") + .put("Chetrum", "切特鲁姆") + .put("Bolivian boliviano", "玻利维亚诺|玻利维亚币") + .put("Bosnia and Herzegovina convertible mark", "波斯尼亚和黑塞哥维那可兑换马克|波赫可兑换马克") + .put("Botswana pula", "博茨瓦纳普拉|普拉") + .put("Thebe", "thebe") + .put("Brazilian real", "巴西雷亚尔") + .put("Bulgarian lev", "保加利亚列弗|保加利亚列瓦") + .put("Stotinka", "斯托丁卡") + .put("Cambodian riel", "瑞尔") + .put("Cape Verdean escudo", "佛得角埃斯库多|维德角埃斯库多") + .put("Croatian kuna", "克罗地亚库纳|克罗地亚库那|克罗埃西亚库纳") + .put("Lipa", "利巴") + .put("Eritrean nakfa", "厄立特里亚纳克法") + .put("Ethiopian birr", "埃塞俄比亚比尔|埃塞俄比亚元") + .put("Gambian dalasi", "冈比亚达拉西|甘比亚达拉西") + .put("Butut", "布达|布图") + .put("Georgian lari", "格鲁吉亚拉里") + .put("Tetri", "特特里|泰特里") + .put("Ghanaian cedi", "塞地|加纳塞地") + .put("Pesewa", "比塞瓦") + .put("Guatemalan quetzal", "瓜地马拉格查尔") + .put("Haitian gourde", "海地古德") + .put("Honduran lempira", "洪都拉斯伦皮拉") + .put("Hungarian forint", "匈牙利福林|匈牙利货币|匈牙利福林币") + .put("Iranian rial", "伊朗里亚尔|伊朗莱尔") + .put("Yemeni rial", "叶门莱尔|叶门里亚尔") + .put("Israeli new shekel", "₪|ils|以色列币|以色列新克尔|谢克尔") + .put("Japanese yen", "日元|日本元|日币|日圆") + .put("Sen", "日本銭") + .put("Kazakhstani tenge", "哈萨克斯坦坚戈") + .put("Kenyan shilling", "肯尼亚先令") + .put("North Korean won", "朝鲜圆|朝鲜元") + .put("South Korean won", "韩元|韩圆") + .put("Korean won", "₩") + .put("Kyrgyzstani som", "吉尔吉斯斯坦索姆") + .put("Lao kip", "基普|老挝基普|老挝币") + .put("Att", "att") + .put("Lesotho loti", "莱索托洛提|莱索托马洛蒂") + .put("South African rand", "南非兰特") + .put("Macedonian denar", "马其顿代纳尔|马其顿币|代纳尔") + .put("Deni", "第尼") + .put("Malagasy ariary", "马达加斯加阿里亚里") + .put("Iraimbilanja", "伊莱姆比拉贾") + .put("Malawian kwacha", "马拉威克瓦查") + .put("Tambala", "坦巴拉") + .put("Malaysian ringgit", "马来西亚币|马币|马来西亚林吉特") + .put("Mauritanian ouguiya", "毛里塔尼亚乌吉亚") + .put("Khoums", "库姆斯") + .put("Mozambican metical", "莫桑比克梅蒂卡尔|梅蒂卡尔") + .put("Burmese kyat", "缅甸元|缅元") + .put("Pya", "缅分") + .put("Nigerian naira", "尼日利亚奈拉|尼日利亚币|奈拉") + .put("Kobo", "考包") + .put("Turkish lira", "土耳其里拉") + .put("Kuruş", "库鲁") + .put("Omani rial", "阿曼里亚尔|阿曼莱尔") + .put("Panamanian balboa", "巴拿马巴波亚") + .put("Centesimo", "意大利分|乌拉圭分|巴拿马分") + .put("Papua New Guinean kina", "基那") + .put("Toea", "托亚|托伊") + .put("Peruvian sol", "秘鲁索尔") + .put("Polish złoty", "波兰币|波兰兹罗提|兹罗提") + .put("Grosz", "格罗希") + .put("Qatari riyal", "卡达里亚尔") + .put("Saudi riyal", "沙特里亚尔") + .put("Riyal", "里亚尔") + .put("Dirham", "迪拉姆") + .put("Halala", "哈拉") + .put("Samoan tālā", "萨摩亚塔拉") + .put("Sierra Leonean leone", "塞拉利昂利昂|利昂") + .put("Peseta", "比塞塔|西班牙比塞塔|西班牙币") + .put("Swazi lilangeni", "斯威士兰里兰吉尼|兰吉尼") + .put("Tajikistani somoni", "塔吉克斯坦索莫尼") + .put("Thai baht", "泰铢|泰元") + .put("Satang", "萨当") + .put("Tongan paʻanga", "汤加潘加|潘加") + .put("Ukrainian hryvnia", "乌克兰格里夫纳|格里夫纳") + .put("Vanuatu vatu", "瓦努阿图瓦图") + .put("Vietnamese dong", "越南盾") + .put("Indonesian rupiah", "印度尼西亚盾") + .put("Netherlands guilder", "荷兰盾|荷属安的列斯盾|列斯盾") + .put("Surinam florin", "苏里南盾") + .put("Guilder", "盾") + .put("Zambian kwacha", "赞比亚克瓦查") + .put("Moroccan dirham", "摩洛哥迪拉姆") + .put("United Arab Emirates dirham", "阿联酋迪拉姆") + .put("Azerbaijani manat", "阿塞拜疆马纳特") + .put("Turkmenistan manat", "土库曼马纳特") + .put("Manat", "马纳特") + .put("Somali shilling", "索马里先令|索马利先令") + .put("Somaliland shilling", "索马里兰先令") + .put("Tanzanian shilling", "坦桑尼亚先令") + .put("Ugandan shilling", "乌干达先令") + .put("Romanian leu", "罗马尼亚列伊") + .put("Moldovan leu", "摩尔多瓦列伊") + .put("Leu", "列伊") + .put("Ban", "巴尼") + .put("Nepalese rupee", "尼泊尔卢比") + .put("Pakistani rupee", "巴基斯坦卢比") + .put("Indian rupee", "印度卢比") + .put("Seychellois rupee", "塞舌尔卢比") + .put("Mauritian rupee", "毛里求斯卢比") + .put("Maldivian rufiyaa", "马尔代夫卢比") + .put("Sri Lankan rupee", "斯里兰卡卢比") + .put("Rupee", "卢比") + .put("Czech koruna", "捷克克朗") + .put("Danish krone", "丹麦克朗|丹麦克郎") + .put("Norwegian krone", "挪威克朗") + .put("Faroese króna", "法罗克朗") + .put("Icelandic króna", "冰岛克朗") + .put("Swedish krona", "瑞典克朗") + .put("Krone", "克朗") + .put("Øre", "奥依拉|奥拉|埃利") + .put("West African CFA franc", "非共体法郎") + .put("Central African CFA franc", "中非法郎|中非金融合作法郎") + .put("Comorian franc", "科摩罗法郎") + .put("Congolese franc", "刚果法郎") + .put("Burundian franc", "布隆迪法郎") + .put("Djiboutian franc", "吉布提法郎") + .put("CFP franc", "太平洋法郎") + .put("Guinean franc", "几内亚法郎") + .put("Swiss franc", "瑞士法郎") + .put("Rwandan franc", "卢旺达法郎") + .put("Belgian franc", "比利时法郎") + .put("Rappen", "瑞士分|瑞士生丁") + .put("Franc", "法郎") + .put("Centime", "生丁|仙士") + .put("Russian ruble", "俄国卢布|俄罗斯卢布") + .put("Transnistrian ruble", "德涅斯特卢布") + .put("Belarusian ruble", "白俄罗斯卢布") + .put("Kopek", "戈比") + .put("Ruble", "卢布") + .put("Algerian dinar", "阿尔及利亚第纳尔") + .put("Bahraini dinar", "巴林第纳尔") + .put("Iraqi dinar", "伊拉克第纳尔") + .put("Jordanian dinar", "约旦第纳尔") + .put("Kuwaiti dinar", "科威特第纳尔|科威特币") + .put("Libyan dinar", "利比亚第纳尔") + .put("Serbian dinar", "塞尔维亚第纳尔|塞尔维亚币") + .put("Tunisian dinar", "突尼斯第纳尔") + .put("Dinar", "第纳尔") + .put("Fils", "费尔") + .put("Para", "帕拉") + .put("Millime", "米利姆") + .put("Argentine peso", "阿根廷比索") + .put("Chilean peso", "智利比索") + .put("Colombian peso", "哥伦比亚比索") + .put("Cuban peso", "古巴比索") + .put("Dominican peso", "多米尼加比索") + .put("Mexican peso", "墨西哥比索") + .put("Philippine peso", "菲律宾比索") + .put("Uruguayan peso", "乌拉圭比索") + .put("Peso", "比索") + .put("Centavo", "仙|菲辅币") + .put("Alderney pound", "奥尔德尼镑") + .put("British pound", "英镑") + .put("Guernsey pound", "根西镑") + .put("Saint Helena pound", "圣赫勒拿镑") + .put("Egyptian pound", "埃及镑") + .put("Falkland Islands pound", "福克兰镑") + .put("Gibraltar pound", "直布罗陀镑") + .put("Manx pound", "马恩岛镑") + .put("Jersey pound", "泽西岛镑") + .put("Lebanese pound", "黎巴嫩镑") + .put("South Sudanese pound", "南苏丹镑") + .put("Sudanese pound", "苏丹镑") + .put("Syrian pound", "叙利亚镑") + .put("Pence", "便士") + .put("Shilling", "先令") + .put("United States dollar", "美元|美金|美圆") + .put("East Caribbean dollar", "东加勒比元") + .put("Australian dollar", "澳大利亚元|澳元") + .put("Bahamian dollar", "巴哈马元") + .put("Barbadian dollar", "巴巴多斯元") + .put("Belize dollar", "伯利兹元") + .put("Bermudian dollar", "百慕大元") + .put("Brunei dollar", "文莱元") + .put("Singapore dollar", "新加坡元|新元") + .put("Canadian dollar", "加元|加拿大元") + .put("Cayman Islands dollar", "开曼岛元") + .put("New Zealand dollar", "新西兰元|纽元") + .put("Cook Islands dollar", "库克群岛元") + .put("Fijian dollar", "斐济元|斐币") + .put("Guyanese dollar", "圭亚那元") + .put("Hong Kong dollar", "蚊|港元|港圆|港币") + .put("Macau Pataca", "澳门币|澳门元") + .put("New Taiwan dollar", "箍|新台币|台币") + .put("Jamaican dollar", "牙买加元") + .put("Kiribati dollar", "吉里巴斯元") + .put("Liberian dollar", "利比里亚元") + .put("Namibian dollar", "纳米比亚元") + .put("Surinamese dollar", "苏里南元") + .put("Trinidad and Tobago dollar", "特立尼达多巴哥元") + .put("Tuvaluan dollar", "吐瓦鲁元") + .put("Chinese yuan", "人民币|人民币元|元人民币|块钱|块|元|圆") + .put("Fen", "分钱|分") + .put("Jiao", "毛钱|毛|角钱|角") + .put("Finnish markka", "芬兰马克") + .put("Penni", "盆尼") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STD") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?又|再)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "us$") + .put("British Virgin Islands dollar", "bvi$") + .put("Brunei dollar", "b$") + .put("Sen", "sen") + .put("Singapore dollar", "s$") + .put("Canadian dollar", "can$|c$|c $") + .put("Cayman Islands dollar", "ci$") + .put("New Zealand dollar", "nz$|nz $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hk$|hkd|hk $") + .put("Jamaican dollar", "j$") + .put("Namibian dollar", "nad|n$|n $") + .put("Solomon Islands dollar", "si$|si $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Turkish lira", "₺") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .build(); + + public static final List CurrencyAmbiguousValues = Arrays.asList("元", "仙", "分", "圆", "块", "毛", "盾", "箍", "蚊", "角"); + + public static final ImmutableMap DimensionSuffixList = ImmutableMap.builder() + .put("Meter", "米|公尺|m") + .put("Kilometer", "千米|公里|km") + .put("Decimeter", "分米|公寸|dm") + .put("Centimeter", "釐米|厘米|公分|cm") + .put("Micrometer", "毫米|公釐|mm") + .put("Microns", "微米") + .put("Picometer", "皮米") + .put("Nanometer", "纳米") + .put("Li", "里|市里") + .put("Zhang", "丈") + .put("Chi", "市尺|尺") + .put("Cun", "市寸|寸") + .put("Fen", "市分|分") + .put("Hao", "毫") + .put("Mile", "英里") + .put("Inch", "英寸") + .put("Foot", "呎|英尺") + .put("Yard", "码") + .put("Knot", "海里") + .put("Light year", "光年") + .put("Meter per second", "米每秒|米/秒|m/s") + .put("Kilometer per hour", "公里每小时|千米每小时|公里/小时|千米/小时|km/h") + .put("Kilometer per minute", "公里每分钟|千米每分钟|公里/分钟|千米/分钟|km/min") + .put("Kilometer per second", "公里每秒|千米每秒|公里/秒|千米/秒|km/s") + .put("Mile per hour", "英里每小时|英里/小时") + .put("Foot per second", "英尺每小时|英尺/小时") + .put("Foot per minute", "英尺每分钟|英尺/分钟") + .put("Yard per minute", "码每分|码/分") + .put("Yard per second", "码每秒|码/秒") + .put("Square centimetre", "平方厘米") + .put("Square decimeter", "平方分米") + .put("Square meter", "平方米") + .put("Square kilometer", "平方公里") + .put("Acre", "英亩|公亩") + .put("Hectare", "公顷") + .put("Mu", "亩|市亩") + .put("Liter", "公升|升|l") + .put("Milliliter", "毫升|ml") + .put("Cubic meter", "立方米") + .put("Cubic decimeter", "立方分米") + .put("Cubic millimeter", "立方毫米") + .put("Cubic feet", "立方英尺") + .put("Gallon", "加仑") + .put("Pint", "品脱") + .put("Dou", "市斗|斗") + .put("Dan", "市石|石") + .put("Kilogram", "千克|公斤|kg") + .put("Jin", "市斤|斤") + .put("Milligram", "毫克|mg") + .put("Barrel", "桶") + .put("Pot", "罐") + .put("Gram", "克|g") + .put("Ton", "公吨|吨|t") + .put("Pound", "磅") + .put("Ounce", "盎司") + .put("Liang", "两") + .put("Bit", "比特|位|b|bit") + .put("Kilobit", "千比特|千位|kb|Kb") + .put("Megabit", "兆比特|兆位|mb|Mb") + .put("Gigabit", "十亿比特|千兆比特|十亿位|千兆位|gb|Gb") + .put("Terabit", "万亿比特|兆兆比特|万亿位|兆兆位|tb|Tb") + .put("Petabit", "千兆兆比特|千万亿比特|千兆兆位|千万亿位|pb|Pb") + .put("Byte", "字节|byte|Byte") + .put("Kilobyte", "千字节|kB|KB") + .put("Megabyte", "兆字节|mB|MB") + .put("Gigabyte", "十亿字节|千兆字节|gB|GB") + .put("Terabyte", "万亿字节|兆兆字节|tB|TB") + .put("Petabyte", "千兆兆字节|千万亿字节|pB|PB") + .build(); + + public static final List DimensionAmbiguousValues = Arrays.asList("丈", "位", "克", "分", "升", "寸", "尺", "斗", "斤", "桶", "毫", "石", "码", "磅", "米", "罐", "里", "m", "km", "dm", "cm", "mm", "l", "ml", "kg", "mg", "g", "t", "b", "byte", "kb", "mb", "gb", "tb", "pb"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("五角", "五角大楼") + .put("普尔", "标准普尔") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "华氏温度|华氏度|°f") + .put("K", "开尔文温度|开氏度|凯氏度|K|k") + .put("R", "兰氏温度|°r") + .put("C", "摄氏温度|摄氏度|°c") + .put("Degree", "度") + .build(); + + public static final ImmutableMap TemperaturePrefixList = ImmutableMap.builder() + .put("F", "华氏温度|华氏") + .put("K", "开氏温度|开氏") + .put("R", "兰氏温度|兰氏") + .put("C", "摄氏温度|摄氏") + .build(); + + public static final List TemperatureAmbiguousValues = Arrays.asList("度", "k"); + + public static final String HalfUnitRegex = "半"; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java new file mode 100644 index 000000000..3ce10a864 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/EnglishNumericWithUnit.java @@ -0,0 +1,721 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class EnglishNumericWithUnit { + + public static final ImmutableMap AgePrefixList = ImmutableMap.builder() + .put("Age", "Age|age") + .build(); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "years old|year old|year-old|years-old|-year-old|-years-old|years of age|year of age|yo") + .put("Month", "months old|month old|month-old|months-old|-month-old|-months-old|month of age|months of age|mo") + .put("Week", "weeks old|week old|week-old|weeks-old|-week-old|-weeks-old|week of age|weeks of age") + .put("Day", "days old|day old|day-old|days-old|-day-old|-days-old|day of age|days of age") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("yo", "mo"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Square kilometer", "sq km|sq kilometer|sq kilometre|sq kilometers|sq kilometres|square kilometer|square kilometre|square kilometers|square kilometres|km2|km^2|km²") + .put("Square hectometer", "sq hm|sq hectometer|sq hectometre|sq hectometers|sq hectometres|square hectometer|square hectometre|square hectometers|square hectometres|hm2|hm^2|hm²|hectare|hectares") + .put("Square decameter", "sq dam|sq decameter|sq decametre|sq decameters|sq decametres|square decameter|square decametre|square decameters|square decametres|sq dekameter|sq dekametre|sq dekameters|sq dekametres|square dekameter|square dekametre|square dekameters|square dekametres|dam2|dam^2|dam²") + .put("Square meter", "sq m|sq meter|sq metre|sq meters|sq metres|sq metre|square meter|square meters|square metre|square metres|m2|m^2|m²") + .put("Square decimeter", "sq dm|sq decimeter|sq decimetre|sq decimeters|sq decimetres|square decimeter|square decimetre|square decimeters|square decimetres|dm2|dm^2|dm²") + .put("Square centimeter", "sq cm|sq centimeter|sq centimetre|sq centimeters|sq centimetres|square centimeter|square centimetre|square centimeters|square centimetres|cm2|cm^2|cm²") + .put("Square millimeter", "sq mm|sq millimeter|sq millimetre|sq millimeters|sq millimetres|square millimeter|square millimetre|square millimeters|square millimetres|mm2|mm^2|mm²") + .put("Square inch", "sq in|sq inch|square inch|square inches|in2|in^2|in²") + .put("Square foot", "sqft|sq ft|sq foot|sq feet|square foot|square feet|feet2|feet^2|feet²|ft2|ft^2|ft²") + .put("Square mile", "sq mi|sq mile|sqmiles|square mile|square miles|mi2|mi^2|mi²") + .put("Square yard", "sq yd|sq yard|sq yards|square yard|square yards|yd2|yd^2|yd²") + .put("Acre", "-acre|acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazian apsar", "abkhazian apsar|apsars") + .put("Afghan afghani", "afghan afghani|؋|afn|afghanis|afghani") + .put("Pul", "pul") + .put("Euro", "euros|euro|€|eur") + .put("Cent", "cents|cent|-cents|-cent") + .put("Albanian lek", "albanian lek|leks|lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Angolan kwanza", "angolan kwanza|kz|aoa|kwanza|kwanzas|angolan kwanzas") + .put("Armenian dram", "armenian drams|armenian dram") + .put("Aruban florin", "aruban florin|ƒ|awg|aruban florins") + .put("Bangladeshi taka", "bangladeshi taka|৳|bdt|taka|takas|bangladeshi takas") + .put("Paisa", "poisha|paisa") + .put("Bhutanese ngultrum", "bhutanese ngultrum|nu.|btn") + .put("Chetrum", "chetrums|chetrum") + .put("Bolivian boliviano", "bolivian boliviano|bob|bs.|bolivia boliviano|bolivia bolivianos|bolivian bolivianos") + .put("Bosnia and Herzegovina convertible mark", "bosnia and herzegovina convertible mark|bam") + .put("Fening", "fenings|fenings") + .put("Botswana pula", "botswana pula|bwp|pula|pulas|botswana pulas") + .put("Thebe", "thebe") + .put("Brazilian real", "brazilian real|r$|brl|brazil real|brazil reals|brazilian reals") + .put("Bulgarian lev", "bulgarian lev|bgn|лв|bulgaria lev|bulgaria levs|bulgarian levs") + .put("Stotinka", "stotinki|stotinka") + .put("Cambodian riel", "cambodian riel|khr|៛|cambodia riel|cambodia riels|cambodian riels") + .put("Cape Verdean escudo", "cape verdean escudo|cve") + .put("Costa Rican colón", "costa rican colón|costa rican colóns|crc|₡|costa rica colón|costa rica colóns|costa rican colon|costa rican colons|costa rica colon|costa rica colons") + .put("Salvadoran colón", "svc|salvadoran colón|salvadoran colóns|salvador colón|salvador colóns|salvadoran colon|salvadoran colons|salvador colon|salvador colons") + .put("Céntimo", "céntimo") + .put("Croatian kuna", "croatian kuna|kn|hrk|croatia kuna|croatian kunas|croatian kuna kunas") + .put("Lipa", "lipa") + .put("Czech koruna", "czech koruna|czk|kč|czech korunas") + .put("Haléř", "haléř") + .put("Eritrean nakfa", "eritrean nakfa|nfk|ern|eritrean nakfas") + .put("Ethiopian birr", "ethiopian birr|etb") + .put("Gambian dalasi", "gmd") + .put("Butut", "bututs|butut") + .put("Georgian lari", "georgian lari|lari|gel|₾") + .put("Tetri", "tetri") + .put("Ghanaian cedi", "ghanaian cedi|ghs|₵|gh₵") + .put("Pesewa", "pesewas|pesewa") + .put("Guatemalan quetzal", "guatemalan quetzal|gtq|guatemala quetzal") + .put("Haitian gourde", "haitian gourde|htg") + .put("Honduran lempira", "honduran lempira|hnl") + .put("Hungarian forint", "hungarian forint|huf|ft|hungary forint|hungary forints|hungarian forints") + .put("Fillér", "fillér") + .put("Iranian rial", "iranian rial|irr|iran rial|iran rials|iranian rials") + .put("Yemeni rial", "yemeni rial|yer|yemeni rials") + .put("Israeli new shekel", "₪|ils|agora") + .put("Lithuanian litas", "ltl|lithuanian litas|lithuan litas|lithuanian lit|lithuan lit") + .put("Japanese yen", "japanese yen|jpy|yen|-yen|¥|yens|japanese yens|japan yen|japan yens") + .put("Kazakhstani tenge", "kazakhstani tenge|kazakh tenge|kazak tenge|kzt") + .put("Kenyan shilling", "kenyan shilling|kes") + .put("North Korean won", "north korean won|kpw|north korean wons") + .put("South Korean won", "south korean won|krw|south korean wons") + .put("Korean won", "korean won|₩|korean wons") + .put("Kyrgyzstani som", "kyrgyzstani som|kgs") + .put("Uzbekitan som", "uzbekitan som|uzs") + .put("Lao kip", "lao kip|lak|₭n|₭") + .put("Att", "att") + .put("Lesotho loti", "lesotho loti|lsl|loti") + .put("Sente", "sente|lisente") + .put("South African rand", "south african rand|zar|south africa rand|south africa rands|south african rands") + .put("Macanese pataca", "macanese pataca|mop$|mop") + .put("Avo", "avos|avo") + .put("Macedonian denar", "macedonian denar|mkd|ден") + .put("Deni", "deni") + .put("Malagasy ariary", "malagasy ariary|mga") + .put("Iraimbilanja", "iraimbilanja") + .put("Malawian kwacha", "malawian kwacha|mk|mwk") + .put("Tambala", "tambala") + .put("Malaysian ringgit", "malaysian ringgit|rm|myr|malaysia ringgit|malaysia ringgits|malaysian ringgits") + .put("Mauritanian ouguiya", "mauritanian ouguiya|um|mro|mauritania ouguiya|mauritania ouguiyas|mauritanian ouguiyas") + .put("Khoums", "khoums") + .put("Mongolian tögrög", "mongolian tögrög|mnt|₮|mongolia tögrög|mongolia tögrögs|mongolian tögrögs|mongolian togrog|mongolian togrogs|mongolia togrog|mongolia togrogs") + .put("Mozambican metical", "mozambican metical|mt|mzn|mozambica metical|mozambica meticals|mozambican meticals") + .put("Burmese kyat", "burmese kyat|ks|mmk") + .put("Pya", "pya") + .put("Nicaraguan córdoba", "nicaraguan córdoba|nio") + .put("Nigerian naira", "nigerian naira|naira|ngn|₦|nigeria naira|nigeria nairas|nigerian nairas") + .put("Kobo", "kobo") + .put("Turkish lira", "turkish lira|try|tl|turkey lira|turkey liras|turkish liras") + .put("Kuruş", "kuruş") + .put("Omani rial", "omani rial|omr|ر.ع.") + .put("Panamanian balboa", "panamanian balboa|b/.|pab") + .put("Centesimo", "centesimo") + .put("Papua New Guinean kina", "papua new guinean kina|kina|pgk") + .put("Toea", "toea") + .put("Paraguayan guaraní", "paraguayan guaraní|₲|pyg") + .put("Peruvian sol", "peruvian sol|soles|sol|peruvian nuevo sol") + .put("Polish złoty", "złoty|polish złoty|zł|pln|zloty|polish zloty|poland zloty|poland złoty") + .put("Grosz", "groszy|grosz|grosze") + .put("Qatari riyal", "qatari riyal|qar|qatari riyals|qatar riyal|qatar riyals") + .put("Saudi riyal", "saudi riyal|sar|saudi riyals") + .put("Riyal", "riyal|riyals|rial|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Samoan tālā", "samoan tālā|tālā|tala|ws$|samoa|wst|samoan tala") + .put("Sene", "sene") + .put("São Tomé and Príncipe dobra", "são tomé and príncipe dobra|dobras|dobra") + .put("Sierra Leonean leone", "sierra leonean leone|sll|leone|le") + .put("Peseta", "pesetas|peseta") + .put("Netherlands guilder", "florin|netherlands antillean guilder|ang|nederlandse gulden|guilders|guilder|gulden|-guilders|-guilder|dutch guilders|dutch guilder|fl") + .put("Swazi lilangeni", "swazi lilangeni|lilangeni|szl|emalangeni") + .put("Tajikistani somoni", "tajikistani somoni|tjs|somoni") + .put("Diram", "dirams|diram") + .put("Thai baht", "thai baht|฿|thb|baht") + .put("Satang", "satang|satangs") + .put("Tongan paʻanga", "tongan paʻanga|paʻanga|tongan pa'anga|pa'anga") + .put("Seniti", "seniti") + .put("Ukrainian hryvnia", "ukrainian hryvnia|hyrvnia|uah|₴|ukrain hryvnia|ukrain hryvnias|ukrainian hryvnias") + .put("Vanuatu vatu", "vanuatu vatu|vatu|vuv") + .put("Venezuelan bolívar", "venezuelan bolívar|venezuelan bolívars|bs.f.|vef|bolívar fuerte|venezuelan bolivar|venezuelan bolivars|venezuela bolivar|venezuela bolivarsvenezuelan bolivar|venezuelan bolivars") + .put("Vietnamese dong", "vietnamese dong|vnd|đồng|vietnam dong|vietnamese dongs|vietnam dongs") + .put("Zambian kwacha", "zambian kwacha|zk|zmw|zambia kwacha|kwachas|zambian kwachas") + .put("Moroccan dirham", "moroccan dirham|mad|د.م.") + .put("United Arab Emirates dirham", "united arab emirates dirham|د.إ|aed") + .put("Azerbaijani manat", "azerbaijani manat|azn") + .put("Turkmenistan manat", "turkmenistan manat|turkmenistan new manat|tmt") + .put("Manat", "manats|manat") + .put("Qəpik", "qəpik") + .put("Somali shilling", "somali shillings|somali shilling|shilin soomaali|-shilin soomaali|scellino|shilin|sh.so.|sos") + .put("Somaliland shilling", "somaliland shillings|somaliland shilling|soomaaliland shilin") + .put("Tanzanian shilling", "tanzanian shilling|tanzanian shillings|tsh|tzs|tanzania shilling|tanzania shillings") + .put("Ugandan shilling", "ugandan shilling|ugandan shillings|ugx|uganda shilling|uganda shillings") + .put("Romanian leu", "romanian leu|lei|ron|romania leu") + .put("Moldovan leu", "moldovan leu|mdl|moldova leu") + .put("Leu", "leu") + .put("Ban", "bani|-ban|ban") + .put("Nepalese rupee", "nepalese rupees|nepalese rupee|npr") + .put("Pakistani rupee", "pakistani rupees|pakistani rupee|pkr") + .put("Indian rupee", "indian rupees|indian rupee|inr|₹|india rupees|india rupee") + .put("Seychellois rupee", "seychellois rupees|seychellois rupee|scr|sr|sre") + .put("Mauritian rupee", "mauritian rupees|mauritian rupee|mur") + .put("Maldivian rufiyaa", "maldivian rufiyaas|maldivian rufiyaa|mvr|.ރ|maldive rufiyaas|maldive rufiyaa") + .put("Sri Lankan rupee", "sri lankan rupees|sri lankan rupee|lkr|රු|ரூ") + .put("Indonesian rupiah", "indonesian rupiah|rupiah|perak|rp|idr") + .put("Rupee", "rupee|rupees|rs") + .put("Danish krone", "danish krone|dkk|denmark krone|denmark krones|danish krones") + .put("Norwegian krone", "norwegian krone|nok|norway krone|norway krones|norwegian krones") + .put("Faroese króna", "faroese króna|faroese krona") + .put("Icelandic króna", "icelandic króna|isk|icelandic krona|iceland króna|iceland krona") + .put("Swedish krona", "swedish krona|sek|swedan krona") + .put("Krone", "kronor|krona|króna|krone|krones|kr|-kr") + .put("Øre", "Øre|oyra|eyrir") + .put("West African CFA franc", "west african cfa franc|xof|west africa cfa franc|west africa franc|west african franc") + .put("Central African CFA franc", "central african cfa franc|xaf|central africa cfa franc|central african franc|central africa franc") + .put("Comorian franc", "comorian franc|kmf") + .put("Congolese franc", "congolese franc|cdf") + .put("Burundian franc", "burundian franc|bif") + .put("Djiboutian franc", "djiboutian franc|djf") + .put("CFP franc", "cfp franc|xpf") + .put("Guinean franc", "guinean franc|gnf") + .put("Swiss franc", "swiss francs|swiss franc|chf|sfr.") + .put("Rwandan franc", "Rwandan franc|rwf|rf|r₣|frw") + .put("Belgian franc", "belgian franc|bi.|b.fr.|bef|belgium franc") + .put("Rappen", "rappen|-rappen") + .put("Franc", "francs|franc|fr.|fs") + .put("Centime", "centimes|centime|santim") + .put("Russian ruble", "russian ruble|₽|rub|russia ruble|russia ₽|russian ₽|russian rubles|russia rubles") + .put("New Belarusian ruble", "new belarusian ruble|byn|new belarus ruble|new belarus rubles|new belarusian rubles") + .put("Old Belarusian ruble", "old belarusian ruble|byr|old belarus ruble|old belarus rubles|old belarusian rubles") + .put("Transnistrian ruble", "transnistrian ruble|prb|р.") + .put("Belarusian ruble", "belarusian ruble|belarus ruble|belarus rubles|belarusian rubles") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Ruble", "rubles|ruble|br") + .put("Algerian dinar", "algerian dinar|د.ج|dzd|algerian dinars|algeria dinar|algeria dinars") + .put("Bahraini dinar", "bahraini dinars|bahraini dinar|bhd|.د.ب") + .put("Santeem", "santeem|santeems") + .put("Iraqi dinar", "iraqi dinars|iraqi dinar|iraq dinars|iraq dinar|iqd|ع.د") + .put("Jordanian dinar", "jordanian dinars|jordanian dinar|د.ا|jod|jordan dinar|jordan dinars") + .put("Kuwaiti dinar", "kuwaiti dinars|kuwaiti dinar|kwd|د.ك") + .put("Libyan dinar", "libyan dinars|libyan dinar|libya dinars|libya dinar|lyd") + .put("Serbian dinar", "serbian dinars|serbian dinar|din.|rsd|дин.|serbia dinars|serbia dinar") + .put("Tunisian dinar", "tunisian dinars|tunisian dinar|tnd|tunisia dinars|tunisia dinar") + .put("Yugoslav dinar", "yugoslav dinars|yugoslav dinar|yun") + .put("Dinar", "dinars|dinar|denar|-dinars|-dinar") + .put("Fils", "fils|fulūs|-fils|-fil") + .put("Para", "para|napa") + .put("Millime", "millimes|millime") + .put("Argentine peso", "argentine peso|ars|argetina peso|argetina pesos|argentine pesos") + .put("Chilean peso", "chilean pesos|chilean peso|clp|chile peso|chile peso") + .put("Colombian peso", "colombian pesos|colombian peso|cop|colombia peso|colombia pesos") + .put("Cuban convertible peso", "cuban convertible pesos|cuban convertible peso|cuc|cuba convertible pesos|cuba convertible peso") + .put("Cuban peso", "cuban pesos|cuban peso|cup|cuba pesos|cuba peso") + .put("Dominican peso", "dominican pesos|dominican peso|dop|dominica pesos|dominica peso") + .put("Mexican peso", "mexican pesos|mexican peso|mxn|mexico pesos|mexico peso") + .put("Philippine peso", "piso|philippine pesos|philippine peso|₱|php") + .put("Uruguayan peso", "uruguayan pesos|uruguayan peso|uyu") + .put("Peso", "pesos|peso") + .put("Centavo", "centavos|centavo") + .put("Alderney pound", "alderney pounds|alderney pound|alderney £") + .put("British pound", "british pounds|british pound|british £|gbp|pound sterling|pound sterlings|sterling|pound scot|pound scots") + .put("Guernsey pound", "guernsey pounds|guernsey £|ggp") + .put("Ascension pound", "ascension pounds|ascension pound|ascension £") + .put("Saint Helena pound", "saint helena pounds|saint helena pound|saint helena £|shp") + .put("Egyptian pound", "egyptian pounds|egyptian pound|egyptian £|egp|ج.م|egypt pounds|egypt pound") + .put("Falkland Islands pound", "falkland islands pounds|falkland islands pound|falkland islands £|fkp|falkland island pounds|falkland island pound|falkland island £") + .put("Gibraltar pound", "gibraltar pounds|gibraltar pound|gibraltar £|gip") + .put("Manx pound", "manx pounds|manx pound|manx £|imp") + .put("Jersey pound", "jersey pounds|jersey pound|jersey £|jep") + .put("Lebanese pound", "lebanese pounds|lebanese pound|lebanese £|lebanan pounds|lebanan pound|lebanan £|lbp|ل.ل") + .put("South Georgia and the South Sandwich Islands pound", "south georgia and the south sandwich islands pounds|south georgia and the south sandwich islands pound|south georgia and the south sandwich islands £") + .put("South Sudanese pound", "south sudanese pounds|south sudanese pound|south sudanese £|ssp|south sudan pounds|south sudan pound|south sudan £") + .put("Sudanese pound", "sudanese pounds|sudanese pound|sudanese £|ج.س.|sdg|sudan pounds|sudan pound|sudan £") + .put("Syrian pound", "syrian pounds|syrian pound|syrian £|ل.س|syp|syria pounds|syria pound|syria £") + .put("Tristan da Cunha pound", "tristan da cunha pounds|tristan da cunha pound|tristan da cunha £") + .put("Pound", "pounds|pound|-pounds|-pound|£") + .put("Pence", "pence") + .put("Shilling", "shillings|shilling|shilingi|sh") + .put("Penny", "pennies|penny") + .put("United States dollar", "united states dollars|united states dollar|united states $|u.s. dollars|u.s. dollar|u s dollar|u s dollars|usd|american dollars|american dollar|us$|us dollar|us dollars|u.s dollar|u.s dollars") + .put("East Caribbean dollar", "east caribbean dollars|east caribbean dollar|east Caribbean $|xcd") + .put("Australian dollar", "australian dollars|australian dollar|australian $|australian$|aud|australia dollars|australia dollar|australia $|australia$") + .put("Bahamian dollar", "bahamian dollars|bahamian dollar|bahamian $|bahamian$|bsd|bahamia dollars|bahamia dollar|bahamia $|bahamia$") + .put("Barbadian dollar", "barbadian dollars|barbadian dollar|barbadian $|bbd") + .put("Belize dollar", "belize dollars|belize dollar|belize $|bzd") + .put("Bermudian dollar", "bermudian dollars|bermudian dollar|bermudian $|bmd|bermudia dollars|bermudia dollar|bermudia $") + .put("British Virgin Islands dollar", "british virgin islands dollars|british virgin islands dollar|british virgin islands $|bvi$|virgin islands dollars|virgin islands dolalr|virgin islands $|virgin island dollars|virgin island dollar|virgin island $") + .put("Brunei dollar", "brunei dollar|brunei $|bnd") + .put("Sen", "sen") + .put("Singapore dollar", "singapore dollars|singapore dollar|singapore $|s$|sgd") + .put("Canadian dollar", "canadian dollars|canadian dollar|canadian $|cad|can$|c$|canada dollars|canada dolllar|canada $") + .put("Cayman Islands dollar", "cayman islands dollars|cayman islands dollar|cayman islands $|kyd|ci$|cayman island dollar|cayman island doolars|cayman island $") + .put("New Zealand dollar", "new zealand dollars|new zealand dollar|new zealand $|nz$|nzd|kiwi") + .put("Cook Islands dollar", "cook islands dollars|cook islands dollar|cook islands $|cook island dollars|cook island dollar|cook island $") + .put("Fijian dollar", "fijian dollars|fijian dollar|fijian $|fjd|fiji dollars|fiji dollar|fiji $") + .put("Guyanese dollar", "guyanese dollars|guyanese dollar|gyd|gy$") + .put("Hong Kong dollar", "hong kong dollars|hong kong dollar|hong kong $|hk$|hkd|hk dollars|hk dollar|hk $|hongkong$") + .put("Jamaican dollar", "jamaican dollars|jamaican dollar|jamaican $|j$|jamaica dollars|jamaica dollar|jamaica $|jmd") + .put("Kiribati dollar", "kiribati dollars|kiribati dollar|kiribati $") + .put("Liberian dollar", "liberian dollars|liberian dollar|liberian $|liberia dollars|liberia dollar|liberia $|lrd") + .put("Micronesian dollar", "micronesian dollars|micronesian dollar|micronesian $") + .put("Namibian dollar", "namibian dollars|namibian dollar|namibian $|nad|n$|namibia dollars|namibia dollar|namibia $") + .put("Nauruan dollar", "nauruan dollars|nauruan dollar|nauruan $") + .put("Niue dollar", "niue dollars|niue dollar|niue $") + .put("Palauan dollar", "palauan dollars|palauan dollar|palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands dollars|pitcairn islands dollar|pitcairn islands $|pitcairn island dollars|pitcairn island dollar|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands dollars|solomon islands dollar|solomon islands $|si$|sbd|solomon island dollars|solomon island dollar|solomon island $") + .put("Surinamese dollar", "surinamese dollars|surinamese dollar|surinamese $|srd") + .put("New Taiwan dollar", "new taiwan dollars|new taiwan dollar|nt$|twd|ntd") + .put("Trinidad and Tobago dollar", "trinidad and tobago dollars|trinidad and tobago dollar|trinidad and tobago $|trinidad $|trinidad dollar|trinidad dollars|trinidadian dollar|trinidadian dollars|trinidadian $|ttd") + .put("Tuvaluan dollar", "tuvaluan dollars|tuvaluan dollar|tuvaluan $") + .put("Dollar", "dollars|dollar|$") + .put("Chinese yuan", "yuan|kuai|chinese yuan|renminbi|cny|rmb|¥|元") + .put("Fen", "fen") + .put("Jiao", "jiao|mao") + .put("Finnish markka", "suomen markka|finnish markka|finsk mark|fim|markkaa|markka") + .put("Penni", "penniä|penni") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STN") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("New Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?and)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dobra", "db|std") + .put("Dollar", "$") + .put("Brazilian Real", "R$") + .put("United States dollar", "united states $|us$|us $|u.s. $|u.s $") + .put("East Caribbean dollar", "east caribbean $") + .put("Australian dollar", "australian $|australia $") + .put("Bahamian dollar", "bahamian $|bahamia $") + .put("Barbadian dollar", "barbadian $|barbadin $") + .put("Belize dollar", "belize $") + .put("Bermudian dollar", "bermudian $") + .put("British Virgin Islands dollar", "british virgin islands $|bvi$|virgin islands $|virgin island $|british virgin island $") + .put("Brunei dollar", "brunei $|b$") + .put("Sen", "sen") + .put("Singapore dollar", "singapore $|s$") + .put("Canadian dollar", "canadian $|can$|c$|c $|canada $") + .put("Cayman Islands dollar", "cayman islands $|ci$|cayman island $") + .put("New Zealand dollar", "new zealand $|nz$|nz $") + .put("Cook Islands dollar", "cook islands $|cook island $") + .put("Fijian dollar", "fijian $|fiji $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hong kong $|hk$|hkd|hk $") + .put("Indian rupee", "₹") + .put("Jamaican dollar", "jamaican $|j$|jamaica $") + .put("Kiribati dollar", "kiribati $") + .put("Liberian dollar", "liberian $|liberia $") + .put("Micronesian dollar", "micronesian $") + .put("Namibian dollar", "namibian $|nad|n$|namibia $") + .put("Nauruan dollar", "nauruan $") + .put("Niue dollar", "niue $") + .put("Palauan dollar", "palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands $|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands $|si$|si $|solomon island $") + .put("Surinamese dollar", "surinamese $|surinam $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Trinidad and Tobago dollar", "trinidad and tobago $|trinidad $|trinidadian $") + .put("Tuvaluan dollar", "tuvaluan $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .put("Turkish lira", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kiwi", "kina", "kobo", "lari", "lipa", "napa", "para", "sfr.", "taka", "tala", "toea", "vatu", "yuan", "all", "ang", "ban", "bob", "btn", "byr", "cad", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "lei", "mga", "mop", "nad", "omr", "pul", "sar", "sbd", "scr", "sdg", "sek", "sen", "sol", "sos", "std", "try", "yer", "yen", "db"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|Kb|kbit") + .put("Megabit", "megabit|megabits|mb|Mb|mbit") + .put("Gigabit", "gigabit|gigabits|gb|Gb|gbit") + .put("Terabit", "terabit|terabits|tb|Tb|tbit") + .put("Petabit", "petabit|petabits|pb|Pb|pbit") + .put("Byte", "-byte|byte|bytes") + .put("Kilobyte", "-kilobyte|-kilobytes|kilobyte|kB|KB|kilobytes|kilo byte|kilo bytes|kbyte") + .put("Megabyte", "-megabyte|-megabytes|megabyte|mB|MB|megabytes|mega byte|mega bytes|mbyte") + .put("Gigabyte", "-gigabyte|-gigabytes|gigabyte|gB|GB|gigabytes|giga byte|giga bytes|gbyte") + .put("Terabyte", "-terabyte|-terabytes|terabyte|tB|TB|terabytes|tera byte|tera bytes|tbyte") + .put("Petabyte", "-petabyte|-petabytes|petabyte|pB|PB|petabytes|peta byte|peta bytes|pbyte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("barrel", "barrels", "grain", "pound", "stone", "yards", "yard", "cord", "dram", "feet", "foot", "gill", "knot", "peck", "cup", "fps", "pts", "in", "dm", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^))"; + + public static final String BuildSuffix = "(?=(\\s|\\W|$))"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilometer", "km|kilometer|kilometre|kilometers|kilometres|kilo meter|kilo meters|kilo metres|kilo metre") + .put("Hectometer", "hm|hectometer|hectometre|hectometers|hectometres|hecto meter|hecto meters|hecto metres|hecto metre") + .put("Decameter", "dam|decameter|decametre|decameters|decametres|deca meter|deca meters|deca metres|deca metre") + .put("Meter", "m|meter|metre|meters|metres") + .put("Decimeter", "dm|decimeter|decimeters|decimetre|decimetres|deci meter|deci meters|deci metres|deci metre") + .put("Centimeter", "cm|centimeter|centimeters|centimetre|centimetres|centi meter|centi meters|centi metres|centi metre") + .put("Millimeter", "mm|millimeter|millimeters|millimetre|millimetres|milli meter|milli meters|milli metres|milli metre") + .put("Micrometer", "μm|micrometer|micrometre|micrometers|micrometres|micro meter|micro meters|micro metres|micro metre") + .put("Nanometer", "nm|nanometer|nanometre|nanometers|nanometres|nano meter|nano meters|nano metres|nano metre") + .put("Picometer", "pm|picometer|picometre|picometers|picometres|pico meter|pico meters|pico metres|pico metre") + .put("Mile", "-mile|mile|miles") + .put("Yard", "yard|yards") + .put("Inch", "-inch|inch|inches|in|\"") + .put("Foot", "-foot|foot|feet|ft") + .put("Light year", "light year|light-year|light years|light-years") + .put("Pt", "pt|pts") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "yard", "yards", "pm", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Meter per second", "meters / second|m/s|meters per second|metres per second|meter per second|metre per second") + .put("Kilometer per hour", "km/h|kilometres per hour|kilometers per hour|kilometer per hour|kilometre per hour") + .put("Kilometer per minute", "km/min|kilometers per minute|kilometres per minute|kilometer per minute|kilometre per minute") + .put("Kilometer per second", "km/s|kilometers per second|kilometres per second|kilometer per second|kilometre per second") + .put("Mile per hour", "mph|mile per hour|miles per hour|mi/h|mile / hour|miles / hour|miles an hour") + .put("Knot", "kt|knot|kn") + .put("Foot per second", "ft/s|foot/s|foot per second|feet per second|fps") + .put("Foot per minute", "ft/min|foot/min|foot per minute|feet per minute") + .put("Yard per minute", "yards per minute|yard per minute|yards / minute|yards/min|yard/min") + .put("Yard per second", "yards per second|yard per second|yards / second|yards/s|yard/s") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "degrees fahrenheit|degree fahrenheit|deg fahrenheit|degs fahrenheit|fahrenheit|°f|degrees farenheit|degree farenheit|deg farenheit|degs farenheit|degrees f|degree f|deg f|degs f|farenheit|f") + .put("K", "k|K|kelvin") + .put("R", "rankine|°r") + .put("D", "delisle|°de") + .put("C", "degrees celsius|degree celsius|deg celsius|degs celsius|celsius|degrees celcius|degree celcius|celcius|deg celcius|degs celcius|degrees centigrade|degree centigrade|centigrade|degrees centigrate|degree centigrate|degs centigrate|deg centigrate|centigrate|degrees c|degree c|deg c|degs c|°c|c") + .put("Degree", "degree|degrees|deg.|deg|°") + .build(); + + public static final List AmbiguousTemperatureUnitList = Arrays.asList("c", "f", "k"); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Cubic meter", "m3|cubic meter|cubic meters|cubic metre|cubic metres") + .put("Cubic centimeter", "cubic centimeter|cubic centimetre|cubic centimeters|cubic centimetres") + .put("Cubic millimiter", "cubic millimiter|cubic millimitre|cubic millimiters|cubic millimitres") + .put("Hectoliter", "hectoliter|hectolitre|hectoliters|hectolitres") + .put("Decaliter", "decaliter|decalitre|dekaliter|dekalitre|decaliters|decalitres|dekaliters|dekalitres") + .put("Liter", "l|litre|liter|liters|litres") + .put("Deciliter", "dl|deciliter|decilitre|deciliters|decilitres") + .put("Centiliter", "cl|centiliter|centilitre|centiliters|centilitres") + .put("Milliliter", "ml|mls|millilitre|milliliter|millilitres|milliliters") + .put("Cubic yard", "cubic yard|cubic yards") + .put("Cubic inch", "cubic inch|cubic inches") + .put("Cubic foot", "cubic foot|cubic feet") + .put("Cubic mile", "cubic mile|cubic miles") + .put("Fluid ounce", "fl oz|fluid ounce|fluid ounces") + .put("Teaspoon", "teaspoon|teaspoons") + .put("Tablespoon", "tablespoon|tablespoons") + .put("Pint", "pint|pints") + .put("Volume unit", "fluid dram|gill|quart|minim|cord|peck|bushel|hogshead|barrels|barrel|bbl") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("l", "ounce", "oz", "cup", "peck", "cord", "gill"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogram", "kg|kilogram|kilograms|kilo|kilos") + .put("Gram", "g|gram|grams") + .put("Milligram", "mg|milligram|milligrams") + .put("Gallon", "-gallon|gallons|gallon") + .put("Metric ton", "metric tons|metric ton") + .put("Ton", "-ton|ton|tons|tonne|tonnes") + .put("Pound", "pound|pounds|lb|lbs") + .put("Ounce", "-ounce|ounce|oz|ounces") + .put("Weight unit", "pennyweight|grain|british long ton|us short hundredweight|stone|dram") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz", "stone", "dram", "lbs"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bm\\b", "((('|’)\\s*m)|(m\\s*('|’)))") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java new file mode 100644 index 000000000..56040b03c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/FrenchNumericWithUnit.java @@ -0,0 +1,401 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class FrenchNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Ans", "ans") + .put("Mois", "mois d'âge|mois d'age|mois") + .put("Semaines", "semaine|semaines|semaines d'âge|semaines d'age") + .put("Jour", "jours|jour") + .build(); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Kilomètre carré", "km2|km^2|km²|kilomètres carrés|kilomètre carré") + .put("Hectomètre carré", "hm2|hm^2|hm²|hectomètre carré|hectomètres carrés") + .put("Décamètre carré", "dam2|dam^2|dam²|décamètre carré|décamètres carrés") + .put("Mètre carré", "m2|m^2|m²|mètre carré|mètres carrés") + .put("Décimètre carré", "dm2|dm^2|dm²|décimètre carré|décimètres carrés") + .put("Centimètre carré", "cm2|cm^2|cm²|centimètre carré|centimètres carrés") + .put("Millimètre carré", "mm2|mm^2|mm²|millimètre carré|millimètres carrés") + .put("Pouce carré", "pouces2|po2|pouce carré|pouces carrés|in^2|in²|in2") + .put("Pied carré", "pied carré|pieds carrés|pi2|pi^2|pi²") + .put("Mile carré", "mile carré|miles carrés|mi2|mi^2|mi²") + .put("Acre", "acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazie apsar", "abkhazie apsar|apsars") + .put("Afghan afghani", "afghan afghani|؋|afn|afghanis|afghani") + .put("Pul", "pul") + .put("Euro", "euros|euro|€|eur|d'euros") + .put("Cent", "cents|cent|-cents|-cent") + .put("lek Albanais", "lek albanais|leks|lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Kwanza angolais", "kwanza angolais|kz|aoa|kwanza|kwanzas") + .put("Dram arménien", "dram arménien|drams arméniens") + .put("Florins d'Aruba", "florins aruba|ƒ|awg") + .put("Bangladeshi taka", "bangladeshi taka|৳|bdt|taka|takas|bangladeshi takas") + .put("Paisa", "poisha|paisa") + .put("Ngultrum bhoutanais", "ngultrum bhoutanais|nu.|btn") + .put("Chetrum", "chetrums|chetrum") + .put("Boliviano bolivien", "boliviano bolivien|bolivianos bolivien|bolivianos bolivie|boliviano bolivie|bob|bs.") + .put("Bosnie-Herzégovine mark convertible", "bosnie-herzégovine mark convertible|bosnie-et-herzégovine mark convertible|bam") + .put("Fening", "fening|fenings") + .put("Pula", "pula|bwp") + .put("Thebe", "thebe") + .put("Réal brésilien", "réal brésilien|réals brésilien|r$|brl|real bresil|reals bresilien") + .put("Lev bulgare", "lev bulgare|levs bulgare|lv|bgn") + .put("Stotinki búlgaro", "stotinki bulgare") + .put("Riel cambodgien", "riel cambodgien|khr|៛") + .put("Escudo du cap-vert", "escudo cap-verdien|cve") + .put("Colon du costa rica", "colon du costa rica|colons du costa rica|crc|₡") + .put("Colon du salvador", "colon du salvador|colons du salvador|svc") + .put("Kuna croate", "kuna croate|kunas croate|kn|hrk") + .put("Lipa", "lipa") + .put("Couronne tchèque", "couronne tchèque|couronnes tchèque|czk|Kč") + .put("Haléř", "haléř") + .put("Nakfas érythréens", "nakfas érythréens|nfk|ern|nakfa érythréens") + .put("Birr éthiopien", "birr éthiopien|birrs éthiopien|etb") + .put("Dalasi gambienne", "gmd") + .put("Butut", "bututs|butut") + .put("Lari géorgien", "lari géorgie|lari géorgiens|gel|₾") + .put("Tetri géorgien", "tetri géorgie|tetris géorgiens") + .put("Cedi", "cedi|ghs|cedi ghanéen|gh₵") + .put("Pesewa", "pesewa|pesewas") + .put("Quetzal guatémaltèque", "quetzal guatémaltèque|gtq|quetzal|quetzales") + .put("Gourdes haïtiennes", "gourdes haïtiennes|gourdes|htg|gourde haïtienne") + .put("Lempira hondurien", "lempira hondurien|hnl") + .put("Forint hongrois", "forint hongrois|huf|fg|forints hongrois") + .put("Fillér", "fillér") + .put("Rial iranien", "rial iranien|irr|rials iranien|rials iraniens") + .put("Litas lituanien", "litas lituanien|ltl|lit lithuanien|litas lithuanie") + .put("Yen Japonais", "yen japonais|yen japon|yens|jpy|yen|¥|-yen") + .put("Tenge kazakh", "tenge kazakh|kzt") + .put("Shilling kényan", "shilling kényan|kes|shillings kényans") + .put("Won coréen", "won coréen|won coréens|₩") + .put("Won sud-coréen", "won sud-coréen|won sud coréen|won sud-coréens|krw") + .put("Corée du nord won", "corée du nord won|corée nord won|kpw") + .put("Som Kirghizie", "som kirghizie|kgs") + .put("Sum Ouzbékistan", "sum ouzbékistan|sum ouzbeks|sum ouzbéks|uzs") + .put("Kip laotien", "kip laotien|lak|₭n|₭") + .put("Att", "att") + .put("Loti", "loti|maloti|lsl") + .put("Sente", "sente|lisente") + .put("Rand sud-africain", "rand sud-africain|zar") + .put("Pataca macanais", "pataca macanais|mop$|mop") + .put("Avo", "avos|avo") + .put("Dinar macédonien", "dinar macédonien|mkd|ден") + .put("Deni", "deni") + .put("Ariary malagache", "ariary malagache|mga") + .put("Iraimbilanja", "Iraimbilanja") + .put("Kwacha malawien", "kwacha malawien|mk|mwk") + .put("Tambala", "Tambala") + .put("Ringitt malaisien", "ringitt malaisien|rm|myr|ringitts malaisien") + .put("Ouguiya mauritanienne", "ouguiya|um|mro|ouguiya mauritanien|ouguiya mauritanienne") + .put("Khoums", "khoums") + .put("Togrogs mongoles", "togrogs mongoles|togrogs|tugriks|tögrög|mnt|₮|tögrög mongoles|tögrög mongolie|togrogs mongolie") + .put("Metical mozambique", "metical du mozambique|metical mozambique|mt|mzn|meticals mozambique") + .put("Kyat birmanie", "kyat birmanie|ks|mmk") + .put("Pya", "pya") + .put("Cordoba nicaraguayen", "cordoba nicaraguayen|córdoba nicaraguayen|nio|córdoba oro|cordoba oro nicaraguayen") + .put("Naira nigérians", "naira nigérians|naira|ngm|₦|nairas nigérians") + .put("Livre turque", "livre turque|try|tl|livre turques") + .put("Kuruş", "kuruş") + .put("Rials omanais", "rials omanais|omr|ر.ع.|rial omanais") + .put("Balboa panaméennes", "balboa panaméennes|balboa|pab") + .put("Kina", "kina|pkg|pgk") + .put("Toea", "toea") + .put("Guaraní paraguayen", "guaraní paraguayen|₲|pyg") + .put("Sol péruvien", "nuevo sol péruvien|soles|sol|sol péruvien") + .put("Złoty polonais", "złoty polonais|złoty|zł|pln|zloty|zloty polonais") + .put("Groxz", "groszy|grosz|grosze") + .put("Riyal qatari", "riyal qatari|qar|riyals qatari") + .put("Riyal saudi", "riyal saudi|sar|riyals saudi") + .put("Riyal", "riyal|riyals|rial|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Tala", "tala|tālā|ws$|sat|wst") + .put("Sene", "sene") + .put("Dobra", "dobra|db|std") + .put("Leone", "leone|sll") + .put("Florins Néerlandais", "florins hollandais|florins néerlandais|florins|ang|florin|fl") + .put("Lilangeni", "lilangeni|szl") + .put("Somoni tadjikistan", "somoni tadjikistan|tjs|somoni") + .put("Diram", "dirams|diram") + .put("Baht thaïlandais", "baht thaïlandais|baht thailandais|baht thaï|baht thai|baht|฿|thb") + .put("Satang", "satang|satangs") + .put("Paʻanga", "paʻanga|pa'anga|top") + .put("Hryvnia ukrainien", "hryvnia ukrainien|hyrvnia|uah|₴|hryvnias ukrainien|hryvnia ukrainienne") + .put("Vanuatu vatu", "vanuatu vatu|vatu|vuv") + .put("Bolívar vénézuélien", "bolívar vénézuélien|bolivar venezuelien|bs.f.|vef|bolívars vénézuélien|bolivars venezuelien") + .put("Dong vietnamien", "dong vietnamien|dongs vietnamiens|dong|đồng|vnd|dông|dông vietnamiens") + .put("Kwacha de Zambie", "kwacha de zambie|zk|zmw|kwachas") + .put("Dirham marocain", "dirham marocain|mad|د.م.") + .put("Dirham des Émirats arabes unis", "dirham des Émirats arabes unis|د.إ|aed") + .put("Manat azerbaïdjanais", "manat azerbaïdjanais|manat azerbaidjanais|azn") + .put("Manat turkmène", "manat turkmène|tmt|manat turkmene") + .put("Manat", "manats|manat") + .put("Qəpik", "qəpik") + .put("Shilling somalien", "shilling somalien|shillings somalien|sos") + .put("Shilling tanzanien", "shilling tanzanien|shillings tanzanien|tzs|tsh|shilling tanzanienne|shillings tanzanienne") + .put("Shilling ougandais", "shilling ougandais|shillings ougandais|ugx") + .put("Leu roumain", "leu roumain|lei|leu roumaine|ron") + .put("Leu moldave", "leu meoldave|mdl") + .put("Leu", "leu") + .put("Ban", "bani|-ban|ban") + .put("Roupie népalaise", "roupie népalaise|roupie nepalaise|npr") + .put("Roupie pakistanaise", "roupie pakistanaise|pkr") + .put("Roupie indienne", "roupie indienne|inr|roupie indien|inr|₹") + .put("Roupie seychelloise", "roupie seychelloise|scr|sr|sre") + .put("Roupie mauricienne", "roupie mauricienne|mur") + .put("Rufiyaa maldives", "rufiyaa maldives|mvr|.ރ|rf") + .put("Roupie srilankaise", "roupie srilankaise|lrk|රු|ரூ") + .put("Rupiah Indonésie", "rupia indonésie|rupia indonesie|rupiah|rp|idr") + .put("Roupie", "roupie") + .put("Couronne danoise", "couronne danoise|dkk|couronnes danoise|couronne danemark|couronnes danemark") + .put("Couronne norvégienne", "couronne norvégienne|couronne norvegienne|couronnes norvégienne|couronnes norvegienne|nok") + .put("Couronne féroïenne", "couronne féroïenne|couronne feroienne") + .put("Couronne suédoise", "couronne suédoise|couronne suéde|sek|couronnes suédoise|couronne suedoise") + .put("Couronne", "couronne|couronnes") + .put("Øre", "Øre|oyra|eyrir") + .put("Franc CFA de l'Afrique de l'Ouest", "franc cfa de l''afrique de l''ouest|franc cfa ouest africain|franc cfa|francs cfa|fcfa|frs cfa|cfa francs|xof") + .put("Franc CFA d'Afrique centrale", "franc cfa d''afrique centrale|franc cfa centrale|frs cfa centrale|xaf") + .put("Franc comorien", "franc comorien|kmf") + .put("Franc congolais", "franc congolais|cdf") + .put("Franc burundais", "franc burundais|bif") + .put("Franc djiboutienne", "franc djiboutienne|djf") + .put("Franc CFP", "franc cfp|xpf") + .put("Franc guinéen", "franc guinéen|gnf") + .put("Franc Suisse", "franc suisse|chf|sfr.|francs suisses") + .put("Franc rwandais", "franc rwandais|rwf|rw|r₣|frw") + .put("Franc belge", "franc belge|bi.|b.fr.|bef") + .put("Rappen", "rappen|-rappen") + .put("Franc", "francs|franc|fr.|fs") + .put("Centimes", "centimes|centime|santim") + .put("Rouble russe", "rouble russe|rub|₽|₽ russe|roubles russe|roubles russes|₽ russes") + .put("Nouveau rouble biélorusse", "nouveau rouble biélorusse|byn|nouveau roubles biélorusse|nouveau rouble bielorusse|nouveau roubles biélorusse") + .put("Rouble transnistriens", "rouble transnistriens|prb") + .put("Rouble biélorusses", "rouble biélorusses|roubles biélorusses|rouble bielorusses|roubles bielorusses") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Rouble", "roubles|rouble|br") + .put("Dinar algérien", "dinar algérien|د.ج|dzd|dinars algérien|dinar algerien|dinars algerien") + .put("Dinar de bahreïn", "dinar de bahreïn|bhd|.د.ب|dinar de bahrein") + .put("Santeem", "santeem|santeems") + .put("Dinar iraquien", "dinar iraquien|dinars iraquien|iqd|ع.د|dinar iraquienne|dinars iraquienne") + .put("Dinar jordanien", "dinar jordanien|dinars jordanien|د.ا|jod") + .put("Dinar koweïtien", "dinar koweïtien|dinar koweitien|dinars koweïtien|kwd|د.ك") + .put("Dinar libyen", "dinar libyen|dinars libyen|lyd") + .put("Dinar serbe", "dinar serbe|dinars serbe|rsd|дин.") + .put("Dinar tunisien", "dinar tunisien|dinars tunisien|tnd") + .put("Dinar yougoslave", "dinar yougoslave|dinars yougoslave|yun") + .put("Dinar", "dinars|dinar|denar|-dinars|-dinar") + .put("Fils", "fils|fulūs|-fils|-fil") + .put("Para", "para|napa") + .put("Millime", "millimes|millime") + .put("Peso argentin", "peso argentin|ars|pesos argentin|peso argentine|pesos argentine") + .put("Peso chilien", "peso chilien|pesos chilien|clp") + .put("Peso colombien", "peso colombien|pesos colombien|cop|peso colombie|pesos colombien") + .put("Peso cubains convertibles", "peso cubains convertibles|pesos cubains convertibles|cuc") + .put("Peso cubains", "peso cubaines|pesos cubaines|peso cubaine|pesos cubaines|cup") + .put("Peso dominicain", "peso dominicain|pesos dominicain|dop|peso dominicaine|pesos dominicaine") + .put("Peso philippin", "peso philippin|pesos philippin|piso|₱|php") + .put("Peso uruguayen", "peso uruguayen|pesos uruguayen|uyu") + .put("Peso", "pesos|Peso") + .put("Centavo", "centavos|Centavo") + .put("Livre britannique", "livre britannique|livres britannique|gbp|£ britannique") + .put("Livre guernesey", "livre guernesey|£ guernesey|ggp") + .put("Livre ascension", "livre ascension|livres ascension|£ ascension") + .put("Livre sainte-hélène", "livre de sainte-hélène|livre sainte-hélène|livre sainte-helene|livre de sainte hélène|shp") + .put("Livre égyptienne", "livre égyptienne|livre egyptienne|egp|ج.م") + .put("Livre des îles falkland", "livre des îles falkland|livre des iles falkland|fkp|£ iles falkland") + .put("Livre gibraltar", "livre gibraltar|livre de gibraltar|£ gibraltar|gip") + .put("Livre manx", "imp|livre manx|£ manx") + .put("Livre jersey", "livre de jersey|livre jersey|jep|£ jersey") + .put("Livre libanaise", "livre libanaise|£ libanaise|livres libanaise|lbp|ل.ل") + .put("Livre des îles malouines", "livre des îles malouines|livre des iles malouines|£ iles malouines") + .put("Livre sud-soudanaise", "livre sud-soudanaise|livre sud soudanaise|livre du soudan du sud|livres sud-soudanaises|livre sud soudan|livre soudan sud") + .put("Livre soudanaise", "livre soudanaise|livres soudanaise|sdg|£ soudan|ج.س.|livre soudan|livres soudan") + .put("Livre syrienne", "livre syrienne|ل.س|syp|livre syrie|livres syrie|£ syrie") + .put("Livre", "livre|livres|-livre|-livres|£") + .put("Pence", "pence") + .put("Shilling", "shilling|shillings|sh") + .put("Penny", "penny|sou") + .put("Dollar États-Unis", "dollar américain|$ américain|$ americain|usd|$usd|$ usd|dollar americain|dollar États-Unis|dollar des États-Unis|dollar États Unis|dollar etats unis|dollar etats-unis|$ etats-unis|$ États-Unis") + .put("Dollar des Caraïbes orientales", "dollar des caraïbes orientales|dollar des caraibes orientales|xcd|$ caraibes orientales|$ caraïbes orientales") + .put("Dollar Australien", "dollar australien|dollars australiens|$ australien|aud|$australien|australien $|$ australie|dollar australie") + .put("Dollar des bahamas", "dollar des bahamas|dollar bahamas|$ bahamas|bsd|bahama $|dollar bahama|$ bahamas") + .put("Dollar des bermudes", "dollar des bermudes|dollar bermude|dollar bermudes|$ bermudes|bmd") + .put("Dollar de belize", "dollar de Belize|dollar belizien|bzd|$ belize") + .put("Dollar îles Vierges britanniques", "dollar îles vierges britanniques|dollar iles vierges britanniques|$ iles vierges britanniques") + .put("Dollar de brunei", "dollar de brunei|$ brunei|bnd|dollar brunei") + .put("Sen", "sen") + .put("Dollar de Singapour", "dollar de singapour|dollar singapour|$ sinapour|sgd|$s") + .put("Dollar Canadien", "dollar canadien|dollars canadien|$ canadien|cad|$can|$c|$ c|dollar canada|dollar canadienne|$ canada|$cad|cad$") + .put("Dollar des îles Caïmans", "dollars des îles caïmanes|dollar des îles caïmanes|dollars des iles caimanes|dollar iles caimanes|kyd|$ci") + .put("Dollar néo-zélandais", "dollar néo-zélandais|dollar néo zélandais|dollar neo-zelandais|dollar neo zelandais|$nz|$ néo-zélandais|$ neo zelandais") + .put("Dollar îles cook", "dollar îles cook|dollar iles cook|$ iles cook") + .put("Dollar des fidji", "dollar des fidji|$ fidji|dollar fidji|dollar de fidji|dollars des fidji|dollars de fidji") + .put("Dollar guyanien", "dollar guyanien|dollar du guyana|dollar dre guyana|$ guayana|gyd|$gy") + .put("Dollar de Hong Kong", "dollar hong kong|dollar hongkong|dollar de hong kong|dollar de hongkong|$hk|$ hk|hkd|hk $|hk$|dollar hk|$hongkong|dollars hongkong|dollars hong kong") + .put("Dollar jamaïcain", "dollar jamaïcain|dollars jamaïcain|dollar jamaicain|dollars jamaicain|$j|$ jamaïque|dollar jamaïque|jmd") + .put("Dollar libérien", "dollar libérien|dollars libérien|dollar liberien|dollars liberien|lrd|$ libérien|$ liberia|$ liberien") + .put("Dollar namibien", "dollar namibien|dollars namibien|$ namibien|nad|$n|dollar namibie|dollars namibie|$ namibie") + .put("Dollar des îles Salomon", "dollar des îles Salomon|dollar des iles salomon|$si|sbd|$ iles salomon|$ îles salomon") + .put("Dollar du suriname", "dollar du suriname|srd|$ du suriname|$ suriname|dollar suriname|dollars suriname|dollars du suriname") + .put("Nouveau dollar de Taïwan", "nouveau dollar de taïwan|nouveau dollar de taiwan|twd|ntd|$nt") + .put("Dollar trinidadien", "dollar trinidadien|dollars trinidadien|ttd|$ trinidadien") + .put("Dollar", "dollar|$|dollars") + .put("Yuan Chinois", "yuan|yuans|yuan chinois|renminbi|cny|rmb|¥") + .put("Fen", "fen") + .put("Jiao", "jiao") + .put("Mark Finlandais", "marks finlandais|mark finlandais|fim|mark") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("Dollar États-Unis", "$us|usd|us$") + .put("Dollar des Caraïbes orientales", "xcd|$ec") + .put("Dollar Australien", "a$|$a|aud") + .put("Dollar des bahamas", "bsd|b$") + .put("Dollar barbadien", "bbd|bds$") + .put("Dollar de belize", "bz$|bzd") + .put("Dollar des bermudes", "bd$|bmd") + .put("Dollar de brunei", "brunei $|bnd") + .put("Dollar de Singapour", "s$|sgd") + .put("Dollar Canadien", "cad|$ ca|$ca|$ c") + .put("Dollar des îles Caïmans", "ci$|kyd") + .put("Dollar néo-zélandais", "nz$|nzd") + .put("Dollar de Fidji", "$fj|fjd") + .put("Dollar guyanien", "g$|gyd") + .put("Dollar de Hong Kong", "hkd|hk$") + .put("Dollar jamaïcain", "j$|jmd") + .put("Dollar libérien", "lrd|l$") + .put("Dollar namibien", "nad|n$") + .put("Dollar des îles Salomon", "$ si|$si|sbd") + .put("Nouveau dollar de Taïwan", "nt$|twd") + .put("Réal brésilien", "r$|brl|reais") + .put("Guaraní paraguayen", "₲|gs.|pyg") + .put("Dollar trinidadien", "ttd|titis") + .put("Yuan Chinois", "cny|rmb|¥|元") + .put("Yen Japonais", "¥|jpy") + .put("Euro", "€|eur") + .put("Livre", "£") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kina", "lari", "taka", "tala", "vatu", "yuan", "bob", "btn", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "mga", "mop", "nad", "omr", "sar", "sbd", "scr", "sdg", "sek", "sos", "std", "try", "yer"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|kbit|kbits") + .put("Megabit", "megabit|megabits|Mb|Mbit|mégabit|mégabits") + .put("Gigabit", "gigabit|gigabits|Gb|Gbit") + .put("Terabit", "terabit|terabits|Tb|Tbit|térabit|térabits") + .put("Petabit", "petabit|petabits|Pb|Pbit|pétabit|pétabits") + .put("octet", "octet|octets|-octet") + .put("Kilooctet", "kilo-octet|kilo-octets|kilooctet|kilooctets|ko|kio|kB|KiB|kilobyte|kilobytes") + .put("Mégaoctet", "mégaoctet|mégaoctets|méga-octet|méga-octets|Mo|Mio|MB|mégabyte|mégabytes") + .put("Gigaoctet", "gigaoctet|gigaoctets|Go|Gio|GB|GiB|gigabyte|gigabytes") + .put("Téraoctet", "téraoctet|téraoctets|To|Tio|TB|TiB|térabyte|térabytes") + .put("Pétaoctet", "pétaoctet|pétaoctets|Po|Pio|PB|PiB|pétabyte|pétabytes") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("mi", "barils", "grain", "pierre", "fps", "pts"); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilomètres", "km|kilomètres|kilomètre|kilometres|kilometre|-km") + .put("Hectomètre", "hm|hectomètre|hectomètres|hectometre|hectometres|-hm") + .put("Décamètre", "dam|décamètre|décamètres|decametre|decametres|-dm") + .put("Mètres", "m|mètres|mètre|metres|metre|m.|-m") + .put("Décimètres", "dm|décimètres|décimètre|decimetres|decimetre") + .put("Centimètres", "cm|centimètres|centimètre|centimetres|centimetre") + .put("Millimètres", "mm|millimètres|millimètre|millimetre|millimetres") + .put("Micromètres", "µm|um|micromètres|micromètre|micrometres|micrometre") + .put("Nanomètres", "nm|nanometre|nanometres|nanomètres|nanomètre") + .put("Picomètres", "pm|picomètre|picomètres|picometres|picometre") + .put("Mile", "mi|mile|miles") + .put("Pied", "pied|pieds") + .put("Yard", "yards|yard|yd") + .put("Pouce", "pouce|pouces") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "m.", "yard", "yards", "pm", "pouce", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Mètre par seconde", "m/s|metres/seconde|metres par seconde|metre par seconde|metres par secondes|mètre par seconde|mètres par seconde|mètres par secondes") + .put("Kilomètre par heure", "km/h|kilomètre par heure|kilomètres par heure|kilomètres par heures|kilometres par heure|kilometre par heure") + .put("Kilomètre par minute", "km/m|kilomètre par minute|kilomètres par minute|kilomètres par minutes|kilometre par minute|kilometre par minutes") + .put("Kilomètre par seconde", "km/s|km à la seconde|km a la seconde|kilomètre par seconde|kilomètres par seconde|kilometre par seconde|kilometres par seconde") + .put("Miles par heure", "mph|miles par heure|miles à l'heure|miles a l'heure|miles un heure") + .put("Noeuds", "noeud|noeuds|nuds") + .put("Pied par seconde", "ft/s|pied par seconde|pieds par seconde|pied/s|pieds/s") + .put("Pied par minute", "pieds/minute|pied/minute|ft/minute|ft/min|pied/min") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("F", "°f|° f|degres f|degrés f|deg f|degrés fahrenheit|degres fahrenheit|fahrenheit|deg fahrenheit|degs fahrenheit") + .put("R", "rankine|°r|° r") + .put("C", "°c|° c|degres c|degrés c|deg c|degrés celsius|degres celsius|celsius|deg celsius|degs celsius|centigrade|deg centigrade|degs centigrade|degrés centigrade|degres centigrade|degré centigrade|degre centigrade") + .put("Degré", "degrés|degres|deg.|°|degré|degre|deg|degs") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Mètre cube", "m3|m^3|m³|mètre cube|mètres cube|metre cube|metres cube") + .put("Centimètre cube", "cm3|cm^3|cm³|centimètre cube|centimètres cube|centimetre cube|centimetres cube") + .put("Millimètre cube", "mm3|mm^3|mm³|millimètre cube|millimètres cube|millimetre cube|millimetres cube") + .put("Kilomètre cube", "km3|km^3|km³|kilomètre cube|kilomètres cube|kilometre cube|kilometres cube") + .put("Pieds cube", "pieds cubes|pieds cube|pied cube|pied cubes") + .put("Litre", "litre|litres|lts|l") + .put("Millilitre", "ml|millilitre|millilitres") + .put("Gallon", "gallon|gallons") + .put("Pintes", "pintes") + .put("Onces", "onces|once|oz") + .put("Décilitre", "dl|décilitre|decilitre|décilitres|decilitres") + .put("Centilitre", "cl|centilitres|centilitre") + .put("Onces liquides", "onces liquides|once liquide|once liquides") + .put("Baril", "baril|barils|bbl") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("oz", "l"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogramme", "kg|kilogramme|kilogrammes|kilo|kilos") + .put("Gram", "g|gramme|grammes") + .put("Milligramme", "mg|milligramme|milligrammes") + .put("Tonne métrique", "tonne métrique|tonnes métrique|tonnes métriques|tonne metrique|tonnes metrique") + .put("Tonne", "tonne|tonnes|-tonnes|-tonne") + .put("Livre", "livre|livres") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("\\bcent\\b", "\\bpour\\s+cent\\b") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java new file mode 100644 index 000000000..f8d694c55 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/GermanNumericWithUnit.java @@ -0,0 +1,451 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class GermanNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "jahr alt|jahre alt|jahren|jahre|lebensjahr") + .put("Month", "monat alt|monate alt|monaten|monate") + .put("Week", "woche alt|wochen alt|wochen|woche") + .put("Day", "tag alt|tage alt|tagen|tage") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("jahren", "jahre", "monaten", "monate", "wochen", "woche", "tagen", "tage"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Square kilometer", "qkm|quadratkilometer|km^2|km²") + .put("Square hectometer", "qhm|quadrathektometer|hm^2|hm²|hektar") + .put("Square decameter", "quadratdekameter|dam^2|dam²") + .put("Square meter", "qm|quadratmeter|m^2|m²") + .put("Square decimeter", "qdm|quadratdezimeter|dm^2|dm²") + .put("Square centimeter", "qcm|quadratzentimeter|cm^2|cm²") + .put("Square millimeter", "qmm|quadratmillimeter|mm^2|mm²") + .put("Square inch", "sqin|quadratzoll|in^2|in²") + .put("Square foot", "sqft|quadratfuß|fuß^2|fuß²|ft2|ft^2|ft²") + .put("Square mile", "sqmi|quadratmeile|mi^2|mi²") + .put("Square yard", "sqyd|quadratyard|yd^2|yd²") + .put("Acre", "-acre|acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Abkhazian apsar", "abkhazian apsar|apsars") + .put("Afghan afghani", "afghanischer afghani|afghanische afghani|afghanischen afghani|؋|afn|afghani") + .put("Pul", "pul") + .put("Euro", "euro|€|eur") + .put("Cent", "cent|-cent") + .put("Albanian lek", "albaninischer Lek|albanische Lek|albanischen Lek") + .put("Qindarkë", "qindarkë|qindarkës|qindarke|qindarkes") + .put("Angolan kwanza", "angolanischer kwanza|angolanische kwanza|angolanischen kwanza|kz|aoa|kwanza|kwanzas") + .put("Armenian dram", "armeninischer dram|armeninische dram|armeninischen dram") + .put("Aruban florin", "Aruba-Florin|ƒ|awg") + .put("Bangladeshi taka", "bangladesischer taka|bengalischer taka|bangladesische taka|bengalische taka|bangladesischen taka|bengalischen taka|৳|bdt|taka") + .put("Paisa", "poisha|paisa") + .put("Bhutanese ngultrum", "bhutanischer ngultrum|bhutanische ngultrum|bhutanischen ngultrum|nu.|btn") + .put("Chetrum", "chetrum") + .put("Bolivian boliviano", "bolivianischer boliviano|bolivianische boliviano|bolivianischen boliviano|bob|bs.|boliviano") + .put("Bosnia and Herzegovina convertible mark", "bosnischer konvertible mark|bosnisch-herzegowinischer konvertible mark|bosnische konvertible mark|bosnisch-herzegowinische konvertible mark|bosnischen konvertible mark|bosnisch-herzegowinischen konvertible mark|konvertible mark|bam") + .put("Fening", "Fening") + .put("Botswana pula", "botswanischer pula|botswanische pula|botswanischen pula|bwp|pula") + .put("Thebe", "thebe") + .put("Brazilian real", "brazilianischer real|brazilianische real|brazilianischen real|r$|brl|real") + .put("Bulgarian lev", "bulgarischer lew|bulgarische lew|bulgarischen lew|bgn|лв|lew") + .put("Stotinka", "stotinki|stotinka") + .put("Cambodian riel", "kambodschanischer riel|kambodschanische riel|kambodschanischen riel|khr|៛|riel") + .put("Cape Verdean escudo", "kap-verde-escudo|cve") + .put("Costa Rican colón", "costa-rica-colón|costa-rica-colon|crc|₡") + .put("Salvadoran colón", "svc|el-salvador-colón|el-salvador-colon") + .put("Céntimo", "céntimo") + .put("Croatian kuna", "kroatischer kuna|kroatische kuna|kroatischen kuna|kn|hrk|kuna") + .put("Lipa", "lipa") + .put("Czech koruna", "tschechische krone|tschechischen kronen|tschechischer kronen|czk|kč") + .put("Haléř", "haléř") + .put("Eritrean nakfa", "eritreischer nakfa|eritreische nakfa|eritreischen nakfa|nfk|ern|nakfa") + .put("Ethiopian birr", "äthiopischer birr|äthiopische birr|äthiopischen birr|etb") + .put("Gambian dalasi", "gambischer dalasi|gambische dalasi|gambischen dalasi|gmd") + .put("Butut", "bututs|butut") + .put("Georgian lari", "georgischer lari|georgische lari|georgischen lari|lari|gel|₾") + .put("Tetri", "tetri") + .put("Ghanaian cedi", "ghanaischer cedi|ghanaische cedi|ghanaischen cedi|Ghana cedi|ghs|₵|gh₵") + .put("Pesewa", "pesewas|pesewa") + .put("Guatemalan quetzal", "guatemaltekischer quetzal|guatemaltekische quetzal|guatemaltekischen quetzal|gtq|quetzal") + .put("Haitian gourde", "haitianischer gourde|haitianische gourde|haitianischen gourde|htg") + .put("Honduran lempira", "honduranischer lempira|honduranische lempira|honduranischen lempira|hnl") + .put("Hungarian forint", "ungarischer forint|ungarische forint|ungarischen forint|huf|ft|forint") + .put("Fillér", "fillér") + .put("Iranian rial", "iranischer rial|iranische rial|iranischen rial|irr") + .put("Yemeni rial", "jemen-rial|yer") + .put("Israeli new shekel", "₪|ils|agora") + .put("Lithuanian litas", "ltl|litauischer litas|litauische litas|litauischen litas") + .put("Japanese yen", "japaneser yen|japanese yen|japanesen yen|jpy|yen|¥") + .put("Kazakhstani tenge", "kasachischer tenge|kasachische tenge|kasachischen tenge|kzt") + .put("Kenyan shilling", "kenia-schilling|kes") + .put("North Korean won", "nordkoreanischer won|nordkoreanische won|nordkoreanischen won|kpw") + .put("South Korean won", "südkoreanischer won|südkoreanische won|südkoreanischen won|krw") + .put("Korean won", "koreanischer won|koreanische won|koreanischen won|₩") + .put("Kyrgyzstani som", "kirgisischer som|kirgisische som|kirgisischen som|kgs") + .put("Uzbekitan som", "usbekischer som|usbekische som|usbekischen som|usbekischer sum|usbekische sum|usbekischen sum|usbekischer so'm|usbekische so'm|usbekischen so'm|usbekischer soum|usbekische soum|usbekischen soum|uzs") + .put("Lao kip", "laotischer kip|laotische kip|laotischen kip|lak|₭n|₭") + .put("Att", "att") + .put("Lesotho loti", "lesothischer loti|lesothische loti|lesothischen loti|lsl|loti") + .put("Sente", "sente|lisente") + .put("South African rand", "südafrikanischer rand|südafrikanische rand|südafrikanischen rand|zar") + .put("Macanese pataca", "macao-pataca|mop$|mop") + .put("Avo", "avos|avo") + .put("Macedonian denar", "mazedonischer denar|mazedonische denar|mazedonischen denar|mkd|ден") + .put("Deni", "deni") + .put("Malagasy ariary", "madagassischer ariary|madagassische ariary|madagassischen ariary|ariary|mga") + .put("Iraimbilanja", "iraimbilanja") + .put("Malawian kwacha", "malawi-kwacha|mk|mwk") + .put("Tambala", "tambala") + .put("Malaysian ringgit", "malaysischer ringgit|malaysische ringgit|malaysischen ringgit|rm|myr") + .put("Mauritanian ouguiya", "mauretanischer ouguiya|mauretanische ouguiya|mauretanischen ouguiya|mro") + .put("Khoums", "khoums") + .put("Mongolian tögrög", "mongolischer tögrög|mongolische tögrög|mongolischen tögrög|mongolischer tugrik|mongolische tugrik|mongolischen tugrik|mnt|₮") + .put("Mozambican metical", "mosambik-metical|mosambik metical|mt|mzn") + .put("Burmese kyat", "myanmar-kyat|myanmar kyat|ks|mmk") + .put("Pya", "pya") + .put("Nicaraguan córdoba", "nicaraguanischer córdoba oro|nicaraguanische córdoba oro|nicaraguanischen córdoba oro|nicaraguanischer córdoba|nicaraguanische córdoba|nicaraguanischen córdoba|nio|córdoba|córdoba oro") + .put("Nigerian naira", "nigerianischer naira|nigerianische naira|nigerianischen naira|naira|ngn|₦|nigeria naira") + .put("Kobo", "kobo") + .put("Turkish lira", "türkischer lira|türkische lira|türkischen lira|tuerkischer lira|tuerkische lira|tuerkischen lira|try|tl") + .put("Kuruş", "kuruş") + .put("Omani rial", "omanischer rial|omanische rial|omanischen rial|omr|ر.ع.") + .put("Panamanian balboa", "panamaischer balboa|panamaische balboa|panamaischen balboa|b/.|pab") + .put("Centesimo", "centesimo") + .put("Papua New Guinean kina", "papua-neuguinea-kina|kina|pgk") + .put("Toea", "toea") + .put("Paraguayan guaraní", "paraguayischer guaraní|paraguayische guaraní|paraguayischen guaraní|guaraní|₲|pyg") + .put("Peruvian sol", "peruanischer sol|peruanische sol|peruanischen sol|soles|sol") + .put("Polish złoty", "polnischer złoty|polnische złoty|polnischen złoty|polnischer zloty|polnische zloty|polnischen zloty|zł|pln|złoty|zloty") + .put("Grosz", "groszy|grosz|grosze") + .put("Qatari riyal", "katar-riyal|katar riyal|qatari riyal|qar") + .put("Saudi riyal", "saudi-riyal|sar") + .put("Riyal", "riyal|﷼") + .put("Dirham", "dirham|dirhem|dirhm") + .put("Halala", "halalas|halala") + .put("Samoan tālā", "samoanischer tala|samoanische tala|samoanischen tala|samoanischer tālā|samoanische tālā|samoanischen tālā|tālā|tala|ws$|samoa|wst|samoa-tālā|samoa-tala") + .put("Sene", "sene") + .put("São Tomé and Príncipe dobra", "são-toméischer dobra|são-toméische dobra|são-toméischen dobra|dobra|std") + .put("Sierra Leonean leone", "sierra-leonischer leone|sierra-leonische leone|sierra-leonischen leone|sll|leone|le") + .put("Peseta", "pesetas|peseta") + .put("Netherlands guilder", "florin|antillen-gulden|niederländische-antillen-gulden|antillen gulden|ang|niederländischer gulden|niederländische gulden|niederländischen gulden|gulden|fl") + .put("Swazi lilangeni", "swazi-lilangeni|swazi lilangeni|lilangeni|szl|swazi-emalangeni|swazi emalangeni") + .put("Tajikistani somoni", "tadschikischer somoni|tadschikische somoni|tadschikischen somoni|tadschikistan-somoni|tadschikistan somoni|tajikischer somoni|tajikische somoni|tajikischen somoni|tajikistan-somoni|tajikistan somoni|tjs") + .put("Diram", "dirams|diram") + .put("Thai baht", "thailändischer baht|thailändische baht|thailändischen baht|thailaendischer baht|thailaendische baht|thailaendischen baht|thai baht|thai-baht|฿|thb") + .put("Satang", "satang|satangs") + .put("Tongan paʻanga", "tongaischer paʻanga|tongaische paʻanga|tongaischen paʻanga|paʻanga|tonga paʻanga|tongaischer pa'anga|tongaische pa'anga|tongaischen pa'anga|pa'anga|tonga pa'anga") + .put("Seniti", "seniti") + .put("Ukrainian hryvnia", "ukrainischer hrywnja|ukrainische hrywnja|ukrainischen hrywnja|hrywnja|uah|₴") + .put("Vanuatu vatu", "vanuatu-vatu|vanuatu vatu|vatu|vuv") + .put("Venezuelan bolívar", "venezolanischer bolívar|venezolanische bolívar|venezolanischen bolívar|bs.f.|vef") + .put("Vietnamese dong", "vietnamesischer đồng|vietnamesische đồng|vietnamesischen đồng|vietnamesischer dong|vietnamesische dong|vietnamesischen dong|vnd|đồng") + .put("Zambian kwacha", "sambischer kwacha|sambische kwacha|sambischen kwacha|zk|zmw") + .put("Moroccan dirham", "marokkanischer dirham|marokkanische dirham|marokkanischen dirham|mad|د.م.") + .put("United Arab Emirates dirham", "vae dirham|vae-dirham|dirham der vereinigten arabischen emirate|د.إ|aed") + .put("Azerbaijani manat", "aserbaidschan-manat|azn") + .put("Turkmenistan manat", "turkmenistan-manat|tmt") + .put("Manat", "manat") + .put("Qəpik", "qəpik") + .put("Somali shilling", "somalia-schilling|sh.so.|sos") + .put("Somaliland shilling", "somaliland-schilling") + .put("Tanzanian shilling", "tansania-schilling|tsh|tzs") + .put("Ugandan shilling", "uganda-schilling|ugx") + .put("Romanian leu", "rumänischer leu|rumänische leu|rumänischen leu|rumaenischer leu|rumaenische leu|rumaenischen leu|lei|ron") + .put("Moldovan leu", "moldauischer leu|moldauische leu|moldauischen leu|mdl|moldau leu") + .put("Leu", "leu") + .put("Ban", "bani|ban") + .put("Nepalese rupee", "nepalesischer rupie|nepalesische rupie|nepalesischen rupie|nepalesische rupien|nepalesischer rupien|nepalesischen rupien|npr") + .put("Pakistani rupee", "pakistanischer rupie|pakistanische rupie|pakistanischen rupie|pakistanischer rupien|pakistanische rupien|pakistanischen rupien|pkr") + .put("Indian rupee", "indischer rupie|indische rupie|indischen rupie|indischer rupien|indische rupien|indischen rupien|inr|₹") + .put("Seychellois rupee", "seychellen-rupie|seychellen-rupien|scr|sr|sre") + .put("Mauritian rupee", "mauritius-rupie|mauritius-rupien|mur") + .put("Maldivian rufiyaa", "maledivischer rufiyaa|maledivische rufiyaa|maledivischen rufiyaa|mvr|.ރ") + .put("Sri Lankan rupee", "sri-lanka-rupie|sri-lanka-rupien|lkr|රු|ரூ") + .put("Indonesian rupiah", "indonesischer rupiah|indonesische rupiah|indonesischen rupiah|rupiah|perak|rp|idr") + .put("Rupee", "rupie|rs") + .put("Danish krone", "dänische krone|dänischen krone|dänischer kronen|dänische kronen|dänischen kronen|daenische krone|daenischen krone|daenischer kronen|daenische kronen|daenischen kronen|dkk") + .put("Norwegian krone", "norwegische krone|norwegischen krone|norwegischer kronen|norwegische kronen|norwegischen kronen|nok") + .put("Faroese króna", "färöische króna|färöische krone|färöischen krone|färöischer kronen|färöische kronen|färöischen kronen") + .put("Icelandic króna", "isländische krone|isländischen krone|isländischer kronen|isländische kronen|isländischen kronen|isk") + .put("Swedish krona", "schwedische krone|schwedischen krone|schwedischer kronen|schwedische kronen|schwedischen kronen|sek") + .put("Krone", "krone|kronen|kr|-kr") + .put("Øre", "Øre|oyra|eyrir") + .put("West African CFA franc", "west african cfa franc|xof|westafrikanische cfa franc|westafrikanische-cfa-franc") + .put("Central African CFA franc", "central african cfa franc|xaf|zentralafrikanische cfa franc|zentralafrikanische-cfa-franc") + .put("Comorian franc", "komoren-franc|kmf") + .put("Congolese franc", "kongo-franc|cdf") + .put("Burundian franc", "burundi-franc|bif") + .put("Djiboutian franc", "dschibuti-franc|djf") + .put("CFP franc", "cfp-franc|xpf") + .put("Guinean franc", "franc guinéen|franc-guinéen|gnf") + .put("Swiss franc", "schweizer franken|schweizer-franken|chf|sfr.") + .put("Rwandan franc", "ruanda-franc|rwf|rf|r₣|frw") + .put("Belgian franc", "belgischer franken|belgische franken|belgischen franken|bi.|b.fr.|bef") + .put("Rappen", "rappen|-rappen") + .put("Franc", "franc|französischer franc|französische franc|französischen franc|französischer franken|französische franken|französischen franken|franken|fr.|fs") + .put("Centime", "centimes|centime|santim") + .put("Russian ruble", "russischer rubel|russische rubel|russischen rubel|₽|rub") + .put("New Belarusian ruble", "neuer weißrussischer rubel|neue weißrussische rubel|neuen weißrussischen rubel|neuem weißrussischen rubel") + .put("Old Belarusian ruble", "alter weißrussischer rubel|alte weißrussische rubel|alten weißrussischen rubel|altem weißrussischen rubel") + .put("Transnistrian ruble", "transnistrischer rubel|transnistrische rubel|transnistrischen rubel|prb|р.") + .put("Belarusian ruble", "weißrussischer rubel|weißrussische rubel|weißrussischen rubel") + .put("Kopek", "kopek|kopeks") + .put("Kapyeyka", "kapyeyka") + .put("Ruble", "rubel|br") + .put("Algerian dinar", "algerischer dinar|algerische dinar|algerischen dinar|د.ج|dzd") + .put("Bahraini dinar", "bahrain-dinar|bhd|.د.ب") + .put("Santeem", "santeem|santeeme") + .put("Iraqi dinar", "irakischer dinar|irakische dinar|irakischen dinar|iqd|ع.د") + .put("Jordanian dinar", "jordanischer dinar|jordanische dinar|jordanischen dinar|د.ا|jod") + .put("Kuwaiti dinar", "kuwait-dinar|kwd|د.ك") + .put("Libyan dinar", "libyscher dinar|libysche dinar|libyschen dinar|lyd") + .put("Serbian dinar", "serbischer dinar|serbische dinar|serbischen dinar|din.|rsd|дин.") + .put("Tunisian dinar", "tunesischer dinar|tunesische dinar|tunesischen dinar|tnd") + .put("Yugoslav dinar", "jugoslawischer dinar|jugoslawische dinar|jugoslawischen dinar|yun") + .put("Dinar", "dinar|denar") + .put("Fils", "fils|fulūs") + .put("Para", "para|napa") + .put("Millime", "millime") + .put("Argentine peso", "argentinischer peso|argentinische peso|argentinischen peso|ars") + .put("Chilean peso", "chilenischer peso|chilenische peso|chilenischen peso|clp") + .put("Colombian peso", "kolumbianischer peso|kolumbianische peso|kolumbianischen peso|cop") + .put("Cuban convertible peso", "kubanischer peso convertible|kubanische peso convertible|kubanischen peso convertible|peso convertible|cuc") + .put("Cuban peso", "kubanischer peso|kubanische peso|kubanischen peso|cup") + .put("Dominican peso", "dominican pesos|dominican peso|dop|dominica pesos|dominica peso") + .put("Mexican peso", "mexikanischer peso|mexikanische peso|mexikanischen peso|mxn") + .put("Philippine peso", "piso|philippinischer peso|philippinische peso|philippinischen peso|₱|php") + .put("Uruguayan peso", "uruguayischer peso|uruguayische peso|uruguayischen peso|uyu") + .put("Peso", "peso") + .put("Centavo", "centavos|centavo") + .put("Alderney pound", "alderney pfund|alderney £") + .put("British pound", "britischer pfund|britische pfund|britischen pfund|british £|gbp|pfund sterling") + .put("Guernsey pound", "guernsey-pfund|guernsey £|ggp") + .put("Ascension pound", "ascension-pfund|ascension pound|ascension £") + .put("Saint Helena pound", "st.-helena-pfund|saint helena £|shp") + .put("Egyptian pound", "ägyptisches pfund|ägyptische pfund|ägyptischen pfund|ägyptisches £|egp|ج.م") + .put("Falkland Islands pound", "falkland-pfund|falkland £|fkp|falkland-£") + .put("Gibraltar pound", "gibraltar-pfund|gibraltar £|gibraltar-£|gip") + .put("Manx pound", "isle-of-man-pfund|isle-of-man-£|imp") + .put("Jersey pound", "jersey-pfund|jersey-£|jep") + .put("Lebanese pound", "libanesisches pfund|libanesische pfund|libanesischen pfund|libanesisches-£|lbp|ل.ل") + .put("South Georgia and the South Sandwich Islands pound", "süd-georgien & die südlichen sandwichinseln pfund|süd-georgien & die südlichen sandwichinseln £") + .put("South Sudanese pound", "südsudanesisches pfund|südsudanesische pfund|südsudanesischen pfund|südsudanesisches £|ssp|südsudanesische £") + .put("Sudanese pound", "sudanesisches pfund|sudanesische pfund|sudanesischen pfund|sudanesisches £|ج.س.|sdg|sudanesische £") + .put("Syrian pound", "syrisches pfund|syrische pfund|syrischen pfund|syrisches £|ل.س|syp|syrische £") + .put("Tristan da Cunha pound", "tristan-da-cunha-pfund|tristan-da-cunha-£") + .put("Pound", "pfund|£") + .put("Pence", "pence") + .put("Shilling", "shillings|shilling|shilingi|sh") + .put("Penny", "pennies|penny") + .put("United States dollar", "us-dollar|us$|usd|amerikanischer dollar|amerikanische dollar|amerikanischen dollar") + .put("East Caribbean dollar", "ostkaribischer dollar|ostkaribische dollar|ostkaribischen dollar|ostkaribische $|xcd") + .put("Australian dollar", "australischer dollar|australische dollar|australischen dollar|australische $|aud") + .put("Bahamian dollar", "bahama-dollar|bahama-$|bsd") + .put("Barbadian dollar", "barbados-dollar|barbados-$|bbd") + .put("Belize dollar", "belize-dollar|belize-$|bzd") + .put("Bermudian dollar", "bermuda-dollar|bermuda-$|bmd") + .put("British Virgin Islands dollar", "british virgin islands dollars|british virgin islands dollar|british virgin islands $|bvi$|virgin islands dollars|virgin islands dolalr|virgin islands $|virgin island dollars|virgin island dollar|virgin island $") + .put("Brunei dollar", "brunei-dollar|brunei $|bnd") + .put("Sen", "sen") + .put("Singapore dollar", "singapur-dollar|singapur-$|s$|sgd") + .put("Canadian dollar", "kanadischer dollar|kanadische dollar|kanadischen dollar|cad|can$|c$") + .put("Cayman Islands dollar", "kaiman-dollar|kaiman-$|kyd|ci$") + .put("New Zealand dollar", "neuseeland-dollar|neuseeland-$|nz$|nzd|kiwi") + .put("Cook Islands dollar", "cookinseln-dollar|cookinseln-$") + .put("Fijian dollar", "fidschi-dollar|fidschi-$|fjd") + .put("Guyanese dollar", "guyana-dollar|gyd|gy$") + .put("Hong Kong dollar", "hongkong-dollar|hong kong $|hk$|hkd|hk dollars|hk dollar|hk $|hongkong$") + .put("Jamaican dollar", "jamaika-dollar|jamaika-$|j$") + .put("Kiribati dollar", "kiribati-dollar|kiribati-$") + .put("Liberian dollar", "liberianischer dollar|liberianische dollar|liberianischen dollar|liberianische $|lrd") + .put("Micronesian dollar", "mikronesischer dollar|mikronesische dollar|mikronesischen dollar|mikronesische $") + .put("Namibian dollar", "namibia-dollar|namibia-$|nad|n$") + .put("Nauruan dollar", "nauru-dollar|nauru-$") + .put("Niue dollar", "niue-dollar|niue-$") + .put("Palauan dollar", "palau-dollar|palau-$") + .put("Pitcairn Islands dollar", "pitcairninseln-dollar|pitcairninseln-$") + .put("Solomon Islands dollar", "salomonen-dollar|salomonen-$|si$|sbd") + .put("Surinamese dollar", "suriname-dollar|suriname-$|srd") + .put("New Taiwan dollar", "neuer taiwan-dollar|neue taiwan-dollar|neuen taiwan-dollar|nt$|twd|ntd") + .put("Trinidad and Tobago dollar", "trinidad-und-tobago-dollar|trinidad-und-tobago-$|ttd") + .put("Tuvaluan dollar", "tuvaluischer dollar|tuvaluische dollar|tuvaluischen dollar|tuvaluische $") + .put("Dollar", "dollar|$") + .put("Chinese yuan", "yuan|chinesischer yuan|chinesische yuan|chinesischen yuan|renminbi|cny|rmb|¥") + .put("Fen", "fen") + .put("Jiao", "jiao") + .put("Finnish markka", "suomen markka|finnish markka|finsk mark|fim|markkaa|markka|finnische mark|finnischen mark") + .put("Penni", "penniä|penni") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "united states $|us$|us $|u.s. $|u.s $") + .put("East Caribbean dollar", "east caribbean $") + .put("Australian dollar", "australian $|australia $") + .put("Bahamian dollar", "bahamian $|bahamia $") + .put("Barbadian dollar", "barbadian $|barbadin $") + .put("Belize dollar", "belize $") + .put("Bermudian dollar", "bermudian $") + .put("British Virgin Islands dollar", "british virgin islands $|bvi$|virgin islands $|virgin island $|british virgin island $") + .put("Brunei dollar", "brunei $|b$") + .put("Sen", "sen") + .put("Singapore dollar", "singapore $|s$") + .put("Canadian dollar", "canadian $|can$|c$|c $|canada $") + .put("Cayman Islands dollar", "cayman islands $|ci$|cayman island $") + .put("New Zealand dollar", "new zealand $|nz$|nz $") + .put("Cook Islands dollar", "cook islands $|cook island $") + .put("Fijian dollar", "fijian $|fiji $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hong kong $|hk$|hkd|hk $") + .put("Jamaican dollar", "jamaican $|j$|jamaica $") + .put("Kiribati dollar", "kiribati $") + .put("Liberian dollar", "liberian $|liberia $") + .put("Micronesian dollar", "micronesian $") + .put("Namibian dollar", "namibian $|nad|n$|namibia $") + .put("Nauruan dollar", "nauruan $") + .put("Niue dollar", "niue $") + .put("Palauan dollar", "palauan $") + .put("Pitcairn Islands dollar", "pitcairn islands $|pitcairn island $") + .put("Solomon Islands dollar", "solomon islands $|si$|si $|solomon island $") + .put("Surinamese dollar", "surinamese $|surinam $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Trinidad and Tobago dollar", "trinidad and tobago $|trinidad $|trinidadian $") + .put("Tuvaluan dollar", "tuvaluan $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .put("Turkish lira", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("din.", "kiwi", "kina", "kobo", "lari", "lipa", "napa", "para", "sfr.", "taka", "tala", "toea", "vatu", "yuan", "ang", "ban", "bob", "btn", "byr", "cad", "cop", "cup", "dop", "gip", "jod", "kgs", "lak", "lei", "mga", "mop", "nad", "omr", "pul", "sar", "sbd", "scr", "sdg", "sek", "sen", "sol", "sos", "std", "try", "yer", "yen"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("Bit", "-bit|bit|bits") + .put("Kilobit", "kilobit|kilobits|kb|kbit") + .put("Megabit", "megabit|megabits|Mb|Mbit") + .put("Gigabit", "gigabit|gigabits|Gb|Gbit") + .put("Terabit", "terabit|terabits|Tb|Tbit") + .put("Petabit", "petabit|petabits|Pb|Pbit") + .put("Byte", "byte|bytes") + .put("Kilobyte", "kilobyte|kB|kilobytes|kilo byte|kilo bytes|kByte") + .put("Megabyte", "megabyte|mB|megabytes|mega byte|mega bytes|MByte") + .put("Gigabyte", "gigabyte|gB|gigabytes|giga byte|giga bytes|GByte") + .put("Terabyte", "terabyte|tB|terabytes|tera byte|tera bytes|TByte") + .put("Petabyte", "petabyte|pB|petabytes|peta byte|peta bytes|PByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("barrel", "grain", "gran", "grän", "korn", "pfund", "stone", "yard", "cord", "dram", "fuß", "gill", "knoten", "peck", "cup", "fps", "pts", "in", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^))"; + + public static final String BuildSuffix = "(?=(\\s|\\W|$))"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilometer", "km|kilometer|kilometern") + .put("Hectometer", "hm|hektometer|hektometern") + .put("Decameter", "dam|dekameter|dekametern") + .put("Meter", "m|meter|metern") + .put("Decimeter", "dm|dezimeter|dezimetern") + .put("Centimeter", "cm|zentimeter|centimeter|zentimetern|centimetern") + .put("Millimeter", "mm|millimeter|millimetern") + .put("Micrometer", "μm|mikrometer|mikrometern") + .put("Nanometer", "nm|nanometer|nanometern") + .put("Picometer", "pm|pikometer|picometer|pikometern|picometern") + .put("Mile", "meile|meilen") + .put("Yard", "yard|yards") + .put("Inch", "zoll|inch|in|\"") + .put("Foot", "fuß|ft") + .put("Light year", "lichtjahr|lichtjahre|lichtjahren") + .put("Pt", "pt|pts") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("m", "yard", "yards", "pm", "pt", "pts"); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Meter per second", "meter/sekunde|m/s|meter pro sekunde|metern pro sekunde") + .put("Kilometer per hour", "km/h|kilometer/stunde|kilometer pro stunde|kilometern pro stunde") + .put("Kilometer per minute", "km/min|kilometer pro minute|kilometern pro minute") + .put("Kilometer per second", "km/s|kilometer pro sekunde|kilometern pro sekunde") + .put("Mile per hour", "mph|mi/h|meilen pro stunde|meilen/stunde|meile pro stunde") + .put("Knot", "kt|knoten|kn") + .put("Foot per second", "ft/s|fuß/sekunde|fuß pro sekunde|fps") + .put("Foot per minute", "ft/min|fuß/minute|fuß pro minute") + .put("Yard per minute", "yard pro minute|yard/minute|yard/min") + .put("Yard per second", "yard pro sekunde|yard/sekunde|yard/s") + .build(); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("F", "grad fahrenheit|°fahrenheit|°f|fahrenheit") + .put("K", "k|K|kelvin|grad kelvin|°kelvin|°k|°K") + .put("R", "rankine|°r") + .put("D", "delisle|°de") + .put("C", "grad celsius|°celsius|°c|celsius") + .put("Degree", "grad|°") + .build(); + + public static final List AmbiguousTemperatureUnitList = Arrays.asList("c", "f", "k"); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Cubic meter", "m3|kubikmeter|m³") + .put("Cubic centimeter", "kubikzentimeter|cm³") + .put("Cubic millimiter", "kubikmillimeter|mm³") + .put("Hectoliter", "hektoliter") + .put("Decaliter", "dekaliter") + .put("Liter", "l|liter") + .put("Deciliter", "dl|deziliter") + .put("Centiliter", "cl|zentiliter") + .put("Milliliter", "ml|mls|milliliter") + .put("Cubic yard", "kubikyard") + .put("Cubic inch", "kubikzoll") + .put("Cubic foot", "kubikfuß") + .put("Cubic mile", "kubikmeile") + .put("Fluid ounce", "fl oz|flüssigunze|fluessigunze") + .put("Teaspoon", "teelöffel|teeloeffel") + .put("Tablespoon", "esslöffel|essloeffel") + .put("Pint", "pinte") + .put("Volume unit", "fluid dram|fluid drachm|flüssigdrachme|gill|quart|minim|cord|peck|beck|scheffel|hogshead|oxhoft") + .build(); + + public static final List AmbiguousVolumeUnitList = Arrays.asList("l", "unze", "oz", "cup", "peck", "cord", "gill"); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Kilogram", "kg|kilogramm|kilo") + .put("Gram", "g|gramm") + .put("Milligram", "mg|milligramm") + .put("Barrel", "barrel") + .put("Gallon", "gallone|gallonen") + .put("Metric ton", "metrische tonne|metrische tonnen") + .put("Ton", "tonne|tonnen") + .put("Pound", "pfund|lb") + .put("Ounce", "unze|unzen|oz|ounces") + .put("Weight unit", "pennyweight|grain|british long ton|US short hundredweight|stone|dram") + .build(); + + public static final List AmbiguousWeightUnitList = Arrays.asList("g", "oz", "stone", "dram"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java new file mode 100644 index 000000000..0e8ffbc43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/JapaneseNumericWithUnit.java @@ -0,0 +1,546 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class JapaneseNumericWithUnit { + + public static final List AgeAmbiguousValues = Arrays.asList("歳"); + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Year", "歳") + .put("Month", "ヶ月") + .put("Week", "週間|週") + .put("Day", "日間|日齢|日大") + .build(); + + public static final String BuildPrefix = ""; + + public static final String BuildSuffix = ""; + + public static final String ConnectorToken = ""; + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Afghan afghani", "アフガニ") + .put("Pul", "プル") + .put("Euro", "ユーロ") + .put("Cent", "セント") + .put("Albanian lek", "アルバニアレク|アルバニア・レク|レク") + .put("Angolan kwanza", "アンゴラクワンザ|アンゴラ・クワンザ|クワンザ") + .put("Armenian dram", "アルメニアドラム|アルメニア・ドラム|ドラム") + .put("Aruban florin", "アルバ・フロリン|フロリン") + .put("Bangladeshi taka", "タカ|バングラデシュ・タカ") + .put("Paisa", "パイサ") + .put("Bhutanese ngultrum", "ニュルタム|ブータン・ニュルタム|ブータンニュルタム") + .put("Chetrum", "チェルタム") + .put("Bolivian boliviano", "ボリビアーノ") + .put("Bosnia and Herzegovina convertible mark", "兌換マルク") + .put("Botswana pula", "ボツワナ・プラ|ボツワナプラ|プラ") + .put("Thebe", "テベ") + .put("Brazilian real", "ブラジル・レアル|ブラジルレアル|レアル") + .put("Bulgarian lev", "ブルガリア・レフ|ブルガリアレフ|レフ") + .put("Stotinka", "ストティンカ") + .put("Cambodian riel", "カンボジア・リエル|カンボジアリエル|リエル") + .put("Cape Verdean escudo", "カーボベルデ・エスクード") + .put("Croatian kuna", "クロアチアクーナ|クロアチア・クーナ|クーナ") + .put("Lipa", "リパ") + .put("Eritrean nakfa", "エリトリア・ナクファ|エリトリアナクファ|ナクファ") + .put("Ethiopian birr", "エチオピア・ブル|エチオピアブル|ブル") + .put("Gambian dalasi", "ガンビア・ダラシ|ガンビアダラシ|ダラシ") + .put("Butut", "ブトゥツ") + .put("Georgian lari", "ジョージア・ラリ|ジョージアラリ|ラリ") + .put("Tetri", "テトリ") + .put("Ghanaian cedi", "ガーナ・セディ|ガーナセディ|セディ") + .put("Pesewa", "ペセワ") + .put("Guatemalan quetzal", "グアテマラ・ケツァル|グアテマラケツァル|ケツァル") + .put("Haitian gourde", "ハイチ・グールド|ハイチグールド|グールド") + .put("Honduran lempira", "ホンジュラス・レンピラ|ホンジュラスレンピラ|レンピラ") + .put("Hungarian forint", "ハンガリー・フォリント|ハンガリーフォリント|フォリント") + .put("Iranian rial", "イラン・リアル") + .put("Yemeni rial", "イエメン・リアル") + .put("Israeli new shekel", "₪|ils|イスラエル・新シェケル|イスラエル新シェケル") + .put("Japanese yen", "円") + .put("Sen", "銭") + .put("Kazakhstani tenge", "テンゲ|カザフスタン・テンゲ|カザフスタンテンゲ") + .put("Kenyan shilling", "ケニア・シリング") + .put("North Korean won", "北朝鮮ウォン") + .put("South Korean won", "韓国ウォン") + .put("Korean won", "₩") + .put("Kyrgyzstani som", "キルギス・ソム|ソム") + .put("Lao kip", "キップ|ラオス・キップ|ラオスキップ") + .put("Att", "att") + .put("Lesotho loti", "ロチ|レソト・ロチ|レソトロチ") + .put("South African rand", "ランド|南アフリカ・ランド|南アフリカランド") + .put("Macedonian denar", "マケドニア・デナール") + .put("Deni", "デニ") + .put("Malagasy ariary", "アリアリ|マダガスカル・アリアリ|マダガスカルアリアリ") + .put("Iraimbilanja", "イライムビランジャ") + .put("Malawian kwacha", "マラウイ・クワチャ") + .put("Tambala", "タンバラ") + .put("Malaysian ringgit", "リンギット|マレーシア・リンギット") + .put("Mauritanian ouguiya", "ウギア|モーリタニア・ウギア|モーリタニアウギア") + .put("Khoums", "コウム") + .put("Mozambican metical", "メティカル|モザンビーク・メティカル|モザンビークメティカル") + .put("Burmese kyat", "チャット|ミャンマー・チャット|ミャンマーチャット") + .put("Pya", "ピャー") + .put("Nigerian naira", "ナイラ|ナイジェリア・ナイラ|ナイジェリアナイラ") + .put("Kobo", "コボ") + .put("Turkish lira", "トルコリラ") + .put("Kuruş", "クルシュ") + .put("Omani rial", "オマーン・リアル") + .put("Panamanian balboa", "バルボア|パナマ・バルボア|パナマバルボア") + .put("Centesimo", "センテシモ") + .put("Papua New Guinean kina", "キナ|パプア・ニューギニア・キナ") + .put("Toea", "トエア") + .put("Peruvian sol", "ヌエボ・ソル") + .put("Polish złoty", "ズウォティ|ポーランド・ズウォティ|ポーランドズウォティ") + .put("Grosz", "グロシュ") + .put("Qatari riyal", "カタール・リヤル") + .put("Saudi riyal", "サウジアラビア・リヤル") + .put("Riyal", "リヤル") + .put("Dirham", "ディルハム") + .put("Halala", "ハララ") + .put("Samoan tālā", "タラ|サモア・タラ|サモアタラ") + .put("Sierra Leonean leone", "レオン|シエラレオネ・レオン|シエラレオネレオン") + .put("Peseta", "ペセタ") + .put("Swazi lilangeni", "リランゲニ|スワジランド・リランゲニ|スワジランドリランゲニ") + .put("Tajikistani somoni", "ソモニ|タジキスタン・ソモニ|タジキスタンソモニ") + .put("Thai baht", "バーツ|タイ・バーツ|タイバーツ") + .put("Satang", "サタン") + .put("Tongan paʻanga", "パアンガ|トンガ・パアンガ|トンガパアンガ") + .put("Ukrainian hryvnia", "フリヴニャ|ウクライナ・フリヴニャ|ウクライナフリヴニャ") + .put("Vanuatu vatu", "バツ|バヌアツ・バツ|バヌアツバツ") + .put("Vietnamese dong", "ドン|ベトナム・ドン|ベトナムドン") + .put("Indonesian rupiah", "ルピア|インドネシア・ルピア|インドネシアルピア") + .put("Netherlands guilder", "オランダ・ユーロ") + .put("Surinam florin", "スリナムフロリン") + .put("Zambian kwacha", "ザンビア・クワチャ") + .put("Moroccan dirham", "モロッコ・ディルハム") + .put("United Arab Emirates dirham", "UAEディルハム") + .put("Azerbaijani manat", "アゼルバイジャン・マナト") + .put("Turkmenistan manat", "トルクメニスタン・マナト") + .put("Manat", "マナト") + .put("Somali shilling", "ソマリア・シリング") + .put("Somaliland shilling", "ソマリランド・シリング") + .put("Tanzanian shilling", "タンザニア・シリング") + .put("Ugandan shilling", "ウガンダ・シリング") + .put("Romanian leu", "ルーマニア・レウ") + .put("Moldovan leu", "モルドバ・レウ") + .put("Leu", "レウ") + .put("Ban", "バン") + .put("Nepalese rupee", "ネパール・ルピー") + .put("Pakistani rupee", "パキスタン・ルピー") + .put("Indian rupee", "インド・ルピー") + .put("Seychellois rupee", "セーシェル・ルピー") + .put("Mauritian rupee", "モーリシャス・ルピー") + .put("Maldivian rufiyaa", "ルフィヤ|モルディブ・ルフィヤ|モルディブルフィヤ") + .put("Sri Lankan rupee", "スリランカ・ルピー") + .put("Rupee", "ルピー") + .put("Czech koruna", "チェコ・コルナ") + .put("Danish krone", "デンマーク・クローネ") + .put("Norwegian krone", "ノルウェー・クローネ") + .put("Faroese króna", "フェロー・クローネ") + .put("Icelandic króna", "アイスランド・クローナ") + .put("Swedish krona", "スウェーデン・クローナ") + .put("Krone", "クローナ") + .put("Øre", "オーレ") + .put("West African CFA franc", "西アフリカCFAフラン") + .put("Central African CFA franc", "中央アフリカCFAフラン") + .put("Comorian franc", "コモロ・フラン") + .put("Congolese franc", "コンゴ・フラン") + .put("Burundian franc", "ブルンジ・フラン") + .put("Djiboutian franc", "ジブチ・フラン") + .put("CFP franc", "CFPフラン") + .put("Guinean franc", "ギニア・フラン") + .put("Swiss franc", "スイス・フラン") + .put("Rwandan franc", "ルワンダ・フラン") + .put("Belgian franc", "ベルギー・フラン") + .put("Rappen", "Rappen") + .put("Franc", "フラン") + .put("Centime", "サンチーム") + .put("Russian ruble", "ロシア・ルーブル") + .put("Transnistrian ruble", "沿ドニエストル・ルーブル") + .put("Belarusian ruble", "ベラルーシ・ルーブル") + .put("Kopek", "カペイカ") + .put("Ruble", "ルーブル") + .put("Algerian dinar", "アルジェリア・ディナール") + .put("Bahraini dinar", "バーレーン・ディナール") + .put("Iraqi dinar", "イラク・ディナール") + .put("Jordanian dinar", "ヨルダン・ディナール") + .put("Kuwaiti dinar", "クウェート・ディナール") + .put("Libyan dinar", "リビア・ディナール") + .put("Serbian dinar", "セルビア・ディナール") + .put("Tunisian dinar", "チュニジア・ディナール") + .put("Dinar", "ディナール") + .put("Fils", "フィルス") + .put("Para", "パラ") + .put("Millime", "ミリム") + .put("Argentine peso", "アルゼンチン・ペソ") + .put("Chilean peso", "チリ・ペソ") + .put("Colombian peso", "コロンビア・ペソ") + .put("Cuban peso", "兌換ペソ") + .put("Dominican peso", "ドミニカ・ペソ") + .put("Mexican peso", "メキシコ・ペソ") + .put("Philippine peso", "フィリピン・ペソ") + .put("Uruguayan peso", "ウルグアイ・ペソ") + .put("Peso", "ペソ") + .put("Centavo", "センターボ") + .put("Alderney pound", "オルダニーポンド") + .put("British pound", "UKポンド") + .put("Guernsey pound", "ガーンジー・ポンド") + .put("Saint Helena pound", "セントヘレナ・ポンド") + .put("Egyptian pound", "エジプト・ポンド") + .put("Falkland Islands pound", "フォークランド諸島ポンド") + .put("Gibraltar pound", "ジブラルタル・ポンド") + .put("Manx pound", "マン島ポンド") + .put("Jersey pound", "ジャージー・ポンド") + .put("Lebanese pound", "レバノン・ポンド") + .put("South Sudanese pound", "南スーダン・ポンド") + .put("Sudanese pound", "スーダン・ポンド") + .put("Syrian pound", "シリア・ポンド") + .put("Pound", "ポンド") + .put("Pence", "ペンス") + .put("Shilling", "シリング") + .put("United States dollar", "ドル|USドル") + .put("East Caribbean dollar", "東カリブ・ドル") + .put("Australian dollar", "オーストラリア・ドル|オーストラリアドル") + .put("Bahamian dollar", "バハマ・ドル") + .put("Barbadian dollar", "バルバドス・ドル") + .put("Belize dollar", "ベリーズ・ドル") + .put("Bermudian dollar", "バミューダ・ドル") + .put("Brunei dollar", "ブルネイ・ドル") + .put("Singapore dollar", "シンガポール・ドル") + .put("Canadian dollar", "カナダ・ドル") + .put("Cayman Islands dollar", "ケイマン諸島・ドル") + .put("New Zealand dollar", "ニュージーランド・ドル") + .put("Cook Islands dollar", "クックアイランド・ドル") + .put("Fijian dollar", "フィジー・ドル|フィジー・ドル") + .put("Guyanese dollar", "ガイアナ・ドル|ガイアナ・ドル") + .put("Hong Kong dollar", "香港ドル") + .put("Macau Pataca", "マカオ・パタカ|マカオ・パタカ") + .put("New Taiwan dollar", "ニュー台湾ドル|ニュー台湾ドル") + .put("Jamaican dollar", "ジャマイカ・ドル|ジャマイカドル") + .put("Kiribati dollar", "キリバス・ドル") + .put("Liberian dollar", "リベリア・ドル|リベリアドル") + .put("Namibian dollar", "ナミビア・ドル|ナミビアドル") + .put("Surinamese dollar", "スリナム・ドル|スリナムドル") + .put("Trinidad and Tobago dollar", "トリニダード・トバゴ・ドル|トリニダードトバゴ・ドル") + .put("Tuvaluan dollar", "ツバル・ドル|ツバルドル") + .put("Chinese yuan", "人民元") + .put("Fen", "分") + .put("Jiao", "角") + .put("Finnish markka", "フィンランド・マルカ") + .put("Penni", "ペニー") + .build(); + + public static final ImmutableMap CurrencyNameToIsoCodeMap = ImmutableMap.builder() + .put("Afghan afghani", "AFN") + .put("Euro", "EUR") + .put("Albanian lek", "ALL") + .put("Angolan kwanza", "AOA") + .put("Armenian dram", "AMD") + .put("Aruban florin", "AWG") + .put("Bangladeshi taka", "BDT") + .put("Bhutanese ngultrum", "BTN") + .put("Bolivian boliviano", "BOB") + .put("Bosnia and Herzegovina convertible mark", "BAM") + .put("Botswana pula", "BWP") + .put("Brazilian real", "BRL") + .put("Bulgarian lev", "BGN") + .put("Cambodian riel", "KHR") + .put("Cape Verdean escudo", "CVE") + .put("Costa Rican colón", "CRC") + .put("Croatian kuna", "HRK") + .put("Czech koruna", "CZK") + .put("Eritrean nakfa", "ERN") + .put("Ethiopian birr", "ETB") + .put("Gambian dalasi", "GMD") + .put("Georgian lari", "GEL") + .put("Ghanaian cedi", "GHS") + .put("Guatemalan quetzal", "GTQ") + .put("Haitian gourde", "HTG") + .put("Honduran lempira", "HNL") + .put("Hungarian forint", "HUF") + .put("Iranian rial", "IRR") + .put("Yemeni rial", "YER") + .put("Israeli new shekel", "ILS") + .put("Japanese yen", "JPY") + .put("Kazakhstani tenge", "KZT") + .put("Kenyan shilling", "KES") + .put("North Korean won", "KPW") + .put("South Korean won", "KRW") + .put("Kyrgyzstani som", "KGS") + .put("Lao kip", "LAK") + .put("Lesotho loti", "LSL") + .put("South African rand", "ZAR") + .put("Macanese pataca", "MOP") + .put("Macedonian denar", "MKD") + .put("Malagasy ariary", "MGA") + .put("Malawian kwacha", "MWK") + .put("Malaysian ringgit", "MYR") + .put("Mauritanian ouguiya", "MRO") + .put("Mongolian tögrög", "MNT") + .put("Mozambican metical", "MZN") + .put("Burmese kyat", "MMK") + .put("Nicaraguan córdoba", "NIO") + .put("Nigerian naira", "NGN") + .put("Turkish lira", "TRY") + .put("Omani rial", "OMR") + .put("Panamanian balboa", "PAB") + .put("Papua New Guinean kina", "PGK") + .put("Paraguayan guaraní", "PYG") + .put("Peruvian sol", "PEN") + .put("Polish złoty", "PLN") + .put("Qatari riyal", "QAR") + .put("Saudi riyal", "SAR") + .put("Samoan tālā", "WST") + .put("São Tomé and Príncipe dobra", "STD") + .put("Sierra Leonean leone", "SLL") + .put("Swazi lilangeni", "SZL") + .put("Tajikistani somoni", "TJS") + .put("Thai baht", "THB") + .put("Ukrainian hryvnia", "UAH") + .put("Vanuatu vatu", "VUV") + .put("Venezuelan bolívar", "VEF") + .put("Zambian kwacha", "ZMW") + .put("Moroccan dirham", "MAD") + .put("United Arab Emirates dirham", "AED") + .put("Azerbaijani manat", "AZN") + .put("Turkmenistan manat", "TMT") + .put("Somali shilling", "SOS") + .put("Tanzanian shilling", "TZS") + .put("Ugandan shilling", "UGX") + .put("Romanian leu", "RON") + .put("Moldovan leu", "MDL") + .put("Nepalese rupee", "NPR") + .put("Pakistani rupee", "PKR") + .put("Indian rupee", "INR") + .put("Seychellois rupee", "SCR") + .put("Mauritian rupee", "MUR") + .put("Maldivian rufiyaa", "MVR") + .put("Sri Lankan rupee", "LKR") + .put("Indonesian rupiah", "IDR") + .put("Danish krone", "DKK") + .put("Norwegian krone", "NOK") + .put("Icelandic króna", "ISK") + .put("Swedish krona", "SEK") + .put("West African CFA franc", "XOF") + .put("Central African CFA franc", "XAF") + .put("Comorian franc", "KMF") + .put("Congolese franc", "CDF") + .put("Burundian franc", "BIF") + .put("Djiboutian franc", "DJF") + .put("CFP franc", "XPF") + .put("Guinean franc", "GNF") + .put("Swiss franc", "CHF") + .put("Rwandan franc", "RWF") + .put("Russian ruble", "RUB") + .put("Transnistrian ruble", "PRB") + .put("Belarusian ruble", "BYN") + .put("Algerian dinar", "DZD") + .put("Bahraini dinar", "BHD") + .put("Iraqi dinar", "IQD") + .put("Jordanian dinar", "JOD") + .put("Kuwaiti dinar", "KWD") + .put("Libyan dinar", "LYD") + .put("Serbian dinar", "RSD") + .put("Tunisian dinar", "TND") + .put("Argentine peso", "ARS") + .put("Chilean peso", "CLP") + .put("Colombian peso", "COP") + .put("Cuban convertible peso", "CUC") + .put("Cuban peso", "CUP") + .put("Dominican peso", "DOP") + .put("Mexican peso", "MXN") + .put("Uruguayan peso", "UYU") + .put("British pound", "GBP") + .put("Saint Helena pound", "SHP") + .put("Egyptian pound", "EGP") + .put("Falkland Islands pound", "FKP") + .put("Gibraltar pound", "GIP") + .put("Manx pound", "IMP") + .put("Jersey pound", "JEP") + .put("Lebanese pound", "LBP") + .put("South Sudanese pound", "SSP") + .put("Sudanese pound", "SDG") + .put("Syrian pound", "SYP") + .put("United States dollar", "USD") + .put("Australian dollar", "AUD") + .put("Bahamian dollar", "BSD") + .put("Barbadian dollar", "BBD") + .put("Belize dollar", "BZD") + .put("Bermudian dollar", "BMD") + .put("Brunei dollar", "BND") + .put("Singapore dollar", "SGD") + .put("Canadian dollar", "CAD") + .put("Cayman Islands dollar", "KYD") + .put("New Zealand dollar", "NZD") + .put("Fijian dollar", "FJD") + .put("Guyanese dollar", "GYD") + .put("Hong Kong dollar", "HKD") + .put("Jamaican dollar", "JMD") + .put("Liberian dollar", "LRD") + .put("Namibian dollar", "NAD") + .put("Solomon Islands dollar", "SBD") + .put("Surinamese dollar", "SRD") + .put("New Taiwan dollar", "TWD") + .put("Trinidad and Tobago dollar", "TTD") + .put("Tuvaluan dollar", "TVD") + .put("Chinese yuan", "CNY") + .put("Rial", "__RI") + .put("Shiling", "__S") + .put("Som", "__SO") + .put("Dirham", "__DR") + .put("Dinar", "_DN") + .put("Dollar", "__D") + .put("Manat", "__MA") + .put("Rupee", "__R") + .put("Krone", "__K") + .put("Krona", "__K") + .put("Crown", "__K") + .put("Frank", "__F") + .put("Mark", "__M") + .put("Ruble", "__RB") + .put("Peso", "__PE") + .put("Pound", "__P") + .put("Tristan da Cunha pound", "_TP") + .put("South Georgia and the South Sandwich Islands pound", "_SP") + .put("Somaliland shilling", "_SS") + .put("Pitcairn Islands dollar", "_PND") + .put("Palauan dollar", "_PD") + .put("Niue dollar", "_NID") + .put("Nauruan dollar", "_ND") + .put("Micronesian dollar", "_MD") + .put("Kiribati dollar", "_KID") + .put("Guernsey pound", "_GGP") + .put("Faroese króna", "_FOK") + .put("Cook Islands dollar", "_CKD") + .put("British Virgin Islands dollar", "_BD") + .put("Ascension pound", "_AP") + .put("Alderney pound", "_ALP") + .put("Abkhazian apsar", "_AA") + .build(); + + public static final ImmutableMap FractionalUnitNameToCodeMap = ImmutableMap.builder() + .put("Jiao", "JIAO") + .put("Kopek", "KOPEK") + .put("Pul", "PUL") + .put("Cent", "CENT") + .put("Qindarkë", "QINDARKE") + .put("Penny", "PENNY") + .put("Santeem", "SANTEEM") + .put("Cêntimo", "CENTIMO") + .put("Centavo", "CENTAVO") + .put("Luma", "LUMA") + .put("Qəpik", "QƏPIK") + .put("Fils", "FILS") + .put("Poisha", "POISHA") + .put("Kapyeyka", "KAPYEYKA") + .put("Centime", "CENTIME") + .put("Chetrum", "CHETRUM") + .put("Paisa", "PAISA") + .put("Fening", "FENING") + .put("Thebe", "THEBE") + .put("Sen", "SEN") + .put("Stotinka", "STOTINKA") + .put("Fen", "FEN") + .put("Céntimo", "CENTIMO") + .put("Lipa", "LIPA") + .put("Haléř", "HALER") + .put("Øre", "ØRE") + .put("Piastre", "PIASTRE") + .put("Santim", "SANTIM") + .put("Oyra", "OYRA") + .put("Butut", "BUTUT") + .put("Tetri", "TETRI") + .put("Pesewa", "PESEWA") + .put("Fillér", "FILLER") + .put("Eyrir", "EYRIR") + .put("Dinar", "DINAR") + .put("Agora", "AGORA") + .put("Tïın", "TIIN") + .put("Chon", "CHON") + .put("Jeon", "JEON") + .put("Tyiyn", "TYIYN") + .put("Att", "ATT") + .put("Sente", "SENTE") + .put("Dirham", "DIRHAM") + .put("Rappen", "RAPPEN") + .put("Avo", "AVO") + .put("Deni", "DENI") + .put("Iraimbilanja", "IRAIMBILANJA") + .put("Tambala", "TAMBALA") + .put("Laari", "LAARI") + .put("Khoums", "KHOUMS") + .put("Ban", "BAN") + .put("Möngö", "MONGO") + .put("Pya", "PYA") + .put("Kobo", "KOBO") + .put("Kuruş", "KURUS") + .put("Baisa", "BAISA") + .put("Centésimo", "CENTESIMO") + .put("Toea", "TOEA") + .put("Sentimo", "SENTIMO") + .put("Grosz", "GROSZ") + .put("Sene", "SENE") + .put("Halala", "HALALA") + .put("Para", "PARA") + .put("Öre", "ORE") + .put("Diram", "DIRAM") + .put("Satang", "SATANG") + .put("Seniti", "SENITI") + .put("Millime", "MILLIME") + .put("Tennesi", "TENNESI") + .put("Kopiyka", "KOPIYKA") + .put("Tiyin", "TIYIN") + .put("Hào", "HAO") + .put("Ngwee", "NGWEE") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?と)"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dollar", "$") + .put("United States dollar", "us$") + .put("British Virgin Islands dollar", "bvi$") + .put("Brunei dollar", "b$") + .put("Sen", "sen") + .put("Singapore dollar", "s$") + .put("Canadian dollar", "can$|c$|c $") + .put("Cayman Islands dollar", "ci$") + .put("New Zealand dollar", "nz$|nz $") + .put("Guyanese dollar", "gy$|gy $|g$|g $") + .put("Hong Kong dollar", "hk$|hkd|hk $") + .put("Jamaican dollar", "j$") + .put("Namibian dollar", "nad|n$|n $") + .put("Solomon Islands dollar", "si$|si $") + .put("New Taiwan dollar", "nt$|nt $") + .put("Samoan tālā", "ws$") + .put("Chinese yuan", "¥") + .put("Japanese yen", "¥|\\") + .put("Turkish lira", "₺") + .put("Euro", "€") + .put("Pound", "£") + .put("Costa Rican colón", "₡") + .build(); + + public static final List CurrencyAmbiguousValues = Arrays.asList("円", "銭", "分", "レク", "プル", "ブル", "\\"); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java new file mode 100644 index 000000000..5e1863215 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/PortugueseNumericWithUnit.java @@ -0,0 +1,510 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class PortugueseNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Ano", "anos|ano") + .put("Mês", "meses|mes|mês") + .put("Semana", "semanas|semana") + .put("Dia", "dias|dia") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("anos", "ano", "meses", "mes", "mês", "semanas", "semana", "dias", "dia"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Quilômetro quadrado", "quilômetro quadrado|quilómetro quadrado|quilometro quadrado|quilômetros quadrados|quilómetros quadrados|quilomeros quadrados|km2|km^2|km²") + .put("Hectare", "hectômetro quadrado|hectómetro quadrado|hectômetros quadrados|hectómetros cuadrados|hm2|hm^2|hm²|hectare|hectares") + .put("Decâmetro quadrado", "decâmetro quadrado|decametro quadrado|decâmetros quadrados|decametro quadrado|dam2|dam^2|dam²|are|ares") + .put("Metro quadrado", "metro quadrado|metros quadrados|m2|m^2|m²") + .put("Decímetro quadrado", "decímetro quadrado|decimentro quadrado|decímetros quadrados|decimentros quadrados|dm2|dm^2|dm²") + .put("Centímetro quadrado", "centímetro quadrado|centimetro quadrado|centímetros quadrados|centrimetros quadrados|cm2|cm^2|cm²") + .put("Milímetro quadrado", "milímetro quadrado|milimetro quadrado|milímetros quadrados|militmetros quadrados|mm2|mm^2|mm²") + .put("Polegada quadrada", "polegada quadrada|polegadas quadradas|in2|in^2|in²") + .put("Pé quadrado", "pé quadrado|pe quadrado|pés quadrados|pes quadrados|pé2|pé^2|pé²|sqft|sq ft|ft2|ft^2|ft²") + .put("Jarda quadrada", "jarda quadrada|jardas quadradas|yd2|yd^2|yd²") + .put("Milha quadrada", "milha quadrada|milhas quadradas|mi2|mi^2|mi²") + .put("Acre", "acre|acres") + .build(); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Dólar", "dólar|dolar|dólares|dolares") + .put("Peso", "peso|pesos") + .put("Coroa", "coroa|coroas") + .put("Rublo", "rublo|rublos") + .put("Libra", "libra|libras") + .put("Florim", "florim|florins|ƒ") + .put("Dinar", "dinar|dinares") + .put("Franco", "franco|francos") + .put("Rupia", "rúpia|rupia|rúpias|rupias") + .put("Escudo", "escudo|escudos") + .put("Xelim", "xelim|xelins|xelims") + .put("Lira", "lira|liras") + .put("Centavo", "centavo|cêntimo|centimo|centavos|cêntimos|centimo") + .put("Centésimo", "centésimo|centésimos") + .put("Pêni", "pêni|péni|peni|penies|pennies") + .put("Manat", "manat|manate|mánate|man|manats|manates|mánates") + .put("Euro", "euro|euros|€|eur") + .put("Centavo de Euro", "centavo de euro|cêntimo de euro|centimo de euro|centavos de euro|cêntimos de euro|centimos de euro") + .put("Dólar do Caribe Oriental", "dólar do Caribe Oriental|dolar do Caribe Oriental|dólares do Caribe Oriental|dolares do Caribe Oriental|dólar das Caraíbas Orientais|dolar das Caraibas Orientais|dólares das Caraíbas Orientais|dolares das Caraibas Orientais|ec$|xcd") + .put("Centavo do Caribe Oriental", "centavo do Caribe Oriental|centavo das Caraíbas Orientais|cêntimo do Caribe Oriental|cêntimo das Caraíbas Orientais|centavos do Caribe Oriental|centavos das Caraíbas Orientais|cêntimos do Caribe Oriental|cêntimos das Caraíbas Orientais") + .put("Franco CFA da África Ocidental", "franco CFA da África Ocidental|franco CFA da Africa Ocidental|francos CFA da África Occidental|francos CFA da Africa Occidental|franco CFA Ocidental|xof") + .put("Centavo de CFA da África Ocidental", "centavo de CFA da Africa Occidental|centavos de CFA da África Ocidental|cêntimo de CFA da Africa Occidental|cêntimos de CFA da África Ocidental") + .put("Franco CFA da África Central", "franco CFA da África Central|franco CFA da Africa Central|francos CFA da África Central|francos CFA da Africa Central|franco CFA central|xaf") + .put("Centavo de CFA da África Central", "centavo de CFA de África Central|centavos de CFA da África Central|cêntimo de CFA de África Central|cêntimos de CFA da África Central") + .put("Apsar abcásio", "apsar abcásio|apsar abecásio|apsar abcasio|apsar|apsares") + .put("Afegani afegão", "afegani afegão|afegane afegão|؋|afn|afegane|afgane|afegâni|afeganis|afeganes|afganes|afegânis") + .put("Pul", "pul|pules|puls") + .put("Lek albanês", "lek|lekë|lekes|lek albanês|leque|leques|all") + .put("Qindarke", "qindarka|qindarkë|qindarke|qindarkas") + .put("Kwanza angolano", "kwanza angolano|kwanzas angolanos|kwanza|kwanzas|aoa|kz") + .put("Cêntimo angolano", "cêntimo angolano") + .put("Florim das Antilhas Holandesas", "florim das antilhas holandesas|florim das antilhas neerlandesas|ang") + .put("Rial saudita", "rial saudita|riais sauditas|riyal saudita|riyals sauditas|riyal|riyals|sar") + .put("Halala saudita", "halala saudita|halala|hallalah") + .put("Dinar argelino", "dinar argelino|dinares argelinos|dzd") + .put("Cêntimo argelino", "centimo argelino|centimos argelinos|cêntimo argelino|cêntimos argelinos|centavo argelino|centavos argelinos") + .put("Peso argentino", "peso argentino|pesos argentinos|ar$|ars") + .put("Centavo argentino", "centavo argentino|centavos argentinos|ctvo.|ctvos.") + .put("Dram armênio", "dram armênio|dram armênios|dram arménio|dram arménios|dram armenio|dram armenios|dram|drame|drames|դր.") + .put("Luma armênio", "luma armênio|lumas armênios|luma arménio|lumas arménios|luma armenio|lumas armenios|luma|lumas") + .put("Florim arubano", "florín arubeño|florines arubeños|ƒ arubeños|aƒ|awg") + .put("Dólar australiano", "dólar australiano|dólares australianos|dolar australiano|dolares australianos|a$|aud") + .put("Centavo australiano", "centavo australiano|centavos australianos") + .put("Manat azeri", "manat azeri|manats azeris|azn|manat azerbaijanês|manat azerbaijano|manats azerbaijaneses|manats azerbaijanos") + .put("Qəpik azeri", "qəpik azeri|qəpik|qəpiks") + .put("Dólar bahamense", "dólar bahamense|dólares bahamense|dolar bahamense|dolares bahamense|dólar baamiano|dólares baamiano|dolar baamiano|dolares baamiano|b$|bsd") + .put("Centavo bahamense", "centavo bahamense|centavos bahamense") + .put("Dinar bareinita", "dinar bareinita|dinar baremita|dinares bareinitas|dinares baremitas|bhd") + .put("Fil bareinita", "fil bareinita|fil baremita|fils bareinitas|fils baremitas") + .put("Taka bengali", "taka bengali|takas bengalis|taca|tacas|taka|takas|bdt") + .put("Poisha bengali", "poisha bengali|poishas bengalis") + .put("Dólar de Barbados", "dólar de barbados|dólares de barbados|dolar de barbados|dolares de barbados|dólar dos barbados|dólares dos barbados|bbd") + .put("Centavo de Barbados", "centavo de barbados|centavos de barbados|centavo dos barbados|centavos dos barbados") + .put("Dólar de Belize", "dólar de belize|dólares de belize|dolar de belize|dolares de belize|dólar do belize|dólares do belize|dolar do belize|dolares do belize|bz$|bzd") + .put("Centavo de Belize", "centavo de belize|centavos de belize|cêntimo do belize|cêntimos do belize") + .put("Dólar bermudense", "dólar bermudense|dólares bermudenses|bd$|bmd") + .put("Centavo bermudense", "centavo bermudense|centavos bermudenses|cêntimo bermudense| cêntimos bermudenses") + .put("Rublo bielorrusso", "rublo bielorrusso|rublos bielorrussos|byr") + .put("Copeque bielorusso", "copeque bielorrusso|copeques bielorrussos|kopek bielorrusso|kopeks bielorrussos|kap") + .put("Quiate mianmarense", "quiate mianmarense|quiates mianmarenses|kyat mianmarense|kyates mianmarenses|quiate myanmarense|quiates myanmarenses|kyat myanmarense|kyates myanmarenses|quiate birmanês|quite birmanes|quiates birmaneses|kyat birmanês|kyat birmanes|kyates birmaneses|mmk") + .put("Pya mianmarense", "pya mianmarense|pyas mianmarenses|pya myanmarense|pyas myanmarenses|pya birmanês|pya birmanes|pyas birmaneses") + .put("Boliviano", "boliviano|bolivianos|bob|bs") + .put("Centavo Boliviano", "centavo boliviano|centavos bolivianos") + .put("Marco da Bósnia e Herzegovina", "marco conversível|marco conversivel|marco convertível|marco convertivel|marcos conversíveis|marcos conversiveis|marcos convertíveis|marcos convertivies|bam") + .put("Fening da Bósnia e Herzegovina", "fening conversível|fening conversivel|fening convertível|fening convertivel|fenings conversíveis|fenings conversiveis|fenings convertíveis|fenings convertiveis") + .put("Pula", "pula|pulas|bwp") + .put("Thebe", "thebe|thebes") + .put("Real brasileiro", "real brasileiro|real do brasil|real|reais brasileiros|reais do brasil|reais|r$|brl") + .put("Centavo brasileiro", "centavo de real|centavo brasileiro|centavos de real|centavos brasileiros") + .put("Dólar de Brunei", "dólar de brunei|dolar de brunei|dólar do brunei|dolar do brunei|dólares de brunéi|dolares de brunei|dólares do brunei|dolares do brunei|bnd") + .put("Sen de Brunei", "sen de brunei|sen do brunei|sens de brunei|sens do brunei") + .put("Lev búlgaro", "lev búlgaro|leve búlgaro|leves búlgaros|lev bulgaro|leve bulgaro|leves bulgaros|lv|bgn") + .put("Stotinka búlgaro", "stotinka búlgaro|stotinki búlgaros|stotinka bulgaro|stotinki bulgaros") + .put("Franco do Burundi", "franco do burundi|francos do burundi|fbu|fib") + .put("Centavo Burundi", "centavo burundi|cêntimo burundi|centimo burundi|centavos burundi|cêntimo burundi|centimo burundi") + .put("Ngultrum butanês", "ngultrum butanês|ngultrum butanes|ngúltrume butanês|ngultrume butanes|ngultrum butaneses|ngúltrumes butaneses|ngultrumes butaneses|btn") + .put("Chetrum butanês", "chetrum butanês|chetrum butanes|chetrum butaneses") + .put("Escudo cabo-verdiano", "escudo cabo-verdiano|escudos cabo-verdianos|cve") + .put("Riel cambojano", "riel cambojano|riéis cambojanos|rieis cambojanos|khr") + .put("Dólar canadense", "dólar canadense|dolar canadense|dólares canadenses|dolares canadenses|c$|cad") + .put("Centavo canadense", "centavo canadense|centavos canadenses") + .put("Peso chileno", "peso chileno|pesos chilenos|cpl") + .put("Yuan chinês", "yuan chinês|yuan chines|yuans chineses|yuan|yuans|renminbi|rmb|cny|¥") + .put("Peso colombiano", "peso colombiano|pesos colombianos|cop|col$") + .put("Centavo colombiano", "centavo colombiano|centavos colombianos") + .put("Franco comorense", "franco comorense|francos comorenses|kmf|₣") + .put("Franco congolês", "franco congolês|franco congoles|francos congoleses|cdf") + .put("Centavo congolês", "centavo congolês|centavo congoles|centavos congoleses|cêntimo congolês|centimo congoles|cêntimos congoleses|cêntimos congoleses") + .put("Won norte-coreano", "won norte-coreano|wŏn norte-coreano|won norte-coreanos|wŏn norte-coreanos|kpw") + .put("Chon norte-coreano", "chon norte-coreano|chŏn norte-coreano|chŏn norte-coreanos|chon norte-coreanos") + .put("Won sul-coreano", "wŏn sul-coreano|won sul-coreano|wŏnes sul-coreanos|wones sul-coreanos|krw") + .put("Jeon sul-coreano", "jeons sul-coreano|jeons sul-coreanos") + .put("Colón costarriquenho", "colón costarriquenho|colon costarriquenho|colons costarriquenho|colones costarriquenhos|crc") + .put("Kuna croata", "kuna croata|kunas croatas|hrk") + .put("Lipa croata", "lipa croata|lipas croatas") + .put("Peso cubano", "peso cubano|pesos cubanos|cup") + .put("Peso cubano convertível", "peso cubano conversível|pesos cubanos conversíveis|peso cubano conversivel|pesos cubanos conversiveis|peso cubano convertível|pesos cubanos convertíveis|peso cubano convertivel|pesos cubanos convertiveis|cuc") + .put("Coroa dinamarquesa", "coroa dinamarquesa|coroas dinamarquesas|dkk") + .put("Libra egípcia", "libra egípcia|libra egipcia|libras egípcias|libras egipcias|egp|l.e.") + .put("Piastra egípcia", "piastra egípcia|piastra egipcia|pisastras egípcias|piastras egipcias") + .put("Dirham dos Emirados Árabes Unidos", "dirham|dirhams|dirham dos emirados arabes unidos|aed|dhs") + .put("Nakfa", "nakfa|nfk|ern") + .put("Centavo de Nakfa", "cêntimo de nakfa|cêntimos de nakfa|centavo de nafka|centavos de nafka") + .put("Peseta", "peseta|pesetas|pts.|ptas.|esp") + .put("Dólar estadunidense", "dólar dos estados unidos|dolar dos estados unidos|dólar estadunidense|dólar americano|dólares dos estados unidos|dolares dos estados unidos|dólares estadunidenses|dólares americanos|dolar estadunidense|dolar americano|dolares estadunidenses|dolares americanos|usd|u$d|us$|usd$") + .put("Coroa estoniana", "coroa estoniana|coroas estonianas|eek") + .put("Senti estoniano", "senti estoniano|senti estonianos") + .put("Birr etíope", "birr etíope|birr etiope|birr etíopes|birr etiopes|br|etb") + .put("Santim etíope", "santim etíope|santim etiope|santim etíopes|santim etiopes") + .put("Peso filipino", "peso filipino|pesos filipinos|php") + .put("Marco finlandês", "marco finlandês|marco finlandes|marcos finlandeses") + .put("Dólar fijiano", "dólar fijiano|dolar fijiano|dólares fijianos|dolares fijianos|fj$|fjd") + .put("Centavo fijiano", "centavo fijiano|centavos fijianos") + .put("Dalasi gambiano", "dalasi|gmd") + .put("Bututs", "butut|bututs") + .put("Lari georgiano", "lari georgiano|lari georgianos|gel") + .put("Tetri georgiano", "tetri georgiano|tetri georgianos") + .put("Cedi", "cedi|ghs|gh₵") + .put("Pesewa", "pesewa") + .put("Libra de Gibraltar", "libra de gibraltar|libras de gibraltar|gip") + .put("Peni de Gibraltar", "peni de gibraltar|penies de gibraltar") + .put("Quetzal guatemalteco", "quetzal guatemalteco|quetzales guatemaltecos|quetzal|quetzales|gtq") + .put("Centavo guatemalteco", "centavo guatemalteco|centavos guatemaltecos") + .put("Libra de Guernsey", "libra de Guernsey|libras de Guernsey|ggp") + .put("Peni de Guernsey", "peni de Guernsey|penies de Guernsey") + .put("Franco da Guiné", "franco da guiné|franco da guine| franco guineense|francos da guiné|francos da guine|francos guineense|gnf|fg") + .put("Centavo da Guiné", "cêntimo guineense|centimo guineense|centavo guineense|cêntimos guineenses|centimos guineenses|centavos guineenses") + .put("Dólar guianense", "dólar guianense|dólares guianense|dolar guianense|dolares guianense|gyd|gy") + .put("Gurde haitiano", "gurde haitiano|gourde|gurdes haitianos|htg") + .put("Centavo haitiano", "cêntimo haitiano|cêntimos haitianos|centavo haitiano|centavos haitianos") + .put("Lempira hondurenha", "lempira hondurenha|lempiras hondurenhas|lempira|lempiras|hnl") + .put("Centavo hondurenho", "centavo hondurenho|centavos hondurehos|cêntimo hondurenho|cêntimos hondurenhos") + .put("Dólar de Hong Kong", "dólar de hong kong|dolar de hong kong|dólares de hong kong|dolares de hong kong|hk$|hkd") + .put("Florim húngaro", "florim húngaro|florim hungaro|florins húngaros|florins hungaros|forinte|forintes|huf") + .put("Filér húngaro", "fillér|filér|filler|filer") + .put("Rupia indiana", "rúpia indiana|rupia indiana|rupias indianas|inr") + .put("Paisa indiana", "paisa indiana|paisas indianas") + .put("Rupia indonésia", "rupia indonesia|rupia indonésia|rupias indonesias|rupias indonésias|idr") + .put("Sen indonésio", "send indonésio|sen indonesio|sen indonésios|sen indonesios") + .put("Rial iraniano", "rial iraniano|riais iranianos|irr") + .put("Dinar iraquiano", "dinar iraquiano|dinares iraquianos|iqd") + .put("Fil iraquiano", "fil iraquiano|fils iraquianos|files iraquianos") + .put("Libra manesa", "libra manesa|libras manesas|imp") + .put("Peni manês", "peni manes|peni manês|penies maneses") + .put("Coroa islandesa", "coroa islandesa|coroas islandesas|isk|íkr") + .put("Aurar islandês", "aurar islandês|aurar islandes|aurar islandeses|eyrir") + .put("Dólar das Ilhas Cayman", "dólar das ilhas cayman|dolar das ilhas cayman|dólar das ilhas caimão|dólares das ilhas cayman|dolares das ilhas cayman|dólares das ilhas caimão|ci$|kyd") + .put("Dólar das Ilhas Cook", "dólar das ilhas cook|dolar das ilhas cook|dólares das ilhas cook|dolares das ilhas cook") + .put("Coroa feroesa", "coroa feroesa|coroas feroesas|fkr") + .put("Libra das Malvinas", "libra das malvinas|libras das malvinas|fk£|fkp") + .put("Dólar das Ilhas Salomão", "dólar das ilhas salomão|dolar das ilhas salomao|dólares das ilhas salomão|dolares das ilhas salomao|sbd") + .put("Novo shekel israelense", "novo shekel|novos shekeles|novo shequel|novo siclo|novo xéquel|shekeles novos|novos sheqalim|sheqalim novos|ils|₪") + .put("Agora", "agora|agorot") + .put("Dólar jamaicano", "dólar jamaicano|dolar jamaicano|dólares jamaicanos|dolares jamaicanos|j$|ja$|jmd") + .put("Yen", "yen|iene|yenes|ienes|jpy") + .put("Libra de Jersey", "libra de Jersey|libras de Jersey|jep") + .put("Dinar jordaniano", "dinar jordaniano|dinar jordano|dinares jordanianos|dinares jordanos|jd|jod") + .put("Piastra jordaniana", "piastra jordaniana|piastra jordano|piastras jordanianas|piastra jordaniano|piastras jordanianos|piastras jordanos") + .put("Tengue cazaque", "tenge|tengue|tengué|tengue cazaque|kzt") + .put("Tiyin", "tiyin|tiyins") + .put("Xelim queniano", "xelim queniano|xelins quenianos|ksh|kes") + .put("Som quirguiz", "som quirguiz|som quirguizes|soms quirguizes|kgs") + .put("Tyiyn", "tyiyn|tyiyns") + .put("Dólar de Kiribati", "dólar de kiribati|dolar de kiribati|dólares de kiribati|dolares de kiribati") + .put("Dinar kuwaitiano", "dinar kuwaitiano|dinar cuaitiano|dinares kuwaitiano|dinares cuaitianos|kwd") + .put("Quipe laosiano", "quipe|quipes|kipe|kipes|kip|kip laosiano|kip laociano|kips laosianos|kips laocianos|lak") + .put("Att laosiano", "at|att|att laosiano|att laosianos") + .put("Loti do Lesoto", "loti|lóti|maloti|lotis|lótis|lsl") + .put("Sente", "sente|lisente") + .put("Libra libanesa", "libra libanesa|libras libanesas|lbp") + .put("Dólar liberiano", "dólar liberiano|dolar liberiano|dólares liberianos|dolares liberianos|l$|lrd") + .put("Dinar libio", "dinar libio|dinar líbio|dinares libios|dinares líbios|ld|lyd") + .put("Dirham libio", "dirham libio|dirhams libios|dirham líbio|dirhams líbios") + .put("Litas lituana", "litas lituana|litai lituanas|ltl") + .put("Pataca macaense", "pataca macaense|patacas macaenses|mop$|mop") + .put("Avo macaense", "avo macaense|avos macaenses") + .put("Ho macaense", "ho macaense|ho macaenses") + .put("Dinar macedônio", "denar macedonio|denare macedonios|denar macedônio|denar macedónio|denare macedônio|denare macedónio|dinar macedonio|dinar macedônio|dinar macedónio|dinares macedonios|dinares macedônios|dinares macedónios|den|mkd") + .put("Deni macedônio", "deni macedonio|deni macedônio|deni macedónio|denis macedonios|denis macedônios|denis macedónios") + .put("Ariary malgaxe", "ariai malgaxe|ariary malgaxe|ariary malgaxes|ariaris|mga") + .put("Iraimbilanja", "iraimbilanja|iraimbilanjas") + .put("Ringuite malaio", "ringgit malaio|ringgit malaios|ringgits malaios|ringuite malaio|ringuites malaios|rm|myr") + .put("Sen malaio", "sen malaio|sen malaios|centavo malaio|centavos malaios|cêntimo malaio|cêntimos malaios") + .put("Kwacha do Malawi", "kwacha|cuacha|quacha|mk|mwk") + .put("Tambala", "tambala|tambalas|tambala malawi") + .put("Rupia maldiva", "rupia maldiva|rupias maldivas|rupia das maldivas| rupias das maldivas|mvr") + .put("Dirame marroquino", "dirame marroquino|dirham marroquinho|dirhams marroquinos|dirames marroquinos|mad") + .put("Rupia maurícia", "rupia maurícia|rupia de Maurício|rupia mauricia|rupia de mauricio|rupias de mauricio|rupias de maurício|rupias mauricias|rupias maurícias|mur") + .put("Uguia", "uguia|uguias|oguia|ouguiya|oguias|mro") + .put("Kume", "kumes|kume|khoums") + .put("Peso mexicano", "peso mexicano|pesos mexicanos|mxn") + .put("Centavo mexicano", "centavo mexicano|centavos mexicanos") + .put("Leu moldávio", "leu moldavo|lei moldavos|leu moldávio|leu moldavio|lei moldávios|lei moldavios|leus moldavos|leus moldavios|leus moldávios|mdl") + .put("Ban moldávio", "ban moldavo|bani moldavos") + .put("Tugrik mongol", "tugrik mongol|tugrik|tugriks mongóis|tugriks mongois|tug|mnt") + .put("Metical moçambicao", "metical|metical moçambicano|metical mocambicano|meticais|meticais moçambicanos|meticais mocambicanos|mtn|mzn") + .put("Dólar namibiano", "dólar namibiano|dólares namibianos|dolar namibio|dolares namibios|n$|nad") + .put("Centavo namibiano", "centavo namibiano|centavos namibianos|centavo namibio|centavos namibianos") + .put("Rupia nepalesa", "rupia nepalesa|rupias nepalesas|npr") + .put("Paisa nepalesa", "paisa nepalesa|paisas nepalesas") + .put("Córdova nicaraguense", "córdova nicaraguense|cordova nicaraguense|cordova nicaraguana|córdoba nicaragüense|córdobas nicaragüenses|cordobas nicaraguenses|córdovas nicaraguenses|cordovas nicaraguenses|córdovas nicaraguanasc$|nio") + .put("Centavo nicaraguense", "centavo nicaragüense|centavos nicaraguenses|centavo nicaraguano|centavos nicaraguenses|centavo nicaraguano|centavos nicaraguanos") + .put("Naira", "naira|ngn") + .put("Kobo", "kobo") + .put("Coroa norueguesa", "coroa norueguesa|coroas norueguesas|nok") + .put("Franco CFP", "franco cfp|francos cfp|xpf") + .put("Dólar neozelandês", "dólar neozelandês|dolar neozelandes|dólares neozelandeses|dolares neozelandeses|dólar da nova zelândia|dolar da nova zelandia|dólares da nova zelândia|dolares da nova zelandia|nz$|nzd") + .put("Centavo neozelandês", "centavo neozelandês|centavo neozelandes|centavo da nova zelandia|centavo da nova zelândia|centavos da nova zelandia|centavos neozelandeses|centavos da nova zelândia") + .put("Rial omanense", "rial omani|riais omanis|rial omanense|riais omanenses|omr") + .put("Baisa omanense", "baisa omani|baisas omanis|baisa omanense|baisas omanenses") + .put("Florim holandês", "florim holandês|florim holandes|florins holandeses|nlg") + .put("Rupia paquistanesa", "rupia paquistanesa|rupias paquistanesas|pkr") + .put("Paisa paquistanesa", "paisa paquistanesa|paisas paquistanesasas") + .put("Balboa panamenho", "balboa panamenho|balboas panamenhos|balboa|pab|balboa panamense|balboas panamenses") + .put("Centavo panamenho", "centavo panamenho|cêntimo panamenho|centavos panamenhos|cêntimos panamenhos|cêntimo panamense|cêntimos panamenses") + .put("Kina", "kina|kina papuásia|kinas|kinas papuásias|pkg|pgk") + .put("Toea", "toea") + .put("Guarani", "guarani|guaranis|gs|pyg") + .put("Novo Sol", "novo sol peruano|novos sóis peruanos|sol|soles|sóis|nuevo sol|pen|s#.") + .put("Centavo de sol", "cêntimo de sol|cêntimos de sol|centavo de sol|centavos de sol") + .put("Złoty", "złoty|złotys|zloty|zlotys|zloti|zlotis|zlóti|zlótis|zlote|zł|pln") + .put("Groszy", "groszy|grosz") + .put("Rial catariano", "rial qatari|riais qataris|rial catarense|riais catarenses|rial catariano|riais catarianos|qr|qar") + .put("Dirame catariano", "dirame catariano|dirames catarianos|dirame qatari|dirames qataris|dirame catarense|dirames catarenses|dirham qatari|dirhams qataris|dirham catarense|dirhams catarenses|dirham catariano|dirhams catariano") + .put("Libra esterlina", "libra esterlina|libras esterlinas|gbp") + .put("Coroa checa", "coroa checa|coroas checas|kc|czk") + .put("Peso dominicano", "peso dominicano|pesos dominicanos|rd$|dop") + .put("Centavo dominicano", "centavo dominicano|centavos dominicanos") + .put("Franco ruandês", "franco ruandês|franco ruandes|francos ruandeses|rf|rwf") + .put("Céntimo ruandês", "cêntimo ruandês|centimo ruandes|centavo ruandês|centavo ruandes|cêntimos ruandeses|centimos ruandeses|centavos ruandeses") + .put("Leu romeno", "leu romeno|lei romenos|leus romenos|ron") + .put("Ban romeno", "ban romeno|bani romeno|bans romenos") + .put("Rublo russo", "rublo russo|rublos russos|rub|р.") + .put("Copeque ruso", "copeque russo|copeques russos|kopek ruso|kopeks rusos|copeque|copeques|kopek|kopeks") + .put("Tala samoano", "tala|tālā|talas|tala samonano|talas samoanos|ws$|sat|wst") + .put("Sene samoano", "sene") + .put("Libra de Santa Helena", "libra de santa helena|libras de santa helena|shp") + .put("Pêni de Santa Helena", "peni de santa helena|penies de santa helena") + .put("Dobra", "dobra|dobras|db|std") + .put("Dinar sérvio", "dinar sérvio|dinar servio|dinar serbio|dinares sérvios|dinares servios|dinares serbios|rsd") + .put("Para sérvio", "para sérvio|para servio|para serbio|paras sérvios|paras servios|paras serbios") + .put("Rupia seichelense", "rupia de seicheles|rupias de seicheles|rupia seichelense|rupias seichelenses|scr") + .put("Centavo seichelense", "centavo de seicheles|centavos de seicheles|centavo seichelense|centavos seichelenses") + .put("Leone serra-leonino", "leone|leones|leone serra-leonino|leones serra-leoninos|le|sll") + .put("Dólar de Cingapura", "dólar de singapura|dolar de singapura|dórar de cingapura|dolar de cingapura|dólares de singapura|dolares de singapura|dólares de cingapura|dolares de cingapura|sgb") + .put("Centavo de Cingapura", "centavo de singapura|centavos de singapura|centavo de cingapura|centavos de cingapura") + .put("Libra síria", "libra síria|libra siria|libras sírias|libras sirias|s£|syp") + .put("Piastra síria", "piastra siria|piastras sirias|piastra síria|piastras sírias") + .put("Xelim somali", "xelim somali|xelins somalis|xelim somaliano|xelins somalianos|sos") + .put("Centavo somali", "centavo somapli|centavos somalis|centavo somaliano|centavos somalianos") + .put("Xelim da Somalilândia", "xelim da somalilândia|xelins da somalilândia|xelim da somalilandia|xelins da somalilandia") + .put("Centavo da Somalilândia", "centavo da somalilândia|centavos da somalilândia|centavo da somalilandia|centavos da somalilandia") + .put("Rupia do Sri Lanka", "rupia do sri lanka|rupia do sri lanca|rupias do sri lanka|rupias do sri lanca|rupia cingalesa|rupias cingalesas|lkr") + .put("Lilangeni", "lilangeni|lilangenis|emalangeni|szl") + .put("Rand sul-africano", "rand|rand sul-africano|rands|rands sul-africanos|zar") + .put("Libra sudanesa", "libra sudanesa|libras sudanesas|sdg") + .put("Piastra sudanesa", "piastra sudanesa|piastras sudanesas") + .put("Libra sul-sudanesa", "libra sul-sudanesa|libras sul-sudanesas|ssp") + .put("Piastra sul-sudanesa", "piastra sul-sudanesa|piastras sul-sudanesas") + .put("Coroa sueca", "coroa sueca|coroas suecas|sek") + .put("Franco suíço", "franco suíço|franco suico|francos suíços|francos suicos|sfr|chf") + .put("Rappen suíço", "rappen suíço|rappen suico|rappens suíços|rappens suicos") + .put("Dólar surinamês", "dólar surinamês|dolar surinames|dólar do Suriname|dolar do Suriname|dólares surinameses|dolares surinameses|dólares do Suriname|dolares do Suriname|srd") + .put("Centavo surinamês", "centavo surinamês|centavo surinames|centavos surinameses") + .put("Baht tailandês", "baht tailandês|bath tailandes|baht tailandeses|thb") + .put("Satang tailandês", "satang tailandês|satang tailandes|satang tailandeses") + .put("Novo dólar taiwanês", "novo dólar taiwanês|novo dolar taiwanes|dólar taiwanês|dolar taiwanes|dólares taiwaneses|dolares taiwaneses|twd") + .put("Centavo taiwanês", "centavo taiwanês|centavo taiwanes|centavos taiwaneses") + .put("Xelim tanzaniano", "xelim tanzaniano|xelins tanzanianos|tzs") + .put("Centavo tanzaniano", "centavo tanzaniano|centavos tanzanianos") + .put("Somoni tajique", "somoni tajique|somoni|somonis tajiques|somonis|tjs") + .put("Diram tajique", "diram tajique|dirams tajiques|dirames tajiques") + .put("Paʻanga", "paanga|paangas|paʻanga|pa'anga|top") + .put("Seniti", "seniti") + .put("Rublo transdniestriano", "rublo transdniestriano|rublos transdniestriano") + .put("Copeque transdniestriano", "copeque transdniestriano|copeques transdniestriano") + .put("Dólar de Trinidade e Tobago", "dólar de trinidade e tobago|dólares trinidade e tobago|dolar de trinidade e tobago|dolares trinidade e tobago|dólar de trinidad e tobago|dólares trinidad e tobago|ttd") + .put("Centavo de Trinidade e Tobago", "centavo de trinidade e tobago|centavos de trinidade e tobago|centavo de trinidad e tobago|centavos de trinidad e tobago") + .put("Dinar tunisiano", "dinar tunisiano|dinares tunisianos|dinar tunisino|dinares tunisinos|tnd") + .put("Milim tunisiano", "milim tunisiano|milim tunesianos|millime tunisianos|millimes tunisianos|milim tunisino|milim tunisinos|millime tunisinos|millimes tunisinos") + .put("Lira turca", "lira turca|liras turcas|try") + .put("Kuruş turco", "kuruş turco|kuruş turcos") + .put("Manat turcomeno", "manat turcomeno|manats turcomenos|tmt") + .put("Tennesi turcomeno", "tennesi turcomeno|tennesis turcomenos|tenge turcomenos|tenges turcomenos") + .put("Dólar tuvaluano", "dólar tuvaluano|dolar tuvaluano|dólares tuvaluanos|dolares tuvaluanos") + .put("Centavo tuvaluano", "centavo tuvaluano|centavos tuvaluanos") + .put("Grívnia", "grívnia|grivnia|grívnias|grivnias|grivna|grivnas|uah") + .put("Copeque ucraniano", "kopiyka|copeque ucraniano|copeques ucranianos") + .put("Xelim ugandês", "xelim ugandês|xelim ugandes|xelins ugandeses|ugx") + .put("Centavo ugandês", "centavo ugandês|centavo ugandes|centavos ugandeses") + .put("Peso uruguaio", "peso uruguaio|pesos uruguayis|uyu") + .put("Centésimo uruguayo", "centésimo uruguaio|centesimo uruguaio|centésimos uruguaios|centesimos uruguaios") + .put("Som uzbeque", "som uzbeque|som uzbeques|soms uzbeques|somes uzbeques|som usbeque|som usbeques|soms usbeques|somes usbeques|uzs") + .put("Tiyin uzbeque", "tiyin uzbeque|tiyin uzbeques|tiyins uzbeques|tiyin usbeque|tiyin usbeques|tiyins usbeques") + .put("Vatu", "vatu|vatus|vuv") + .put("Bolívar forte venezuelano", "bolívar forte|bolivar forte|bolívar|bolivar|bolívares|bolivares|vef") + .put("Centavo de bolívar", "cêntimo de bolívar|cêntimos de bolívar|centavo de bolívar|centavo de bolivar|centavos de bolívar|centavos de bolivar") + .put("Dongue vietnamita", "dongue vietnamita|Đồng vietnamita|dong vietnamita|dongues vietnamitas|dongs vietnamitas|vnd") + .put("Hào vietnamita", "hào vietnamita|hao vietnamita|hào vietnamitas|hàos vietnamitas|haos vietnamitas") + .put("Rial iemenita", "rial iemenita|riais iemenitas|yer") + .put("Fils iemenita", "fils iemenita|fils iemenitas") + .put("Franco djibutiano", "franco djibutiano|francos djibutianos|franco jibutiano|francos jibutianos|djf") + .put("Dinar iugoslavo", "dinar iugoslavo|dinares iugoslavos|dinar jugoslavo|dinares jugoslavos|yud") + .put("Kwacha zambiano", "kwacha zambiano|kwacha zambianos|kwachas zambianos|zmw") + .put("Ngwee zambiano", "ngwee zambiano|ngwee zambianos|ngwees zambianos") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dólar", "$") + .put("Dólar estadunidense", "us$|u$d|usd$|usd") + .put("Dólar do Caribe Oriental", "ec$|xcd") + .put("Dólar australiano", "a$|aud") + .put("Dólar bahamense", "b$|bsd") + .put("Dólar de Barbados", "bds$|bbd") + .put("Dólar de Belize", "bz$|bzd") + .put("Dólar bermudense", "bd$|bmd") + .put("Dólar de Brunei", "brunéi $|bnd") + .put("Dólar de Cingapura", "s$|sgd") + .put("Dólar canadense", "c$|can$|cad") + .put("Dólar das Ilhas Cayman", "ci$|kyd") + .put("Dólar neozelandês", "nz$|nzd") + .put("Dólar fijiano", "fj$|fjd") + .put("Dólar guianense", "gy$|gyd") + .put("Dólar de Hong Kong", "hk$|hkd") + .put("Dólar jamaicano", "j$|ja$|jmd") + .put("Dólar liberiano", "l$|lrd") + .put("Dólar namibiano", "n$|nad") + .put("Dólar das Ilhas Salomão", "si$|sbd") + .put("Novo dólar taiwanês", "nt$|twd") + .put("Real brasileiro", "r$|brl") + .put("Guarani", "₲|gs.|pyg") + .put("Dólar de Trinidade e Tobago", "tt$|ttd") + .put("Yuan chinês", "¥|cny|rmb") + .put("Yen", "¥|jpy") + .put("Euro", "€|eur") + .put("Florim", "ƒ") + .put("Libra", "£") + .put("Colón costarriquenho", "₡") + .put("Lira turca", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("le", "agora"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("bit", "bit|bits") + .put("kilobit", "kilobit|kilobits|kb|kbit") + .put("megabit", "megabit|megabits|Mb|Mbit") + .put("gigabit", "gigabit|gigabits|Gb|Gbit") + .put("terabit", "terabit|terabits|Tb|Tbit") + .put("petabit", "petabit|petabits|Pb|Pbit") + .put("kibibit", "kibibit|kibibits|kib|kibit") + .put("mebibit", "mebibit|mebibits|Mib|Mibit") + .put("gibibit", "gibibit|gibibits|Gib|Gibit") + .put("tebibit", "tebibit|tebibits|Tib|Tibit") + .put("pebibit", "pebibit|pebibits|Pib|Pibit") + .put("byte", "byte|bytes") + .put("kilobyte", "kilobyte|kilobytes|kB|kByte") + .put("megabyte", "megabyte|megabytes|MB|MByte") + .put("gigabyte", "gigabyte|gigabytes|GB|GByte") + .put("terabyte", "terabyte|terabytes|TB|TByte") + .put("petabyte", "petabyte|petabytes|PB|PByte") + .put("kibibyte", "kibibyte|kibibytes|kiB|kiByte") + .put("mebibyte", "mebibyte|mebibytes|MiB|MiByte") + .put("gibibyte", "gibibyte|gibibytes|GiB|GiByte") + .put("tebibyte", "tebibyte|tebibytes|TiB|TiByte") + .put("pebibyte", "pebibyte|pebibytes|PiB|PiByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("ton", "tonelada", "área", "area", "áreas", "areas", "milha", "milhas", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Quilômetro", "km|quilometro|quilômetro|quilómetro|quilometros|quilômetros|quilómetros") + .put("Hectômetro", "hm|hectometro|hectômetro|hectómetro|hectometros|hectômetros|hectómetros") + .put("Decâmetro", "decametro|decâmetro|decámetro|decametros|decâmetro|decámetros|dam") + .put("Metro", "m|m.|metro|metros") + .put("Decímetro", "dm|decimetro|decímetro|decimetros|decímetros") + .put("Centímetro", "cm|centimetro|centímetro|centimetros|centimetros") + .put("Milímetro", "mm|milimetro|milímetro|milimetros|milímetros") + .put("Micrômetro", "µm|um|micrometro|micrômetro|micrómetro|micrometros|micrômetros|micrómetros|micron|mícron|microns|mícrons|micra") + .put("Nanômetro", "nm|nanometro|nanômetro|nanómetro|nanometros|nanômetros|nanómetros|milimicron|milimícron|milimicrons|milimícrons") + .put("Picômetro", "pm|picometro|picômetro|picómetro|picometros|picômetros|picómetros") + .put("Milha", "mi|milha|milhas") + .put("Jarda", "yd|jarda|jardas") + .put("Polegada", "polegada|polegadas|\"") + .put("Pé", "pé|pe|pés|pes|ft") + .put("Ano luz", "ano luz|anos luz|al") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("mi", "milha", "milhas", "\""); + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Metro por segundo", "metro/segundo|m/s|metro por segundo|metros por segundo|metros por segundos") + .put("Quilômetro por hora", "km/h|quilômetro por hora|quilómetro por hora|quilometro por hora|quilômetros por hora|quilómetros por hora|quilometros por hora|quilômetro/hora|quilómetro/hora|quilometro/hora|quilômetros/hora|quilómetros/hora|quilometros/hora") + .put("Quilômetro por minuto", "km/min|quilômetro por minuto|quilómetro por minuto|quilometro por minuto|quilômetros por minuto|quilómetros por minuto|quilometros por minuto|quilômetro/minuto|quilómetro/minuto|quilometro/minuto|quilômetros/minuto|quilómetros/minuto|quilometros/minuto") + .put("Quilômetro por segundo", "km/seg|quilômetro por segundo|quilómetro por segundo|quilometro por segundo|quilômetros por segundo|quilómetros por segundo|quilometros por segundo|quilômetro/segundo|quilómetro/segundo|quilometro/segundo|quilômetros/segundo|quilómetros/segundo|quilometros/segundo") + .put("Milha por hora", "mph|milha por hora|mi/h|milha/hora|milhas/hora|milhas por hora") + .put("Nó", "kt|nó|nós|kn") + .put("Pé por segundo", "ft/s|pé/s|pe/s|ft/seg|pé/seg|pe/seg|pé por segundo|pe por segundo|pés por segundo|pes por segundo") + .put("Pé por minuto", "ft/min|pé/mind|pe/min|pé por minuto|pe por minuto|pés por minuto|pes por minuto") + .put("Jarda por minuto", "jardas por minuto|jardas/minuto|jardas/min") + .put("Jarda por segundo", "jardas por segundo|jardas/segundo|jardas/seg") + .build(); + + public static final List AmbiguousSpeedUnitList = Arrays.asList("nó", "no", "nós", "nos"); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("Grau Rankine", "r|°r|°ra|grau rankine|graus rankine| rankine") + .put("Grau Celsius", "°c|grau c|grau celsius|graus c|graus celsius|celsius|grau centígrado|grau centrigrado|graus centígrados|graus centigrados|centígrado|centígrados|centigrado|centigrados") + .put("Grau Fahrenheit", "°f|grau f|graus f|grau fahrenheit|graus fahrenheit|fahrenheit") + .put("Grau", "°|graus|grau") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Quilômetro cúbico", "quilômetro cúbico|quilómetro cúbico|quilometro cubico|quilômetros cúbicos|quilómetros cúbicos|quilometros cubicos|km3|km^3|km³") + .put("Hectômetro cúbico", "hectômetro cúbico|hectómetro cúbico|hectometro cubico|hectômetros cúbicos|hectómetros cúbicos|hectometros cubicos|hm3|hm^3|hm³") + .put("Decâmetro cúbico", "decâmetro cúbico|decámetro cúbico|decametro cubico|decâmetros cúbicos|decámetros cúbicos|decametros cubicosdam3|dam^3|dam³") + .put("Metro cúbico", "metro cúbico|metro cubico|metros cúbicos|metros cubicos|m3|m^3|m³") + .put("Decímetro cúbico", "decímetro cúbico|decimetro cubico|decímetros cúbicos|decimetros cubicos|dm3|dm^3|dm³") + .put("Centímetro cúbico", "centímetro cúbico|centimetro cubico|centímetros cúbicos|centrimetros cubicos|cc|cm3|cm^3|cm³") + .put("Milímetro cúbico", "milímetro cúbico|milimetro cubico|milímetros cúbicos|milimetros cubicos|mm3|mm^3|mm³") + .put("Polegada cúbica", "polegada cúbica|polegada cubica|polegadas cúbicas|polegadas cubicas") + .put("Pé cúbico", "pé cúbico|pe cubico|pés cúbicos|pes cubicos|pé3|pe3|pé^3|pe^3|pé³|pe³|ft3|ft^3|ft³") + .put("Jarda cúbica", "jarda cúbica|jarda cubica|jardas cúbicas|jardas cubicas|yd3|yd^3|yd³") + .put("Hectolitro", "hectolitro|hectolitros|hl") + .put("Litro", "litro|litros|lts|l") + .put("Mililitro", "mililitro|mililitros|ml") + .put("Galão", "galão|galões|galao|galoes") + .put("Pint", "pinta|pintas|pinto|pintos|quartilho|quartilhos|pint|pints") + .put("Barril", "barril|barris|bbl") + .put("Onça líquida", "onça líquida|onca liquida|onças líquidas|oncas liquidas") + .build(); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Tonelada métrica", "tonelada métrica|tonelada metrica|toneladas métricas|toneladas metricas") + .put("Tonelada", "ton|tonelada|toneladas") + .put("Quilograma", "kg|quilograma|quilogramas|quilo|quilos|kilo|kilos") + .put("Hectograma", "hg|hectograma|hectogramas") + .put("Decagrama", "dag|decagrama|decagramas") + .put("Grama", "g|grama|gramas") + .put("Decigrama", "dg|decigrama|decigramas") + .put("Centigrama", "cg|centigrama|centigramas") + .put("Miligrama", "mg|miligrama|miligramas") + .put("Micrograma", "µg|ug|micrograma|microgramas") + .put("Nanograma", "ng|nanograma|nanogramas") + .put("Picograma", "pg|picograma|picogramas") + .put("Libra", "lb|libra|libras") + .put("Onça", "oz|onça|onca|onças|oncas") + .put("Grão", "grão|grao|grãos|graos|gr") + .put("Quilate", "ct|quilate|quilates") + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java new file mode 100644 index 000000000..198cdea86 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/resources/SpanishNumericWithUnit.java @@ -0,0 +1,517 @@ +// ------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +// ------------------------------------------------------------------------------ + +package com.microsoft.recognizers.text.numberwithunit.resources; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; + +import com.google.common.collect.ImmutableMap; + +public class SpanishNumericWithUnit { + + public static final ImmutableMap AgeSuffixList = ImmutableMap.builder() + .put("Año", "años|año") + .put("Mes", "meses|mes") + .put("Semana", "semanas|semana") + .put("Día", "dias|días|día|dia") + .build(); + + public static final List AmbiguousAgeUnitList = Arrays.asList("años", "año", "meses", "mes", "semanas", "semana", "dias", "días", "día", "dia"); + + public static final ImmutableMap AreaSuffixList = ImmutableMap.builder() + .put("Kilómetro cuadrado", "kilómetro cuadrado|kilómetros cuadrados|km2|km^2|km²") + .put("Hectómetro cuadrado", "hectómetro cuadrado|hectómetros cuadrados|hm2|hm^2|hm²|hectárea|hectáreas") + .put("Decámetro cuadrado", "decámetro cuadrado|decámetros cuadrados|dam2|dam^2|dam²|área|áreas") + .put("Metro cuadrado", "metro cuadrado|metros cuadrados|m2|m^2|m²") + .put("Decímetro cuadrado", "decímetro cuadrado|decímetros cuadrados|dm2|dm^2|dm²") + .put("Centímetro cuadrado", "centímetro cuadrado|centímetros cuadrados|cm2|cm^2|cm²") + .put("Milímetro cuadrado", "milímetro cuadrado|milímetros cuadrados|mm2|mm^2|mm²") + .put("Pulgada cuadrado", "pulgada cuadrada|pulgadas cuadradas") + .put("Pie cuadrado", "pie cuadrado|pies cuadrados|pie2|pie^2|pie²|ft2|ft^2|ft²") + .put("Yarda cuadrado", "yarda cuadrada|yardas cuadradas|yd2|yd^2|yd²") + .put("Acre", "acre|acres") + .build(); + + public static final List AreaAmbiguousValues = Arrays.asList("área", "áreas"); + + public static final ImmutableMap CurrencySuffixList = ImmutableMap.builder() + .put("Dólar", "dólar|dólares") + .put("Peso", "peso|pesos") + .put("Rublo", "rublo|rublos") + .put("Libra", "libra|libras") + .put("Florín", "florín|florines") + .put("Dinar", "dinar|dinares") + .put("Franco", "franco|francos") + .put("Rupia", "rupia|rupias") + .put("Escudo", "escudo|escudos") + .put("Chelín", "chelín|chelines") + .put("Lira", "lira|liras") + .put("Centavo", "centavo|centavos") + .put("Céntimo", "céntimo|céntimos") + .put("Centésimo", "centésimo|centésimos") + .put("Penique", "penique|peniques") + .put("Euro", "euro|euros|€|eur") + .put("Céntimo de Euro", "céntimo de euro|céntimos de euros") + .put("Dólar del Caribe Oriental", "dólar del caribe oriental|dólares del caribe oriental|ec$|xcd") + .put("Centavo del Caribe Oriental", "centavo del caribe oriental|centavos del caribe oriental") + .put("Franco CFA de África Occidental", "franco cfa de África occidental|francos cfa de África occidental|fcfa|xof") + .put("Céntimo de CFA de África Occidental", "céntimo de cfa de África occidental|céntimos de cfa de África occidental") + .put("Franco CFA de África Central", "franco cfa de África central|francos cfa de África central|xaf") + .put("Céntimo de CFA de África Central", "céntimo de cfa de África central|céntimos de cfa de África central") + .put("Apsar", "apsar|apsares") + .put("Afgani afgano", "afgani afgano|؋|afn|afganis|afgani") + .put("Pul", "pul|puls") + .put("Lek albanés", "lek|lekë|lekes|lek albanés") + .put("Qindarka", "qindarka|qindarkë|qindarkas") + .put("Kwanza angoleño", "kwanza angoleño|kwanzas angoleños|kwanza angoleños|kwanzas angoleño|kwanzas|aoa|kz") + .put("Cêntimo angoleño", "cêntimo angoleño|cêntimo|cêntimos") + .put("Florín antillano neerlandés", "florín antillano neerlandés|florínes antillano neerlandés|ƒ antillano neerlandés|ang|naƒ") + .put("Cent antillano neerlandés", "cent|centen") + .put("Riyal saudí", "riyal saudí|riyales saudí|sar") + .put("Halalá saudí", "halalá saudí|hallalah") + .put("Dinar argelino", "dinar argelino|dinares argelinos|dzd") + .put("Céntimo argelino", "centimo argelino|centimos argelinos") + .put("Peso argentino", "peso argentino|pesos argentinos|ar$|ars") + .put("Centavo argentino", "centavo argentino|centavos argentinos|ctvo.|ctvos.") + .put("Dram armenio", "dram armenio|dram armenios|dram|դր.") + .put("Luma armenio", "luma armenio|luma armenios") + .put("Florín arubeño", "florín arubeño|florines arubeños|ƒ arubeños|aƒ|awg") + .put("Yotin arubeño", "yotin arubeño|yotines arubeños") + .put("Dólar australiano", "dólar australiano|dólares australianos|a$|aud") + .put("Centavo australiano", "centavo australiano|centavos australianos") + .put("Manat azerí", "manat azerí|man|azn") + .put("Qəpik azerí", "qəpik azerí|qəpik") + .put("Dólar bahameño", "dólar bahameño|dólares bahameños|b$|bsd") + .put("Centavo bahameño", "centavo bahameño|centavos bahameños") + .put("Dinar bahreiní", "dinar bahreiní|dinares bahreinies|bhd") + .put("Fil bahreiní", "fil bahreiní|fils bahreinies") + .put("Taka bangladeshí", "taka bangladeshí|takas bangladeshí|bdt") + .put("Poisha bangladeshí", "poisha bangladeshí|poishas bangladeshí") + .put("Dólar de Barbados", "dólar de barbados|dólares de barbados|bbd") + .put("Centavo de Barbados", "centavo de barbados|centavos de barbados") + .put("Dólar beliceño", "dólar beliceño|dólares beliceños|bz$|bzd") + .put("Centavo beliceño", "centavo beliceño|centavos beliceños") + .put("Dólar bermudeño", "dólar bermudeño|dólares bermudeños|bd$|bmd") + .put("Centavo bermudeño", "centavo bermudeño|centavos bermudeños") + .put("Rublo bielorruso", "rublo bielorruso|rublos bielorrusos|byr") + .put("Kópek bielorruso", "kópek bielorruso|kópeks bielorrusos|kap") + .put("Kyat birmano", "kyat birmano|kyats birmanos|mmk") + .put("Pya birmano", "pya birmano|pyas birmanos") + .put("Boliviano", "boliviano|bolivianos|bob|bs") + .put("Centésimo Boliviano", "centésimo boliviano|centésimos bolivianos") + .put("Marco bosnioherzegovino", "marco convertible|marco bosnioherzegovino|marcos convertibles|marcos bosnioherzegovinos|bam") + .put("Feningas bosnioherzegovino", "feninga convertible|feninga bosnioherzegovina|feningas convertibles") + .put("Pula", "pula|bwp") + .put("Thebe", "thebe") + .put("Real brasileño", "real brasileño|reales brasileños|r$|brl") + .put("Centavo brasileño", "centavo brasileño|centavos brasileños") + .put("Dólar de Brunéi", "dólar de brunei|dólares de brunéi|bnd") + .put("Sen de Brunéi", "sen|sen de brunéi") + .put("Lev búlgaro", "lev búlgaro|leva búlgaros|lv|bgn") + .put("Stotinki búlgaro", "stotinka búlgaro|stotinki búlgaros") + .put("Franco de Burundi", "franco de burundi|francos de burundi|fbu|fib") + .put("Céntimo Burundi", "céntimo burundi|céntimos burundies") + .put("Ngultrum butanés", "ngultrum butanés|ngultrum butaneses|btn") + .put("Chetrum butanés", "chetrum butanés|chetrum butaneses") + .put("Escudo caboverdiano", "escudo caboverdiano|escudos caboverdianos|cve") + .put("Riel camboyano", "riel camboyano|rieles camboyanos|khr") + .put("Dólar canadiense", "dólar canadiense|dólares canadienses|c$|cad") + .put("Centavo canadiense", "centavo canadiense|centavos canadienses") + .put("Peso chileno", "peso chileno|pesos chilenos|cpl") + .put("Yuan chino", "yuan chino|yuanes chinos|yuan|yuanes|renminbi|rmb|cny|¥") + .put("Peso colombiano", "peso colombiano|pesos colombianos|cop|col$") + .put("Centavo colombiano", "centavo colombiano|centavos colombianos") + .put("Franco comorano", "franco comorano|francos comoranos|kmf|₣") + .put("Franco congoleño", "franco congoleño|francos congoleños|cdf") + .put("Céntimo congoleño", "céntimo congoleño|céntimos congoleños") + .put("Won norcoreano", "won norcoreano|wŏn norcoreano|wŏn norcoreanos|kpw") + .put("Chon norcoreano", "chon norcoreano|chŏn norcoreano|chŏn norcoreanos|chon norcoreanos") + .put("Won surcoreano", "wŏn surcoreano|won surcoreano|wŏnes surcoreanos|wones surcoreanos|krw") + .put("Chon surcoreano", "chon surcoreano|chŏn surcoreano|chŏn surcoreanos|chon surcoreanos") + .put("Colón costarricense", "colón costarricense|colones costarricenses|crc") + .put("Kuna croata", "kuna croata|kuna croatas|hrk") + .put("Lipa croata", "lipa croata|lipa croatas") + .put("Peso cubano", "peso cubano|pesos cubanos|cup") + .put("Peso cubano convertible", "peso cubano convertible|pesos cubanos convertible|cuc") + .put("Corona danesa", "corona danesa|coronas danesas|dkk") + .put("Libra egipcia", "libra egipcia|libras egipcias|egp|l.e.") + .put("Piastra egipcia", "piastra egipcia|piastras egipcias") + .put("Colón salvadoreño", "colón salvadoreño|colones salvadoreños|svc") + .put("Dirham de los Emiratos Árabes Unidos", "dirham|dirhams|dirham de los emiratos Árabes unidos|aed|dhs") + .put("Nakfa", "nakfa|nfk|ern") + .put("Céntimo de Nakfa", "céntimo de nakfa|céntimos de nakfa") + .put("Peseta", "peseta|pesetas|pts.|ptas.|esp") + .put("Dólar estadounidense", "dólar estadounidense|dólares estadounidenses|usd|u$d|us$") + .put("Corona estonia", "corona estonia|coronas estonias|eek") + .put("Senti estonia", "senti estonia|senti estonias") + .put("Birr etíope", "birr etíope|birr etíopes|br|etb") + .put("Santim etíope", "santim etíope|santim etíopes") + .put("Peso filipino", "peso filipino|pesos filipinos|php") + .put("Marco finlandés", "marco finlandés|marcos finlandeses") + .put("Dólar fiyiano", "dólar fiyiano|dólares fiyianos|fj$|fjd") + .put("Centavo fiyiano", "centavo fiyiano|centavos fiyianos") + .put("Dalasi", "dalasi|gmd") + .put("Bututs", "butut|bututs") + .put("Lari georgiano", "lari georgiano|lari georgianos|gel") + .put("Tetri georgiano", "tetri georgiano|tetri georgianos") + .put("Cedi", "cedi|ghs|gh₵") + .put("Pesewa", "pesewa") + .put("Libra gibraltareña", "libra gibraltareña|libras gibraltareñas|gip") + .put("Penique gibraltareña", "penique gibraltareña|peniques gibraltareñas") + .put("Quetzal guatemalteco", "quetzal guatemalteco|quetzales guatemaltecos|quetzal|quetzales|gtq") + .put("Centavo guatemalteco", "centavo guatemalteco|centavos guatemaltecos") + .put("Libra de Guernsey", "libra de guernsey|libras de guernsey|ggp") + .put("Penique de Guernsey", "penique de guernsey|peniques de guernsey") + .put("Franco guineano", "franco guineano|francos guineanos|gnf|fg") + .put("Céntimo guineano", "céntimo guineano|céntimos guineanos") + .put("Dólar guyanés", "dólar guyanés|dólares guyaneses|gyd|gy") + .put("Gourde haitiano", "gourde haitiano|gourde haitianos|htg") + .put("Céntimo haitiano", "céntimo haitiano|céntimos haitianos") + .put("Lempira hondureño", "lempira hondureño|lempira hondureños|hnl") + .put("Centavo hondureño", "centavo hondureño|centavos hondureño") + .put("Dólar de Hong Kong", "dólar de hong kong|dólares de hong kong|hk$|hkd") + .put("Forinto húngaro", "forinto húngaro|forinto húngaros|huf") + .put("Rupia india", "rupia india|rupias indias|inr") + .put("Paisa india", "paisa india|paise indias") + .put("Rupia indonesia", "rupia indonesia|rupias indonesias|idr") + .put("Sen indonesia", "sen indonesia|sen indonesias") + .put("Rial iraní", "rial iraní|rial iranies|irr") + .put("Dinar iraquí", "dinar iraquí|dinares iraquies|iqd") + .put("Fil iraquí", "fil iraquí|fils iraquies") + .put("Libra manesa", "libra manesa|libras manesas|imp") + .put("Penique manes", "penique manes|peniques maneses") + .put("Corona islandesa", "corona islandesa|coronas islandesas|isk|íkr") + .put("Aurar islandes", "aurar islandes|aurar islandeses") + .put("Dólar de las Islas Caimán", "dólar de las islas caimán|dólares de las islas caimán|ci$|kyd") + .put("Dólar de las Islas Cook", "dólar de las islas cook|dólares de las islas cook") + .put("Corona feroesa", "corona feroesa|coronas feroesas|fkr") + .put("Libra malvinense", "libra malvinense|libras malvinenses|fk£|fkp") + .put("Dólar de las Islas Salomón", "dólar de las islas salomón|dólares de las islas salomón|sbd") + .put("Nuevo shéquel", "nuevo shéquel|nuevos shéquel|ils") + .put("Agorot", "agorot") + .put("Dólar jamaiquino", "dólar jamaiquino|dólares jamaiquinos|j$|ja$|jmd") + .put("Yen", "yen|yenes|jpy") + .put("Libra de Jersey", "libra de jersey|libras de jersey|jep") + .put("Dinar jordano", "dinar jordano|dinares jordanos|jd|jod") + .put("Piastra jordano", "piastra jordano|piastras jordanos") + .put("Tenge kazajo", "tenge|tenge kazajo|kzt") + .put("Chelín keniano", "chelín keniano|chelines kenianos|ksh|kes") + .put("Som kirguís", "som kirguís|kgs") + .put("Tyiyn", "tyiyn") + .put("Dólar de Kiribati", "dólar de kiribati|dólares de kiribati") + .put("Dinar kuwaití", "dinar kuwaití|dinares kuwaití") + .put("Kip laosiano", "kip|kip laosiano|kip laosianos|lak") + .put("Att laosiano", "att|att laosiano|att laosianos") + .put("Loti", "loti|maloti|lsl") + .put("Sente", "sente|lisente") + .put("Libra libanesa", "libra libanesa|libras libanesas|lbp") + .put("Dólar liberiano", "dólar liberiano|dólares liberianos|l$|lrd") + .put("Dinar libio", "dinar libio|dinares libios|ld|lyd") + .put("Dirham libio", "dirham libio|dirhams libios") + .put("Litas lituana", "litas lituana|litai lituanas|ltl") + .put("Pataca macaense", "pataca macaense|patacas macaenses|mop$|mop") + .put("Avo macaense", "avo macaense|avos macaenses") + .put("Ho macaense", "ho macaense|ho macaenses") + .put("Denar macedonio", "denar macedonio|denare macedonios|den|mkd") + .put("Deni macedonio", "deni macedonio|deni macedonios") + .put("Ariary malgache", "ariary malgache|ariary malgaches|mga") + .put("Iraimbilanja malgache", "iraimbilanja malgache|iraimbilanja malgaches") + .put("Ringgit malayo", "ringgit malayo|ringgit malayos|rm|myr") + .put("Sen malayo", "sen malayo|sen malayos") + .put("Kwacha malauí", "kwacha malauí|mk|mwk") + .put("Támbala malauí", "támbala malauí") + .put("Rupia de Maldivas", "rupia de maldivas|rupias de maldivas|mvr") + .put("Dirham marroquí", "dirham marroquí|dirhams marroquies|mad") + .put("Rupia de Mauricio", "rupia de Mauricio|rupias de Mauricio|mur") + .put("Uguiya", "uguiya|uguiyas|mro") + .put("Jum", "jum|jums") + .put("Peso mexicano", "peso mexicano|pesos mexicanos|mxn") + .put("Centavo mexicano", "centavo mexicano|centavos mexicanos") + .put("Leu moldavo", "leu moldavo|lei moldavos|mdl") + .put("Ban moldavo", "ban moldavo|bani moldavos") + .put("Tugrik mongol", "tugrik mongol|tugrik|tugrik mongoles|tug|mnt") + .put("Metical mozambiqueño", "metical|metical mozambiqueño|meticales|meticales mozambiqueños|mtn|mzn") + .put("Dram de Nagorno Karabaj", "dram de nagorno karabaj|drams de nagorno karabaj") + .put("Luma de Nagorno Karabaj", "luma de nagorno karabaj") + .put("Dólar namibio", "dólar namibio|dólares namibios|n$|nad") + .put("Centavo namibio", "centavo namibio|centavos namibios") + .put("Rupia nepalí", "rupia nepalí|rupias nepalies|npr") + .put("Paisa nepalí", "paisa nepalí|paisas nepalies") + .put("Córdoba nicaragüense", "córdoba nicaragüense|córdobas nicaragüenses|nio") + .put("Centavo nicaragüense", "centavo nicaragüense|centavos nicaragüenses") + .put("Naira", "naira|ngn") + .put("Kobo", "kobo") + .put("Corona noruega", "corona noruega|coronas noruegas|nok") + .put("Franco CFP", "franco cfp|francos cfp|xpf") + .put("Dólar neozelandés", "dólar neozelandés|dólares neozelandeses|dólar de nueva zelanda|dólares de nueva zelanda|nz$|nzd") + .put("Centavo neozelandés", "centavo neozelandés|centavo de nueva zelanda|centavos de nueva zelanda|centavos neozelandeses") + .put("Rial omaní", "rial omaní|riales omanies|omr") + .put("Baisa omaní", "baisa omaní|baisa omanies") + .put("Florín neerlandés", "florín neerlandés|florines neerlandeses|nlg") + .put("Rupia pakistaní", "rupia pakistaní|rupias pakistanies|pkr") + .put("Paisa pakistaní", "paisa pakistaní|paisas pakistanies") + .put("Balboa panameño", "balboa panameño|balboa panameños|pab") + .put("Centésimo panameño", "centésimo panameño|centésimos panameños") + .put("Kina", "kina|pkg|pgk") + .put("Toea", "toea") + .put("Guaraní", "guaraní|guaranies|gs|pyg") + .put("Sol", "sol|soles|nuevo sol|pen|s#.") + .put("Céntimo de sol", "céntimo de sol|céntimos de sol") + .put("Złoty", "złoty|esloti|eslotis|zł|pln") + .put("Groszy", "groszy") + .put("Riyal qatarí", "riyal qatarí|riyal qataries|qr|qar") + .put("Dirham qatarí", "dirham qatarí|dirhams qataries") + .put("Libra esterlina", "libra esterlina|libras esterlinas|gbp") + .put("Corona checa", "corona checa|coronas checas|kc|czk") + .put("Peso dominicano", "peso dominicano|pesos dominicanos|rd$|dop") + .put("Centavo dominicano", "centavo dominicano|centavos dominicanos") + .put("Franco ruandés", "franco ruandés|francos ruandeses|rf|rwf") + .put("Céntimo ruandés", "céntimo ruandés|céntimos ruandeses") + .put("Leu rumano", "leu rumano|lei rumanos|ron") + .put("Ban rumano", "ban rumano|bani rumanos") + .put("Rublo ruso", "rublo ruso|rublos rusos|rub") + .put("Kopek ruso", "kopek ruso|kopeks rusos") + .put("Tala", "tala|tālā|ws$|sat|wst") + .put("Sene", "sene") + .put("Libra de Santa Helena", "libra de santa helena|libras de santa helena|shp") + .put("Penique de Santa Helena", "penique de santa helena|peniques de santa helena") + .put("Dobra", "dobra") + .put("Dinar serbio", "dinar serbio|dinares serbios|rsd") + .put("Para serbio", "para serbio|para serbios") + .put("Rupia de Seychelles", "rupia de seychelles|rupias de seychelles|scr") + .put("Centavo de Seychelles", "centavo de seychelles|centavos de seychelles") + .put("Leone", "leone|le|sll") + .put("Dólar de Singapur", "dólar de singapur|dólares de singapur|sgb") + .put("Centavo de Singapur", "centavo de Singapur|centavos de Singapur") + .put("Libra siria", "libra siria|libras sirias|s£|syp") + .put("Piastra siria", "piastra siria|piastras sirias") + .put("Chelín somalí", "chelín somalí|chelines somalies|sos") + .put("Centavo somalí", "centavo somalí|centavos somalies") + .put("Chelín somalilandés", "chelín somalilandés|chelines somalilandeses") + .put("Centavo somalilandés", "centavo somalilandés|centavos somalilandeses") + .put("Rupia de Sri Lanka", "rupia de Sri Lanka|rupias de Sri Lanka|lkr") + .put("Céntimo de Sri Lanka", "céntimo de Sri Lanka|céntimos de Sri Lanka") + .put("Lilangeni", "lilangeni|emalangeni|szl") + .put("Rand sudafricano", "rand|rand sudafricano|zar") + .put("Libra sudanesa", "libra sudanesa|libras sudanesas|sdg") + .put("Piastra sudanesa", "piastra sudanesa|piastras sudanesas") + .put("Libra sursudanesa", "libra sursudanesa|libras sursudanesa|ssp") + .put("Piastra sursudanesa", "piastra sursudanesa|piastras sursudanesas") + .put("Corona sueca", "corona sueca|coronas suecas|sek") + .put("Franco suizo", "franco suizo|francos suizos|sfr|chf") + .put("Rappen suizo", "rappen suizo|rappens suizos") + .put("Dólar surinamés", "óolar surinamés|dólares surinameses|srd") + .put("Centavo surinamés", "centavo surinamés|centavos surinamés") + .put("Baht tailandés", "baht tailandés|baht tailandeses|thb") + .put("Satang tailandés", "satang tailandés|satang tailandeses") + .put("Nuevo dólar taiwanés", "nuevo dólar taiwanés|dólar taiwanés|dólares taiwaneses|twd") + .put("Centavo taiwanés", "centavo taiwanés|centavos taiwaneses") + .put("Chelín tanzano", "chelín tanzano|chelines tanzanos|tzs") + .put("Centavo tanzano", "centavo tanzano|centavos tanzanos") + .put("Somoni tayiko", "somoni tayiko|somoni|tjs") + .put("Diram", "diram|dirams") + .put("Paʻanga", "dólar tongano|dólares tonganos|paʻanga|pa'anga|top") + .put("Seniti", "seniti") + .put("Rublo de Transnistria", "rublo de transnistria|rublos de transnistria") + .put("Kopek de Transnistria", "kopek de transnistria|kopeks de transnistria") + .put("Dólar trinitense", "dólar trinitense|dólares trinitenses|ttd") + .put("Centavo trinitense", "centavo trinitense|centavos trinitenses") + .put("Dinar tunecino", "dinar tunecino|dinares tunecinos|tnd") + .put("Millime tunecino", "millime tunecino|millimes tunecinos") + .put("Lira turca", "lira turca|liras turcas|try") + .put("Kuruş turca", "kuruş turca|kuruş turcas") + .put("Manat turkmeno", "manat turkmeno|manat turkmenos|tmt") + .put("Tennesi turkmeno", "tennesi turkmeno|tenge turkmeno") + .put("Dólar tuvaluano", "dólar tuvaluano|dólares tuvaluanos") + .put("Centavo tuvaluano", "centavo tuvaluano|centavos tuvaluanos") + .put("Grivna", "grivna|grivnas|uah") + .put("Kopiyka", "kopiyka|kópeks") + .put("Chelín ugandés", "chelín ugandés|chelines ugandeses|ugx") + .put("Centavo ugandés", "centavo ugandés|centavos ugandeses") + .put("Peso uruguayo", "peso uruguayo|pesos uruguayos|uyu") + .put("Centésimo uruguayo", "centésimo uruguayo|centésimos uruguayos") + .put("Som uzbeko", "som uzbeko|som uzbekos|uzs") + .put("Tiyin uzbeko", "tiyin uzbeko|tiyin uzbekos") + .put("Vatu", "vatu|vuv") + .put("Bolívar fuerte", "bolívar fuerte|bolívar|bolívares|vef") + .put("Céntimo de bolívar", "céntimo de bolívar|céntimos de bolívar") + .put("Đồng vietnamita", "Đồng vietnamita|dong vietnamita|dong vietnamitas|vnd") + .put("Hào vietnamita", "Hào vietnamita|hao vietnamita|hao vietnamitas") + .put("Rial yemení", "rial yemení|riales yemenies|yer") + .put("Fils yemení", "fils yemení|fils yemenies") + .put("Franco yibutiano", "franco yibutiano|francos yibutianos|djf") + .put("Dinar yugoslavo", "dinar yugoslavo|dinares yugoslavos|yud") + .put("Kwacha zambiano", "kwacha zambiano|kwacha zambianos|zmw") + .put("Ngwee zambiano", "ngwee zambiano|ngwee zambianos") + .build(); + + public static final String CompoundUnitConnectorRegex = "(?[^.])"; + + public static final ImmutableMap CurrencyPrefixList = ImmutableMap.builder() + .put("Dobra", "db|std") + .put("Dólar", "$") + .put("Dólar estadounidense", "us$|u$d|usd") + .put("Dólar del Caribe Oriental", "ec$|xcd") + .put("Dólar australiano", "a$|aud") + .put("Dólar bahameño", "b$|bsd") + .put("Dólar de Barbados", "bds$|bbd") + .put("Dólar beliceño", "bz$|bzd") + .put("Dólar bermudeño", "bd$|bmd") + .put("Dólar de Brunéi", "brunéi $|bnd") + .put("Dólar de Singapur", "s$|sgd") + .put("Dólar canadiense", "c$|can$|cad") + .put("Dólar de las Islas Caimán", "ci$|kyd") + .put("Dólar neozelandés", "nz$|nzd") + .put("Dólar fiyiano", "fj$|fjd") + .put("Dólar guyanés", "gy$|gyd") + .put("Dólar de Hong Kong", "hk$|hkd") + .put("Dólar jamaiquino", "j$|ja$|jmd") + .put("Dólar liberiano", "l$|lrd") + .put("Dólar namibio", "n$|nad") + .put("Dólar de las Islas Salomón", "si$|sbd") + .put("Nuevo dólar taiwanés", "nt$|twd") + .put("Real brasileño", "r$|brl") + .put("Guaraní", "₲|gs.|pyg") + .put("Dólar trinitense", "tt$|ttd") + .put("Yuan chino", "¥|cny|rmb") + .put("Yen", "¥|jpy") + .put("Euro", "€|eur") + .put("Florín", "ƒ") + .put("Libra", "£") + .put("Colón costarricense", "₡") + .put("Lira turca", "₺") + .build(); + + public static final List AmbiguousCurrencyUnitList = Arrays.asList("le", "db", "std"); + + public static final ImmutableMap InformationSuffixList = ImmutableMap.builder() + .put("bit", "bit|bits") + .put("kilobit", "kilobit|kilobits|kb|kbit") + .put("megabit", "megabit|megabits|Mb|Mbit") + .put("gigabit", "gigabit|gigabits|Gb|Gbit") + .put("terabit", "terabit|terabits|Tb|Tbit") + .put("petabit", "petabit|petabits|Pb|Pbit") + .put("kibibit", "kibibit|kibibits|kib|kibit") + .put("mebibit", "mebibit|mebibits|Mib|Mibit") + .put("gibibit", "gibibit|gibibits|Gib|Gibit") + .put("tebibit", "tebibit|tebibits|Tib|Tibit") + .put("pebibit", "pebibit|pebibits|Pib|Pibit") + .put("byte", "byte|bytes") + .put("kilobyte", "kilobyte|kilobytes|kB|kByte") + .put("megabyte", "megabyte|megabytes|MB|MByte") + .put("gigabyte", "gigabyte|gigabytes|GB|GByte") + .put("terabyte", "terabyte|terabytes|TB|TByte") + .put("petabyte", "petabyte|petabytes|PB|PByte") + .put("kibibyte", "kibibyte|kibibytes|kiB|kiByte") + .put("mebibyte", "mebibyte|mebibytes|MiB|MiByte") + .put("gibibyte", "gibibyte|gibibytes|GiB|GiByte") + .put("tebibyte", "tebibyte|tebibytes|TiB|TiByte") + .put("pebibyte", "pebibyte|pebibytes|PiB|PiByte") + .build(); + + public static final List AmbiguousDimensionUnitList = Arrays.asList("al", "mi", "área", "áreas", "pie", "pies", "\""); + + public static final ImmutableMap LengthSuffixList = ImmutableMap.builder() + .put("Kilómetro", "km|kilometro|kilómetro|kilometros|kilómetros") + .put("Hectómetro", "hm|hectometro|hectómetro|hectometros|hectómetros") + .put("Decámetro", "decametro|decámetro|decametros|decámetros|dam") + .put("Metro", "m|m.|metro|metros") + .put("Decímetro", "dm|decimetro|decímetro|decimetros|decímetros") + .put("Centímetro", "cm|centimetro|centímetro|centimetros|centimetros") + .put("Milímetro", "mm|milimetro|milímetro|milimetros|milímetros") + .put("Micrómetro", "µm|um|micrometro|micrómetro|micrometros|micrómetros|micrón|micrónes") + .put("Nanómetro", "nm|nanometro|nanómetro|nanometros|nanómetros") + .put("Picómetro", "pm|picometro|picómetro|picometros|picómetros") + .put("Milla", "mi|milla|millas") + .put("Yarda", "yd|yarda|yardas") + .put("Pulgada", "pulgada|pulgadas|\"") + .put("Pie", "pie|pies|ft") + .put("Año luz", "año luz|años luz|al") + .build(); + + public static final List AmbiguousLengthUnitList = Arrays.asList("mi", "área", "áreas", "\""); + + public static final String BuildPrefix = "(?<=(\\s|^|\\P{L}))"; + + public static final String BuildSuffix = "(?=(\\s|\\P{L}|$))"; + + public static final String ConnectorToken = "de"; + + public static final ImmutableMap SpeedSuffixList = ImmutableMap.builder() + .put("Metro por segundo", "metro/segundo|m/s|metro por segundo|metros por segundo|metros por segundos") + .put("Kilómetro por hora", "km/h|kilómetro por hora|kilometro por hora|kilómetros por hora|kilometros por hora|kilómetro/hora|kilometro/hora|kilómetros/hora|kilometros/hora") + .put("Kilómetro por minuto", "km/min|kilómetro por minuto|kilometro por minuto|kilómetros por minuto|kilometros por minuto|kilómetro/minuto|kilometro/minuto|kilómetros/minuto|kilometros/minuto") + .put("Kilómetro por segundo", "km/seg|kilómetro por segundo|kilometro por segundo|kilómetros por segundo|kilometros por segundo|kilómetro/segundo|kilometro/segundo|kilómetros/segundo|kilometros/segundo") + .put("Milla por hora", "mph|milla por hora|mi/h|milla/hora|millas/hora|millas por hora") + .put("Nudo", "kt|nudo|nudos|kn") + .put("Pie por segundo", "ft/s|pie/s|ft/seg|pie/seg|pie por segundo|pies por segundo") + .put("Pie por minuto", "ft/min|pie/min|pie por minuto|pies por minuto") + .put("Yarda por minuto", "yardas por minuto|yardas/minuto|yardas/min") + .put("Yarda por segundo", "yardas por segundo|yardas/segundo|yardas/seg") + .build(); + + public static final List AmbiguousSpeedUnitList = Arrays.asList("nudo", "nudos"); + + public static final ImmutableMap TemperatureSuffixList = ImmutableMap.builder() + .put("Kelvin", "k|K|kelvin") + .put("Rankine", "r|rankine") + .put("Grado Celsius", "°c|grados c|grado celsius|grados celsius|celsius|grado centígrado|grados centígrados|centígrado|centígrados") + .put("Grado Fahrenheit", "°f|grados f|grado fahrenheit|grados fahrenheit|fahrenheit") + .put("Grado Réaumur", "°r|°re|grados r|grado réaumur|grados réaumur|réaumur") + .put("Grado Delisle", "°d|grados d|grado delisle|grados delisle|delisle") + .put("Grado", "°|grados|grado") + .build(); + + public static final ImmutableMap VolumeSuffixList = ImmutableMap.builder() + .put("Kilómetro cúbico", "kilómetro cúbico|kilómetros cúbico|km3|km^3|km³") + .put("Hectómetro cúbico", "hectómetro cúbico|hectómetros cúbico|hm3|hm^3|hm³") + .put("Decámetro cúbico", "decámetro cúbico|decámetros cúbico|dam3|dam^3|dam³") + .put("Metro cúbico", "metro cúbico|metros cúbico|m3|m^3|m³") + .put("Decímetro cúbico", "decímetro cúbico|decímetros cúbico|dm3|dm^3|dm³") + .put("Centímetro cúbico", "centímetro cúbico|centímetros cúbico|cc|cm3|cm^3|cm³") + .put("Milímetro cúbico", "milímetro cúbico|milímetros cúbico|mm3|mm^3|mm³") + .put("Pulgada cúbica", "pulgada cúbica|pulgadas cúbicas") + .put("Pie cúbico", "pie cúbico|pies cúbicos|pie3|pie^3|pie³|ft3|ft^3|ft³") + .put("Yarda cúbica", "yarda cúbica|yardas cúbicas|yd3|yd^3|yd³") + .put("Hectolitro", "hectolitro|hectolitros|hl") + .put("Litro", "litro|litros|lts|l") + .put("Mililitro", "mililitro|mililitros|ml") + .put("Galón", "galón|galones") + .put("Pinta", "pinta|pintas") + .put("Barril", "barril|barriles|bbl") + .put("Onza líquida", "onza líquida|onzas líquidas") + .build(); + + public static final ImmutableMap WeightSuffixList = ImmutableMap.builder() + .put("Tonelada métrica", "tonelada métrica|toneladas métricas") + .put("Tonelada", "ton|tonelada|toneladas") + .put("Kilogramo", "kg|kilogramo|kilogramos") + .put("Hectogramo", "hg|hectogramo|hectogramos") + .put("Decagramo", "dag|decagramo|decagramos") + .put("Gramo", "g|gr|gramo|gramos") + .put("Decigramo", "dg|decigramo|decigramos") + .put("Centigramo", "cg|centigramo|centigramos") + .put("Miligramo", "mg|miligramo|miligramos") + .put("Microgramo", "µg|ug|microgramo|microgramos") + .put("Nanogramo", "ng|nanogramo|nanogramos") + .put("Picogramo", "pg|picogramo|picogramos") + .put("Libra", "lb|libra|libras") + .put("Onza", "oz|onza|onzas") + .put("Grano", "grano|granos") + .put("Quilate", "ct|quilate|quilates") + .build(); + + public static final ImmutableMap AmbiguityFiltersDict = ImmutableMap.builder() + .put("null", "null") + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java new file mode 100644 index 000000000..3255c1639 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AgeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AgeExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public AgeExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AgeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AGE; + } + + @Override + public Map getSuffixList() { + return AgeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousAgeUnitList; + } + + public static Map AgeSuffixList = SpanishNumericWithUnit.AgeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java new file mode 100644 index 000000000..161395874 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/AreaExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class AreaExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public AreaExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AreaExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_AREA; + } + + @Override + public Map getSuffixList() { + return AreaSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map AreaSuffixList = SpanishNumericWithUnit.AreaSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java new file mode 100644 index 000000000..2de60c673 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/CurrencyExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.List; +import java.util.Map; + +public class CurrencyExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public CurrencyExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public CurrencyExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_CURRENCY; + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousCurrencyUnitList; + } + + @Override + public Map getSuffixList() { + return CurrencySuffixList; + } + + @Override + public Map getPrefixList() { + return CurrencyPrefixList; + } + + public static Map CurrencySuffixList = SpanishNumericWithUnit.CurrencySuffixList; + public static Map CurrencyPrefixList = SpanishNumericWithUnit.CurrencyPrefixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java new file mode 100644 index 000000000..7df9fe163 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/DimensionExtractorConfiguration.java @@ -0,0 +1,51 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class DimensionExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public DimensionExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public DimensionExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_DIMENSION; + } + + @Override + public Map getSuffixList() { + return DimensionSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map DimensionSuffixList = new ImmutableMap.Builder() + .putAll(SpanishNumericWithUnit.InformationSuffixList) + .putAll(AreaExtractorConfiguration.AreaSuffixList) + .putAll(LengthExtractorConfiguration.LengthSuffixList) + .putAll(SpeedExtractorConfiguration.SpeedSuffixList) + .putAll(VolumeExtractorConfiguration.VolumeSuffixList) + .putAll(WeightExtractorConfiguration.WeightSuffixList) + .build(); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java new file mode 100644 index 000000000..734dd420d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/LengthExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class LengthExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public LengthExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public LengthExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_LENGTH; + } + + @Override + public Map getSuffixList() { + return LengthSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousLengthUnitList; + } + + public static Map LengthSuffixList = SpanishNumericWithUnit.LengthSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java new file mode 100644 index 000000000..8043d7368 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpanishNumberWithUnitExtractorConfiguration.java @@ -0,0 +1,76 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.ExtractResult; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor; +import com.microsoft.recognizers.text.numberwithunit.extractors.INumberWithUnitExtractorConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; +import com.microsoft.recognizers.text.utilities.DefinitionLoader; + +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class SpanishNumberWithUnitExtractorConfiguration implements INumberWithUnitExtractorConfiguration { + + private final CultureInfo cultureInfo; + private final IExtractor unitNumExtractor; + private final Pattern compoundUnitConnectorRegex; + private Map ambiguityFiltersDict; + + protected SpanishNumberWithUnitExtractorConfiguration(CultureInfo cultureInfo) { + this.cultureInfo = cultureInfo; + + this.unitNumExtractor = NumberExtractor.getInstance(NumberMode.Unit);; + this.compoundUnitConnectorRegex = + Pattern.compile(SpanishNumericWithUnit.CompoundUnitConnectorRegex, Pattern.CASE_INSENSITIVE | Pattern.UNICODE_CHARACTER_CLASS); + + this.ambiguityFiltersDict = DefinitionLoader.loadAmbiguityFilters(SpanishNumericWithUnit.AmbiguityFiltersDict); + } + + public CultureInfo getCultureInfo() { + return this.cultureInfo; + } + + public IExtractor getUnitNumExtractor() { + return this.unitNumExtractor; + } + + public String getBuildPrefix() { + return SpanishNumericWithUnit.BuildPrefix; + } + + public String getBuildSuffix() { + return SpanishNumericWithUnit.BuildSuffix; + } + + public String getConnectorToken() { + return SpanishNumericWithUnit.ConnectorToken; + } + + public Pattern getCompoundUnitConnectorRegex() { + return this.compoundUnitConnectorRegex; + } + + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return null; + } + + public abstract String getExtractType(); + + public abstract Map getSuffixList(); + + public abstract Map getPrefixList(); + + public abstract List getAmbiguousUnitList(); + + public Map getAmbiguityFiltersDict() { + return ambiguityFiltersDict; + } + + public List expandHalfSuffix(String source, List result, List numbers) { + return result; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java new file mode 100644 index 000000000..3610df8eb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/SpeedExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class SpeedExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public SpeedExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public SpeedExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_SPEED; + } + + @Override + public Map getSuffixList() { + return SpeedSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map SpeedSuffixList = SpanishNumericWithUnit.SpeedSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java new file mode 100644 index 000000000..9f8016357 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/TemperatureExtractorConfiguration.java @@ -0,0 +1,56 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.BaseUnits; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; + +public class TemperatureExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + private final Pattern ambiguousUnitNumberMultiplierRegex; + + public TemperatureExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public TemperatureExtractorConfiguration(CultureInfo ci) { + super(ci); + + this.ambiguousUnitNumberMultiplierRegex = + Pattern.compile(BaseUnits.AmbiguousUnitNumberMultiplierRegex, Pattern.UNICODE_CHARACTER_CLASS); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_TEMPERATURE; + } + + @Override + public Map getSuffixList() { + return TemperatureSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public Pattern getAmbiguousUnitNumberMultiplierRegex() { + return this.ambiguousUnitNumberMultiplierRegex; + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map TemperatureSuffixList = new HashMap(SpanishNumericWithUnit.TemperatureSuffixList); +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java new file mode 100644 index 000000000..5b0b85325 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/VolumeExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class VolumeExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public VolumeExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public VolumeExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_VOLUME; + } + + @Override + public Map getSuffixList() { + return VolumeSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return Collections.emptyList(); + } + + public static Map VolumeSuffixList = SpanishNumericWithUnit.VolumeSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java new file mode 100644 index 000000000..89dfa2e62 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/extractors/WeightExtractorConfiguration.java @@ -0,0 +1,43 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.extractors; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.Constants; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class WeightExtractorConfiguration extends SpanishNumberWithUnitExtractorConfiguration { + + public WeightExtractorConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public WeightExtractorConfiguration(CultureInfo ci) { + super(ci); + } + + @Override + public String getExtractType() { + return Constants.SYS_UNIT_WEIGHT; + } + + @Override + public Map getSuffixList() { + return WeightSuffixList; + } + + @Override + public Map getPrefixList() { + return Collections.emptyMap(); + } + + @Override + public List getAmbiguousUnitList() { + return SpanishNumericWithUnit.AmbiguousDimensionUnitList; + } + + public static Map WeightSuffixList = SpanishNumericWithUnit.WeightSuffixList; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java new file mode 100644 index 000000000..12b25d8dd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AgeParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AgeExtractorConfiguration; + +public class AgeParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public AgeParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AgeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AgeExtractorConfiguration.AgeSuffixList); + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java new file mode 100644 index 000000000..6ca384127 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/AreaParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.AreaExtractorConfiguration; + +public class AreaParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public AreaParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public AreaParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(AreaExtractorConfiguration.AreaSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java new file mode 100644 index 000000000..0bad7646d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/CurrencyParserConfiguration.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.CurrencyExtractorConfiguration; + +public class CurrencyParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public CurrencyParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public CurrencyParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(CurrencyExtractorConfiguration.CurrencySuffixList); + this.bindDictionary(CurrencyExtractorConfiguration.CurrencyPrefixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java new file mode 100644 index 000000000..dc95e0bee --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/DimensionParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.DimensionExtractorConfiguration; + +public class DimensionParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public DimensionParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public DimensionParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(DimensionExtractorConfiguration.DimensionSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java new file mode 100644 index 000000000..28b6dfb5e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/LengthParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.LengthExtractorConfiguration; + +public class LengthParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public LengthParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public LengthParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(LengthExtractorConfiguration.LengthSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java new file mode 100644 index 000000000..9eb6f824b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpanishNumberWithUnitParserConfiguration.java @@ -0,0 +1,39 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.IExtractor; +import com.microsoft.recognizers.text.IParser; +import com.microsoft.recognizers.text.number.NumberMode; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserFactory; +import com.microsoft.recognizers.text.number.parsers.AgnosticNumberParserType; +import com.microsoft.recognizers.text.number.spanish.extractors.NumberExtractor; +import com.microsoft.recognizers.text.number.spanish.parsers.SpanishNumberParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.parsers.BaseNumberWithUnitParserConfiguration; +import com.microsoft.recognizers.text.numberwithunit.resources.SpanishNumericWithUnit; + +public class SpanishNumberWithUnitParserConfiguration extends BaseNumberWithUnitParserConfiguration { + + private final IParser internalNumberParser; + private final IExtractor internalNumberExtractor; + + @Override + public IParser getInternalNumberParser() { + return this.internalNumberParser; + } + + @Override + public IExtractor getInternalNumberExtractor() { + return this.internalNumberExtractor; + } + + @Override + public String getConnectorToken() { + return SpanishNumericWithUnit.ConnectorToken; + } + + public SpanishNumberWithUnitParserConfiguration(CultureInfo ci) { + super(ci); + this.internalNumberExtractor = NumberExtractor.getInstance(NumberMode.Default); + this.internalNumberParser = AgnosticNumberParserFactory.getParser(AgnosticNumberParserType.Number, new SpanishNumberParserConfiguration()); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java new file mode 100644 index 000000000..93f905f96 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/SpeedParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.SpeedExtractorConfiguration; + +public class SpeedParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public SpeedParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public SpeedParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(SpeedExtractorConfiguration.SpeedSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java new file mode 100644 index 000000000..86a075361 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/TemperatureParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.TemperatureExtractorConfiguration; + +public class TemperatureParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public TemperatureParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public TemperatureParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(TemperatureExtractorConfiguration.TemperatureSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java new file mode 100644 index 000000000..c47e5ae22 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/VolumeParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.VolumeExtractorConfiguration; + +public class VolumeParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public VolumeParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public VolumeParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(VolumeExtractorConfiguration.VolumeSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java new file mode 100644 index 000000000..ecc4e5261 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/spanish/parsers/WeightParserConfiguration.java @@ -0,0 +1,18 @@ +package com.microsoft.recognizers.text.numberwithunit.spanish.parsers; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.CultureInfo; +import com.microsoft.recognizers.text.numberwithunit.spanish.extractors.WeightExtractorConfiguration; + +public class WeightParserConfiguration extends SpanishNumberWithUnitParserConfiguration { + + public WeightParserConfiguration() { + this(new CultureInfo(Culture.Spanish)); + } + + public WeightParserConfiguration(CultureInfo cultureInfo) { + super(cultureInfo); + + this.bindDictionary(WeightExtractorConfiguration.WeightSuffixList); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java new file mode 100644 index 000000000..38c88abbf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/DictionaryUtils.java @@ -0,0 +1,41 @@ +package com.microsoft.recognizers.text.numberwithunit.utilities; + +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class DictionaryUtils { + + /** + * Safely bind dictionary which contains several key-value pairs to the destination dictionary. + * This function is used to bind all the prefix and suffix for units. + */ + public static void bindDictionary(Map dictionary, + Map sourceDictionary) { + if (dictionary == null) { + return; + } + + for (Map.Entry pair : dictionary.entrySet()) { + if (pair.getKey() == null || pair.getKey().isEmpty()) { + continue; + } + + bindUnitsString(sourceDictionary, pair.getKey(), pair.getValue()); + } + } + + /** + * Bind keys in a string which contains words separated by '|'. + */ + public static void bindUnitsString(Map sourceDictionary, String key, String source) { + String[] values = source.trim().split(Pattern.quote("|")); + + for (String token : values) { + if (token.isEmpty() || sourceDictionary.containsKey(token)) { + continue; + } + + sourceDictionary.put(token, key); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java new file mode 100644 index 000000000..46fbc0bfd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/numberwithunit/utilities/StringComparer.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.numberwithunit.utilities; + +import com.microsoft.recognizers.text.utilities.StringUtility; +import java.util.Comparator; + +public class StringComparer implements Comparator { + @Override + public int compare(String stringA, String stringB) { + if (StringUtility.isNullOrEmpty(stringA) && StringUtility.isNullOrEmpty(stringB)) { + return 0; + } else { + if (StringUtility.isNullOrEmpty(stringB)) { + return -1; + } + if (StringUtility.isNullOrEmpty(stringA)) { + return 1; + } + int stringComparedLength = stringB.length() - stringA.length(); + + if (stringComparedLength != 0) { + return stringComparedLength; + } else { + return stringA.compareToIgnoreCase(stringB); + } + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java new file mode 100644 index 000000000..049b1b39c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/CodeGenerator.java @@ -0,0 +1,132 @@ +package com.microsoft.recognizers.text.resources; + +import com.microsoft.recognizers.text.resources.datatypes.*; +import com.microsoft.recognizers.text.resources.writters.*; +import org.yaml.snakeyaml.TypeDescription; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.Constructor; + +import java.io.*; +import java.nio.charset.StandardCharsets; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Map; + +public class CodeGenerator { + + private static final String lineBreak = "\n"; + + private static final String headerComment = String.join( + lineBreak, + "// ------------------------------------------------------------------------------", + "// ", + "// This code was generated by a tool.", + "// Changes to this file may cause incorrect behavior and will be lost if", + "// the code is regenerated.", + "// ", + "//", + "// Copyright (c) Microsoft Corporation. All rights reserved.", + "// Licensed under the MIT License.", + "// ------------------------------------------------------------------------------"); + + public static void Generate(Path yamlFilePath, Path outputFilePath, String header, String footer) throws IOException { + + // Config YAML parser + Constructor constructor = new Constructor(); + constructor.addTypeDescription(new TypeDescription(ParamsRegex.class, "!paramsRegex")); + constructor.addTypeDescription(new TypeDescription(SimpleRegex.class, "!simpleRegex")); + constructor.addTypeDescription(new TypeDescription(NestedRegex.class, "!nestedRegex")); + constructor.addTypeDescription(new TypeDescription(Character.class, "!char")); + constructor.addTypeDescription(new TypeDescription(Boolean.class, "!bool")); + constructor.addTypeDescription(new TypeDescription(Dictionary.class, "!dictionary")); + constructor.addTypeDescription(new TypeDescription(List.class, "!list")); + + // Read and Parse YAML + Yaml yaml = new Yaml(constructor); + Map raw = yaml.load(new InputStreamReader(new FileInputStream(yamlFilePath.toString()), StandardCharsets.UTF_8)); + + // Transform + String[] lines = GenerateCodeLines(raw); + + // Write to file + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(outputFilePath.toString()), StandardCharsets.UTF_8)); + writer.write(headerComment); + writer.write(lineBreak); + writer.write(lineBreak); + writer.write(header); + writer.write(lineBreak); + + BufferedWriter finalWriter = writer; + for (String l : lines) { + writer.write(lineBreak); + finalWriter.write(l); + writer.write(lineBreak); + } + + writer.write(footer); + writer.write(lineBreak); + } catch (Exception e) { + throw e; + } finally { + try { + writer.close(); + } catch (Exception e) { + throw e; + } + } + } + + private static String[] GenerateCodeLines(Map raw) { + return raw.entrySet().stream().map(kv -> { + String tokenName = kv.getKey(); + Object token = kv.getValue(); + return getWriter(tokenName, token).write(); + }).toArray(size -> new String[size]); + } + + private static ICodeWriter getWriter(String tokenName, Object token) throws IllegalArgumentException { + if (token instanceof ParamsRegex) { + return new ParamsRegexWriter(tokenName, (ParamsRegex)token); + } + + if (token instanceof SimpleRegex) { + return new SimpleRegexWriter(tokenName, (SimpleRegex)token); + } + + if (token instanceof NestedRegex) { + return new NestedRegexWriter(tokenName, (NestedRegex)token); + } + + if (token instanceof Character) { + return new CharacterWriter(tokenName, (char)token); + } + + if (token instanceof Boolean) { + return new BooleanWriter(tokenName, (boolean)token); + } + + if (token instanceof String) { + return new DefaultWriter(tokenName, (String)token); + } + + if (token instanceof Integer) { + return new IntegerWriter(tokenName, (int)token); + } + + if (token instanceof ArrayList) { + return new ListWriter(tokenName, "String", (String[])((ArrayList) token).stream().map(o -> o.toString()).toArray(size -> new String[size])); + } + + if (token instanceof List) { + return new ListWriter(tokenName, ((List)token).types[0], ((List)token).entries); + } + + if (token instanceof Dictionary) { + return new DictionaryWriter(tokenName, (Dictionary)token); + } + + throw new IllegalArgumentException(String.format("Data Type not supported for %s: %s", tokenName, token)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java new file mode 100644 index 000000000..b4b72a1af --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceConfig.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources; + +public class ResourceConfig { + public String[] input; + public String output; + public String[] header; + public String[] footer; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java new file mode 100644 index 000000000..6beee11a9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourceDefinitions.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources; + +import java.util.List; + +public class ResourceDefinitions { + public String outputPath; + public List configFiles; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java new file mode 100644 index 000000000..f0b58c697 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/ResourcesGenerator.java @@ -0,0 +1,49 @@ +package com.microsoft.recognizers.text.resources; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import java.io.*; +import java.nio.file.FileSystems; +import java.nio.file.Path; +import java.util.stream.Collectors; + +public class ResourcesGenerator { + + private static final String ResourcesPath = "../Patterns"; + + private static final String LineBreak = "\n"; + + public static void main(String[] args) throws Exception { + if (args.length == 0) { + throw new Exception("Please specify path to pattern/resource file."); + } + + for (String resourceDefinitionFilePath : args) { + + ResourceDefinitions definition = Parse(resourceDefinitionFilePath); + definition.configFiles.forEach(config -> { + Path inputPath = FileSystems.getDefault().getPath(ResourcesPath, String.join(File.separator, config.input) + ".yaml"); + Path outputPath = FileSystems.getDefault().getPath(definition.outputPath, config.output + ".java"); + System.out.println(String.format("%s => %s", inputPath.toString(), outputPath.toString())); + + String header = String.join(LineBreak, config.header); + String footer = String.join(LineBreak, config.footer); + + try { + CodeGenerator.Generate(inputPath, outputPath, header, footer); + } catch (IOException e) { + e.printStackTrace(); + } + }); + } + } + + private static ResourceDefinitions Parse(String resourceDefinitionFile) throws IOException { + Reader reader = new InputStreamReader(new FileInputStream(resourceDefinitionFile), "utf-8"); + BufferedReader br = new BufferedReader(reader); + + String json = br.lines().collect(Collectors.joining()); + + return new ObjectMapper().readValue(json, ResourceDefinitions.class); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java new file mode 100644 index 000000000..971237ae8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/Dictionary.java @@ -0,0 +1,8 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +import java.util.Map; + +public class Dictionary { + public String[] types; + public Map entries; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java new file mode 100644 index 000000000..e0b52a1d8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/List.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class List { + public String[] types; + public String[] entries; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java new file mode 100644 index 000000000..267fc731f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/NestedRegex.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class NestedRegex { + public String def; + public String[] references; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java new file mode 100644 index 000000000..9e33e395f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/ParamsRegex.java @@ -0,0 +1,6 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class ParamsRegex { + public String def; + public String[] params; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java new file mode 100644 index 000000000..6f1d2dd97 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/datatypes/SimpleRegex.java @@ -0,0 +1,5 @@ +package com.microsoft.recognizers.text.resources.datatypes; + +public class SimpleRegex { + public String def; +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java new file mode 100644 index 000000000..d3481bfa9 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/BooleanWriter.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class BooleanWriter implements ICodeWriter { + + private final String name; + private final boolean value; + + public BooleanWriter(String name, boolean value) { + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final Boolean %s = %s;", + this.name, + sanitize(String.valueOf(this.value))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java new file mode 100644 index 000000000..d874fd0ef --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/CharacterWriter.java @@ -0,0 +1,20 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class CharacterWriter implements ICodeWriter { + + private final String name; + private final char value; + + public CharacterWriter(String name, char value) { + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final Character %s = \'%s\';", + this.name, + sanitize(String.valueOf(this.value))); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java new file mode 100644 index 000000000..ed6bffa71 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DefaultWriter.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class DefaultWriter implements ICodeWriter { + + private final String name; + private final String value; + + public DefaultWriter(String name, String value) { + + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final String %s = \"%s\";", + this.name, + sanitize(this.value)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java new file mode 100644 index 000000000..1b8aa475e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/DictionaryWriter.java @@ -0,0 +1,94 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.Dictionary; +import com.microsoft.recognizers.text.resources.datatypes.List; + +import java.util.ArrayList; + +public class DictionaryWriter implements ICodeWriter { + + private final String name; + private final Dictionary def; + + public DictionaryWriter(String name, Dictionary def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + + String keyType = toJavaType(this.def.types[0]); + String valueType = toJavaType(this.def.types[1]); + + String keyQuote; + if(keyType.equals("Long") || keyType.equals("Double")) { + keyQuote = ""; + } else if(keyType.equals("Character")) { + keyQuote = "'"; + } else { + keyQuote = "\""; + } + + String valueQuote1; + String valueQuote2; + String prefix; + boolean hasList = false; + if (valueType.endsWith("[]")) { + hasList = true; + valueQuote1 = "{"; + valueQuote2 = "}"; + } else if(valueType.equals("Integer") || valueType.equals("Long") || valueType.equals("Double")) { + valueQuote1 = valueQuote2 = ""; + } else if(valueType.equals("Character")) { + valueQuote1 = valueQuote2 = "'"; + } else { + valueQuote1 = valueQuote2 = "\""; + } + + if (hasList) { + prefix = String.format("new %s", valueType); + } else { + prefix = ""; + } + + String[] entries = this.def.entries.entrySet().stream() + .map(kv -> String.format("\n .put(%s%s%s, %s%s%s%s)", keyQuote, sanitize(kv.getKey(), keyType), keyQuote, prefix, valueQuote1, getEntryValue(kv.getValue(), valueType), valueQuote2)) + .toArray(size -> new String[size]); + + return String.format( + " public static final ImmutableMap<%s, %s> %s = ImmutableMap.<%s, %s>builder()%s\n .build();", + keyType, + valueType, + this.name, + keyType, + valueType, + String.join("", entries)); + } + + private String getEntryValue(Object value, String valueType) { + if (value instanceof ArrayList) { + return String.join(", ", (String[])((ArrayList) value).stream().map(o -> String.format("\"%s\"", sanitize(o.toString(), valueType))).toArray(size -> new String[size])); + } + return sanitize(value.toString(), valueType); + } + + private String toJavaType(String type) { + switch (type) { + case "string": + return "String"; + case "char": + return "Character"; + case "long": + return "Long"; + case "int": + return "Integer"; + case "double": + return "Double"; + case "string[]": + return "String[]"; + default: + throw new IllegalArgumentException("Type '" + type + "' is not supported."); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java new file mode 100644 index 000000000..1bc908698 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ICodeWriter.java @@ -0,0 +1,36 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.google.common.collect.ImmutableMap; + +import java.util.Map; + +public interface ICodeWriter { + String write(); + + default String sanitize(String input) { + ObjectMapper mapper = new ObjectMapper(); + try { + String stringified = mapper.writeValueAsString(input); + return stringified.substring(1, stringified.length() - 1); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return ""; + } + } + + Map NumericTypes = ImmutableMap.of("Double", "D", "Long", "L"); + + default String sanitize(String input, String valueType) { + if(valueType.equals("Character")) { + return input.replace("'", "\\'"); + } + + if(NumericTypes.containsKey(valueType)) { + return input + (NumericTypes.get(valueType)); + } + + return sanitize(input); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java new file mode 100644 index 000000000..17e5f65c7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/IntegerWriter.java @@ -0,0 +1,21 @@ +package com.microsoft.recognizers.text.resources.writters; + +public class IntegerWriter implements ICodeWriter { + + private final String name; + private final int value; + + public IntegerWriter(String name, int value) { + + this.name = name; + this.value = value; + } + + @Override + public String write() { + return String.format( + " public static final int %s = %s;", + this.name, + this.value); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java new file mode 100644 index 000000000..67a61c29c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ListWriter.java @@ -0,0 +1,31 @@ +package com.microsoft.recognizers.text.resources.writters; + +import java.util.Arrays; + +public class ListWriter implements ICodeWriter { + + private final String name; + private final String type; + private final String[] items; + + public ListWriter(String name, String type, String[] items) { + this.name = name; + this.type = type; + this.items = items; + } + + @Override + public String write() { + String typeName = this.type.equalsIgnoreCase("char") ? "Character" : "String"; + String quoteChar = this.type.equalsIgnoreCase("char") ? "'" : "\""; + String[] stringItems = Arrays.stream(this.items) + .map(s -> quoteChar + sanitize(s) + quoteChar) + .toArray(size -> new String[size]); + + return String.format( + " public static final List<%s> %s = Arrays.asList(%s);", + typeName, + this.name, + String.join(", ", stringItems)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java new file mode 100644 index 000000000..37f172c27 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/NestedRegexWriter.java @@ -0,0 +1,24 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.NestedRegex; + +import java.util.Arrays; + +public class NestedRegexWriter implements ICodeWriter { + + private final String name; + private final NestedRegex def; + + public NestedRegexWriter(String name, NestedRegex def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + String replace = String.join("", Arrays.stream(this.def.references).map(p -> "\n .replace(\"{" + p + "}\", " + p + ")").toArray(size -> new String[size])); + + String template = " public static final String %s = \"%s\"%s;"; + return String.format(template, this.name, sanitize(this.def.def), replace); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java new file mode 100644 index 000000000..be1d839fa --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/ParamsRegexWriter.java @@ -0,0 +1,29 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.ParamsRegex; + +import java.util.Arrays; + +public class ParamsRegexWriter implements ICodeWriter { + + private final String name; + private ParamsRegex params; + + public ParamsRegexWriter(String name, ParamsRegex params) { + this.name = name; + this.params = params; + } + + @Override + public String write() { + String parameters = String.join(", ", Arrays.stream(this.params.params).map(p -> "String " + p).toArray(size -> new String[size])); + String replace = String.join("", Arrays.stream(this.params.params).map(p -> "\n .replace(\"{" + p + "}\", " + p + ")").toArray(size -> new String[size])); + + String template = String.join( + "\n ", + " public static String %s(%s) {", + " return \"%s\"%s;", + "}"); + return String.format(template, this.name, parameters, sanitize(this.params.def), replace); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java new file mode 100644 index 000000000..95817bb7e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/resources/writters/SimpleRegexWriter.java @@ -0,0 +1,19 @@ +package com.microsoft.recognizers.text.resources.writters; + +import com.microsoft.recognizers.text.resources.datatypes.SimpleRegex; + +public class SimpleRegexWriter implements ICodeWriter { + + private String name; + private SimpleRegex def; + + public SimpleRegexWriter(String name, SimpleRegex def) { + this.name = name; + this.def = def; + } + + @Override + public String write() { + return String.format(" public static final String %s = \"%s\";", this.name, sanitize(this.def.def)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java new file mode 100644 index 000000000..14cc5ba96 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Capture.java @@ -0,0 +1,13 @@ +package com.microsoft.recognizers.text.utilities; + +public class Capture { + public final String value; + public final int index; + public final int length; + + public Capture(String value, int index, int length) { + this.value = value; + this.index = index; + this.length = length; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java new file mode 100644 index 000000000..4207c9480 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DefinitionLoader.java @@ -0,0 +1,25 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Pattern; + +public abstract class DefinitionLoader { + + public static Map loadAmbiguityFilters(Map filters) { + + HashMap ambiguityFiltersDict = new HashMap<>(); + + for (Map.Entry pair : filters.entrySet()) { + + if (!"null".equals(pair.getKey())) { + Pattern key = RegExpUtility.getSafeRegExp(pair.getKey()); + Pattern val = RegExpUtility.getSafeRegExp(pair.getValue()); + ambiguityFiltersDict.put(key, val); + } + } + + return ambiguityFiltersDict; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java new file mode 100644 index 000000000..eee99eedf --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/DoubleUtility.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class DoubleUtility { + public static boolean canParse(String value) { + try { + Double.parseDouble(value); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java new file mode 100644 index 000000000..a1b34f28f --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/FormatUtility.java @@ -0,0 +1,83 @@ +package com.microsoft.recognizers.text.utilities; + +import java.text.Normalizer; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class FormatUtility { + public static String preprocess(String query) { + return FormatUtility.preprocess(query, true); + } + + public static String preprocess(String query, boolean toLower) { + if (toLower) { + query = query.toLowerCase(); + } + + return query + .replace('0', '0') + .replace('1', '1') + .replace('2', '2') + .replace('3', '3') + .replace('4', '4') + .replace('5', '5') + .replace('6', '6') + .replace('7', '7') + .replace('8', '8') + .replace('9', '9') + .replace(':', ':') + .replace('-', '-') + .replace(',', ',') + .replace('/', '/') + .replace('G', 'G') + .replace('M', 'M') + .replace('T', 'T') + .replace('K', 'K') + .replace('k', 'k') + .replace('.', '.') + .replace('(', '(') + .replace(')', ')'); + } + + public static String trimEnd(String input) { + return input.replaceAll("\\s+$", ""); + } + + public static String trimEnd(String input, CharSequence chars) { + return input.replaceAll("[" + Pattern.quote(chars.toString()) + "]+$", ""); + } + + public static List split(String input, List delimiters) { + String delimitersRegex = String.join( + "|", + delimiters.stream() + .map(s -> Pattern.quote(s)) + .collect(Collectors.toList())); + + return Arrays.stream(input.split(delimitersRegex)).filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + public static String removeDiacritics(String query) { + if (query == null) { + return null; + } + + String norm = Normalizer.normalize(query, Normalizer.Form.NFD); + int j = 0; + char[] out = new char[query.length()]; + for (int i = 0, n = norm.length(); i < n; ++i) { + char c = norm.charAt(i); + int type = Character.getType(c); + + if (type != Character.NON_SPACING_MARK) { + out[j] = c; + j++; + } + } + + return new String(out); + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java new file mode 100644 index 000000000..9d7e11cd0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/IntegerUtility.java @@ -0,0 +1,12 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class IntegerUtility { + public static boolean canParse(String value) { + try { + Integer.parseInt(value); + } catch (Exception e) { + return false; + } + return true; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java new file mode 100644 index 000000000..bbed9c58e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/Match.java @@ -0,0 +1,25 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.Map; + +public class Match { + public final int index; + public final int length; + public final String value; + public final Map innerGroups; + + public Match(int index, int length, String value, Map innerGroups) { + this.index = index; + this.length = length; + this.value = value; + this.innerGroups = innerGroups; + } + + public MatchGroup getGroup(String key) { + if (innerGroups.containsKey(key)) { + return innerGroups.get(key); + } + + return new MatchGroup("", 0, 0, new Capture[0]); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java new file mode 100644 index 000000000..15d8e8cc0 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/MatchGroup.java @@ -0,0 +1,15 @@ +package com.microsoft.recognizers.text.utilities; + +public class MatchGroup { + public final String value; + public final int index; + public final int length; + public final Capture[] captures; + + public MatchGroup(String value, int index, int length, Capture[] captures) { + this.value = value; + this.index = index; + this.length = length; + this.captures = captures; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java new file mode 100644 index 000000000..c857d64fd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/QueryProcessor.java @@ -0,0 +1,120 @@ +package com.microsoft.recognizers.text.utilities; + +import java.text.Normalizer; +import java.util.Arrays; +import java.util.List; +import java.util.Locale; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +public class QueryProcessor { + + + public static String preprocess(String query) { + return QueryProcessor.preprocess(query, false, true); + } + + public static String preprocess(String query, boolean caseSensitive) { + return QueryProcessor.preprocess(query, caseSensitive, true); + } + + public static String preprocess(String query, boolean caseSensitive, boolean recode) { + + if (recode) { + query = query.replace('0', '0') + .replace('1', '1') + .replace('2', '2') + .replace('3', '3') + .replace('4', '4') + .replace('5', '5') + .replace('6', '6') + .replace('7', '7') + .replace('8', '8') + .replace('9', '9') + .replace(':', ':') + .replace('-', '-') + .replace(',', ',') + .replace('/', '/') + .replace('G', 'G') + .replace('M', 'M') + .replace('T', 'T') + .replace('K', 'K') + .replace('k', 'k') + .replace('.', '.') + .replace('(', '(') + .replace(')', ')') + .replace('%', '%') + .replace('、', ','); + } + + if (!caseSensitive) { + query = query.toLowerCase(); + } else { + query = toLowerTermSensitive(query); + } + + return query; + } + + private static final String tokens = "(kB|K[Bb]?|M[BbM]?|G[Bb]?|B)"; + private static final String expression = "(?<=(\\s|\\d))" + tokens + "\\b"; + private static final Pattern special_tokens_regex = Pattern.compile(expression, Pattern.UNICODE_CHARACTER_CLASS); + + private static String toLowerTermSensitive(String input) { + + char[] inputChars = input.toLowerCase(Locale.ROOT).toCharArray(); + + Match[] matches = RegExpUtility.getMatches(special_tokens_regex, input); + for (Match match : matches) { + QueryProcessor.applyReverse(match.index, inputChars, match.value); + } + + return new String(inputChars); + } + + private static void applyReverse(int index, char[] inputChars, String value) { + for (int i = 0; i < value.length(); ++i) { + inputChars[index + i] = value.charAt(i); + } + } + + public static String trimEnd(String input) { + return input.replaceAll("\\s+$", ""); + } + + public static String trimEnd(String input, CharSequence chars) { + return input.replaceAll("[" + Pattern.quote(chars.toString()) + "]+$", ""); + } + + public static List split(String input, List delimiters) { + String delimitersRegex = String.join( + "|", + delimiters.stream() + .map(s -> Pattern.quote(s)) + .collect(Collectors.toList())); + + return Arrays.stream(input.split(delimitersRegex)).filter(s -> !s.isEmpty()) + .collect(Collectors.toList()); + } + + public static String removeDiacritics(String query) { + if (query == null) { + return null; + } + + String norm = Normalizer.normalize(query, Normalizer.Form.NFD); + int j = 0; + char[] out = new char[query.length()]; + for (int i = 0, n = norm.length(); i < n; ++i) { + char c = norm.charAt(i); + int type = Character.getType(c); + + if (type != Character.NON_SPACING_MARK) { + out[j] = c; + j++; + } + } + + return new String(out); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java new file mode 100644 index 000000000..41612c136 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/RegExpUtility.java @@ -0,0 +1,419 @@ +package com.microsoft.recognizers.text.utilities; + +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Stack; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.javatuples.Pair; + +public abstract class RegExpUtility { + + private static final Pattern matchGroup = Pattern.compile("\\?<(?\\w+)>", Pattern.CASE_INSENSITIVE); + private static final Pattern matchGroupNames = Pattern.compile("\\(\\?<([a-zA-Z][a-zA-Z0-9]*)>", Pattern.CASE_INSENSITIVE); + private static final Pattern matchPositiveLookbehind = Pattern.compile("\\(\\?<=", Pattern.CASE_INSENSITIVE); + private static final Pattern matchNegativeLookbehind = Pattern.compile("\\(\\? bindings = new HashMap() { + { + put('+', "{1,10}"); + put('*', "{0,10}"); + } + }; + + public static Pattern getSafeRegExp(String source) { + return getSafeRegExp(source, 0); + } + + public static Pattern getSafeRegExp(String source, int flags) { + String sanitizedSource = sanitizeGroups(source); + return Pattern.compile(sanitizedSource, flags); + } + + public static Map getNamedGroups(Matcher groupedMatcher) { + return getNamedGroups(groupedMatcher, false); + } + + public static Map getNamedGroups(Matcher groupedMatcher, boolean sanitize) { + + Map matchedGroups = new LinkedHashMap<>(); + Matcher m = matchGroupNames.matcher(groupedMatcher.pattern().pattern()); + + while (m.find()) { + String groupName = m.group(1); + String groupValue = groupedMatcher.group(groupName); + if (sanitize && groupName.contains(groupNameIndexSep)) { + groupName = groupName.substring(0, groupName.lastIndexOf(groupNameIndexSep)); + } + + if (!groupName.contains(groupNameIndexSep)) { + groupName = groupName.replace("ii", "_"); + } + + // If matchedGroups previously contained a mapping for groupName, the old value is replaced. + if (groupValue != null) { + matchedGroups.put(groupName, groupValue); + } + } + + return matchedGroups; + } + + public static Match[] getMatches(Pattern regex, String source) { + + if (regex == null) { + return new Match[0]; + } + + String rawRegex = regex.pattern(); + if (!rawRegex.contains("(? realMatches = new ArrayList<>(); + List> negativeLookbehindRegexes = new ArrayList<>(); + int flags = regex.flags(); + + int closePos = 0; + int startPos = rawRegex.indexOf("(?= 0) { + closePos = getClosePos(rawRegex, startPos); + Pattern nlbRegex = Pattern.compile(rawRegex.substring(startPos, closePos + 1), flags); + String nextRegex = getNextRegex(rawRegex, startPos); + + negativeLookbehindRegexes.add(Pair.with(nlbRegex, nextRegex != null ? Pattern.compile(nextRegex, flags) : null)); + + rawRegex = rawRegex.substring(0, startPos) + rawRegex.substring(closePos + 1); + startPos = rawRegex.indexOf("(? { + + AtomicBoolean isClean = new AtomicBoolean(true); + negativeLookbehindRegexes.forEach((pair) -> { + + Pattern currRegex = pair.getValue0(); + Match[] negativeLookbehindMatches = getMatchesSimple(currRegex, source); + Arrays.stream(negativeLookbehindMatches).forEach(negativeLookbehindMatch -> { + + int negativeLookbehindEnd = negativeLookbehindMatch.index + negativeLookbehindMatch.length; + Pattern nextRegex = pair.getValue1(); + + if (match.index == negativeLookbehindEnd) { + + if (nextRegex == null) { + isClean.set(false); + return; + } else { + Match nextMatch = getFirstMatchIndex(nextRegex, source.substring(negativeLookbehindMatch.index)); + if (nextMatch != null && ((nextMatch.index == negativeLookbehindMatch.length) || (source.contains(nextMatch.value + match.value)))) { + isClean.set(false); + return; + } + + } + } + + if (negativeLookbehindMatch.value.contains(match.value)) { + + Match[] preMatches = getMatchesSimple(regex, source.substring(0, match.index)); + Arrays.stream(preMatches).forEach(preMatch -> { + if (source.contains(preMatch.value + match.value)) { + isClean.set(false); + return; + } + }); + } + }); + + if (!isClean.get()) { + return; + } + }); + + if (isClean.get()) { + realMatches.add(match); + } + }); + + return realMatches.toArray(new Match[realMatches.size()]); + } + + private static String sanitizeGroups(String source) { + + String result = source; + + AtomicInteger index = new AtomicInteger(0); + result = replace(result, matchGroup, (Matcher m) -> m.group(0).replace(m.group(1), m.group(1).replace("_", "ii") + groupNameIndexSep + index.getAndIncrement())); + + index.set(0); + result = replace(result, matchPositiveLookbehind, (Matcher m) -> String.format("(?", groupNameIndexSep, index.getAndIncrement())); + + index.set(0); + result = replace(result, matchNegativeLookbehind, (Matcher m) -> String.format("(?", groupNameIndexSep, index.getAndIncrement())); + + return result; + } + + public static Pattern getSafeLookbehindRegExp(String source) { + return getSafeLookbehindRegExp(source, 0); + } + + public static Pattern getSafeLookbehindRegExp(String source, int flags) { + + String result = source; + + // Java pre 1.9 doesn't support unbounded lookbehind lengths + if (unboundedLookBehindNotSupported) { + result = bindLookbehinds(result); + } + + return Pattern.compile(result, flags); + } + + private static String bindLookbehinds(String regex) { + + String result = regex; + Stack replaceStack = new Stack<>(); + + Matcher matcher = lookBehindCheckRegex.matcher(regex); + + while (matcher.find()) { + getReplaceIndexes(result, matcher.start(), replaceStack); + } + + if (!replaceStack.empty()) { + + StringBuilder buffer = new StringBuilder(result); + while (!replaceStack.isEmpty()) { + int idx = replaceStack.peek(); + buffer.replace(idx, idx + 1, bindings.get(result.charAt(idx))); + replaceStack.pop(); + } + + result = buffer.toString(); + } + + return result; + } + + private static void getReplaceIndexes(String input, int startIndex, Stack replaceStack) { + + int idx = startIndex + 3; + Stack stack = new Stack<>(); + + while (idx < input.length()) { + switch (input.charAt(idx)) { + case ')': + if (stack.isEmpty()) { + idx = input.length(); + } else { + stack.pop(); + } + break; + case '(': + stack.push('('); + break; + case '*': + case '+': + replaceStack.push(idx); + break; + case '|': + if (stack.isEmpty()) { + idx = input.length(); + } + break; + default: + break; + } + + idx += 1; + } + } + + private static Match[] getMatchesSimple(Pattern regex, String source) { + + List matches = new ArrayList<>(); + + Matcher match = regex.matcher(source); + while (match.find()) { + + List> positiveLookbehinds = new ArrayList<>(); + Map groups = new HashMap<>(); + AtomicReference lastGroup = new AtomicReference<>(""); + + getNamedGroups(match).forEach((key, groupValue) -> { + + if (!key.contains(groupNameIndexSep)) { + return; + } + + if (key.startsWith("plb") && !StringUtility.isNullOrEmpty(match.group(key))) { + + if (match.group(0).indexOf(match.group(key)) != 0 && !StringUtility.isNullOrEmpty(lastGroup.get())) { + + int index = match.start() + match.group(0).indexOf(match.group(key)); + int length = match.group(key).length(); + String value = source.substring(index, index + length); + + MatchGroup lastMatchGroup = groups.get(lastGroup.get()); + groups.replace(lastGroup.get(), new MatchGroup( + lastMatchGroup.value + value, + lastMatchGroup.index, + lastMatchGroup.length, + lastMatchGroup.captures)); + } + + positiveLookbehinds.add(Pair.with(key, match.group(key))); + return; + } + + if (key.startsWith("nlb")) { + return; + } + + String groupKey = key.substring(0, key.lastIndexOf(groupNameIndexSep)).replace("ii", "_"); + lastGroup.set(groupKey); + + if (!groups.containsKey(groupKey)) { + groups.put(groupKey, new MatchGroup("", 0, 0, new Capture[0])); + } + + if (!StringUtility.isNullOrEmpty(match.group(key))) { + + int index = match.start(key); + int length = match.group(key).length(); + String value = source.substring(index, match.end(key)); + List captures = new ArrayList<>(Arrays.asList(groups.get(groupKey).captures)); + captures.add(new Capture(value, index, length)); + + groups.replace(groupKey, new MatchGroup(value, index, length, captures.toArray(new Capture[0]))); + } + }); + + String value = match.group(0); + int index = match.start(); + int length = value.length(); + + if (positiveLookbehinds.size() > 0 && value.indexOf(positiveLookbehinds.get(0).getValue1()) == 0) { + int valueLength = positiveLookbehinds.get(0).getValue1().length(); + value = source.substring(index, index + length).substring(valueLength); + index += valueLength; + length -= valueLength; + } else { + value = source.substring(index, index + length); + } + + matches.add(new Match(index, length, value, groups)); + } + + return matches.toArray(new Match[matches.size()]); + } + + private static Match getFirstMatchIndex(Pattern regex, String source) { + + Match[] matches = getMatches(regex, source); + if (matches.length > 0) { + return matches[0]; + } + + return null; + } + + private static String getNextRegex(String source, int startPos) { + + startPos = getClosePos(source, startPos) + 1; + int closePos = getClosePos(source, startPos); + if (source.charAt(startPos) != '(') { + closePos--; + } + + String next = (startPos == closePos) ? + null : + source.substring(startPos, closePos + 1); + + return next; + } + + private static int getClosePos(String rawRegex, int startPos) { + + int counter = 1; + int closePos = startPos; + + while (counter > 0 && closePos < rawRegex.length()) { + + ++closePos; + if (closePos < rawRegex.length()) { + char c = rawRegex.charAt(closePos); + if (c == '(') { + counter++; + } else if (c == ')') { + counter--; + } + } + } + + return closePos; + } + + public static String replace(String input, Pattern regex, StringReplacerCallback callback) { + + StringBuffer resultString = new StringBuffer(); + Matcher regexMatcher = regex.matcher(input); + + while (regexMatcher.find()) { + String replacement = callback.replace(regexMatcher); + regexMatcher.appendReplacement(resultString, replacement); + } + + regexMatcher.appendTail(resultString); + + return resultString.toString(); + } + + // Checks if Java version is <= 8, as they don't support look-behind groups with no maximum length. + private static boolean isRestrictedJavaVersion() { + + boolean result = false; + BigDecimal targetVersion = new BigDecimal("1.8"); + + try { + String specVersion = System.getProperty("java.specification.version"); + result = new BigDecimal(specVersion).compareTo(targetVersion) >= 0; + } catch (Exception e1) { + + try { + // Could also be "java.runtime.version". + String runtimeVersion = System.getProperty("java.version"); + result = new BigDecimal(runtimeVersion).compareTo(targetVersion) >= 0; + + } catch (Exception e2) { + // Nothing to do, ignore. + } + + } + + if (result) { + System.out.println("WARN: Look-behind groups with no maximum length not supported. Java version <= 8."); + } + + return result; + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java new file mode 100644 index 000000000..0052865e8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringReplacerCallback.java @@ -0,0 +1,7 @@ +package com.microsoft.recognizers.text.utilities; + +import java.util.regex.Matcher; + +public interface StringReplacerCallback { + public String replace(Matcher match); +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java new file mode 100644 index 000000000..1d443908d --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/utilities/StringUtility.java @@ -0,0 +1,27 @@ +package com.microsoft.recognizers.text.utilities; + +public abstract class StringUtility { + public static boolean isNullOrEmpty(String source) { + return source == null || source.equals(""); + } + + public static boolean isNullOrWhiteSpace(String source) { + return source == null || source.trim().equals(""); + } + + public static String trimStart(String source) { + return source.replaceFirst("^\\s+", ""); + } + + public static String trimEnd(String source) { + return source.replaceFirst("\\s+$", ""); + } + + public static String format(double d) { + if (d == (long)d) { + return String.format("%d", (long)d); + } + + return String.format("%s", d); + } +} diff --git a/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt b/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt new file mode 100644 index 000000000..a89c7a42b --- /dev/null +++ b/libraries/bot-dialogs/src/main/resources/naughtyStrings.txt @@ -0,0 +1,742 @@ +# Reserved Strings +# +# Strings which may be used elsewhere in code + +undefined +undef +null +NULL +(null) +nil +NIL +true +false +True +False +TRUE +FALSE +None +hasOwnProperty +then +constructor +\ +\\ + +# Numeric Strings +# +# Strings which can be interpreted as numeric + +0 +1 +1.00 +$1.00 +1/2 +1E2 +1E02 +1E+02 +-1 +-1.00 +-$1.00 +-1/2 +-1E2 +-1E02 +-1E+02 +1/0 +0/0 +-2147483648/-1 +-9223372036854775808/-1 +-0 +-0.0 ++0 ++0.0 +0.00 +0..0 +. +0.0.0 +0,00 +0,,0 +, +0,0,0 +0.0/0 +1.0/0.0 +0.0/0.0 +1,0/0,0 +0,0/0,0 +--1 +- +-. +-, +999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999 +NaN +Infinity +-Infinity +INF +1#INF +-1#IND +1#QNAN +1#SNAN +1#IND +0x0 +0xffffffff +0xffffffffffffffff +0xabad1dea +123456789012345678901234567890123456789 +1,000.00 +1 000.00 +1'000.00 +1,000,000.00 +1 000 000.00 +1'000'000.00 +1.000,00 +1 000,00 +1'000,00 +1.000.000,00 +1 000 000,00 +1'000'000,00 +01000 +08 +09 +2.2250738585072011e-308 + +# Special Characters +# +# ASCII punctuation. All of these characters may need to be escaped in some +# contexts. Divided into three groups based on (US-layout) keyboard position. + +,./;'[]\-= +<>?:"{}|_+ +!@#$%^&*()`~ + +# Non-whitespace C0 controls: U+0001 through U+0008, U+000E through U+001F, +# and U+007F (DEL) +# Often forbidden to appear in various text-based file formats (e.g. XML), +# or reused for internal delimiters on the theory that they should never +# appear in input. +# The next line may appear to be blank or mojibake in some viewers. + + +# Non-whitespace C1 controls: U+0080 through U+0084 and U+0086 through U+009F. +# Commonly misinterpreted as additional graphic characters. +# The next line may appear to be blank, mojibake, or dingbats in some viewers. +€‚ƒ„†‡ˆ‰Š‹ŒŽ‘’“”•–—˜™š›œžŸ + +# Whitespace: all of the characters with category Zs, Zl, or Zp (in Unicode +# version 8.0.0), plus U+0009 (HT), U+000B (VT), U+000C (FF), U+0085 (NEL), +# and U+200B (ZERO WIDTH SPACE), which are in the C categories but are often +# treated as whitespace in some contexts. +# This file unfortunately cannot express strings containing +# U+0000, U+000A, or U+000D (NUL, LF, CR). +# The next line may appear to be blank or mojibake in some viewers. +# The next line may be flagged for "trailing whitespace" in some viewers. + …             ​

    + +# Unicode additional control characters: all of the characters with +# general category Cf (in Unicode 8.0.0). +# The next line may appear to be blank or mojibake in some viewers. +­؀؁؂؃؄؅؜۝܏᠎​‌‍‎‏‪‫‬‭‮⁠⁡⁢⁣⁤⁦⁧⁨⁩𑂽𛲠𛲡𛲢𛲣𝅳𝅴𝅵𝅶𝅷𝅸𝅹𝅺󠀁󠀠󠀡󠀢󠀣󠀤󠀥󠀦󠀧󠀨󠀩󠀪󠀫󠀬󠀭󠀮󠀯󠀰󠀱󠀲󠀳󠀴󠀵󠀶󠀷󠀸󠀹󠀺󠀻󠀼󠀽󠀾󠀿󠁀󠁁󠁂󠁃󠁄󠁅󠁆󠁇󠁈󠁉󠁊󠁋󠁌󠁍󠁎󠁏󠁐󠁑󠁒󠁓󠁔󠁕󠁖󠁗󠁘󠁙󠁚󠁛󠁜󠁝󠁞󠁟󠁠󠁡󠁢󠁣󠁤󠁥󠁦󠁧󠁨󠁩󠁪󠁫󠁬󠁭󠁮󠁯󠁰󠁱󠁲󠁳󠁴󠁵󠁶󠁷󠁸󠁹󠁺󠁻󠁼󠁽󠁾󠁿 + +# "Byte order marks", U+FEFF and U+FFFE, each on its own line. +# The next two lines may appear to be blank or mojibake in some viewers. + +￾ + +# Unicode Symbols +# +# Strings which contain common unicode symbols (e.g. smart quotes) + +Ω≈ç√∫˜µ≤≥÷ +åß∂ƒ©˙∆˚¬…æ +œ∑´®†¥¨ˆøπ“‘ +¡™£¢∞§¶•ªº–≠ +¸˛Ç◊ı˜Â¯˘¿ +ÅÍÎÏ˝ÓÔÒÚÆ☃ +Œ„´‰ˇÁ¨ˆØ∏”’ +`⁄€‹›fifl‡°·‚—± +⅛⅜⅝⅞ +ЁЂЃЄЅІЇЈЉЊЋЌЍЎЏАБВГДЕЖЗИЙКЛМНОПРСТУФХЦЧШЩЪЫЬЭЮЯабвгдежзийклмнопрстуфхцчшщъыьэюя +٠١٢٣٤٥٦٧٨٩ + +# Unicode Subscript/Superscript/Accents +# +# Strings which contain unicode subscripts/superscripts; can cause rendering issues + +⁰⁴⁵ +₀₁₂ +⁰⁴⁵₀₁₂ +ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ ด้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็็้้้้้้้้็็็็็้้้้้็็็็ + +# Quotation Marks +# +# Strings which contain misplaced quotation marks; can cause encoding errors + +' +" +'' +"" +'"' +"''''"'" +"'"'"''''" + + + + + +# Two-Byte Characters +# +# Strings which contain two-byte characters: can cause rendering issues or character-length issues + +田中さんにあげて下さい +パーティーへ行かないか +和製漢語 +部落格 +사회과학원 어학연구소 +찦차를 타고 온 펲시맨과 쑛다리 똠방각하 +社會科學院語學研究所 +울란바토르 +𠜎𠜱𠝹𠱓𠱸𠲖𠳏 + +# Strings which contain two-byte letters: can cause issues with naïve UTF-16 capitalizers which think that 16 bits == 1 character + +𐐜 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐙𐐊𐐡𐐝𐐓/𐐝𐐇𐐗𐐊𐐤𐐔 𐐒𐐋𐐗 𐐒𐐌 𐐜 𐐡𐐀𐐖𐐇𐐤𐐓𐐝 𐐱𐑂 𐑄 𐐔𐐇𐐝𐐀𐐡𐐇𐐓 𐐏𐐆𐐅𐐤𐐆𐐚𐐊𐐡𐐝𐐆𐐓𐐆 + +# Special Unicode Characters Union +# +# A super string recommended by VMware Inc. Globalization Team: can effectively cause rendering issues or character-length issues to validate product globalization readiness. +# +# 表 CJK_UNIFIED_IDEOGRAPHS (U+8868) +# ポ KATAKANA LETTER PO (U+30DD) +# あ HIRAGANA LETTER A (U+3042) +# A LATIN CAPITAL LETTER A (U+0041) +# 鷗 CJK_UNIFIED_IDEOGRAPHS (U+9DD7) +# Œ LATIN SMALL LIGATURE OE (U+0153) +# é LATIN SMALL LETTER E WITH ACUTE (U+00E9) +# B FULLWIDTH LATIN CAPITAL LETTER B (U+FF22) +# 逍 CJK_UNIFIED_IDEOGRAPHS (U+900D) +# Ü LATIN SMALL LETTER U WITH DIAERESIS (U+00FC) +# ß LATIN SMALL LETTER SHARP S (U+00DF) +# ª FEMININE ORDINAL INDICATOR (U+00AA) +# ą LATIN SMALL LETTER A WITH OGONEK (U+0105) +# ñ LATIN SMALL LETTER N WITH TILDE (U+00F1) +# 丂 CJK_UNIFIED_IDEOGRAPHS (U+4E02) +# 㐀 CJK Ideograph Extension A, First (U+3400) +# 𠀀 CJK Ideograph Extension B, First (U+20000) + +表ポあA鷗ŒéB逍Üߪąñ丂㐀𠀀 + +# Changing length when lowercased +# +# Characters which increase in length (2 to 3 bytes) when lowercased +# Credit: https://twitter.com/jifa/status/625776454479970304 + +Ⱥ +Ⱦ + +# Japanese Emoticons +# +# Strings which consists of Japanese-style emoticons which are popular on the web + +ヽ༼ຈل͜ຈ༽ノ ヽ༼ຈل͜ຈ༽ノ +(。◕ ∀ ◕。) +`ィ(´∀`∩ +__ロ(,_,*) +・( ̄∀ ̄)・:*: +゚・✿ヾ╲(。◕‿◕。)╱✿・゚ +,。・:*:・゜’( ☻ ω ☻ )。・:*:・゜’ +(╯°□°)╯︵ ┻━┻) +(ノಥ益ಥ)ノ ┻━┻ +┬─┬ノ( º _ ºノ) +( ͡° ͜ʖ ͡°) +¯\_(ツ)_/¯ + +# Emoji +# +# Strings which contain Emoji; should be the same behavior as two-byte characters, but not always + +😍 +👩🏽 +👨‍🦰 👨🏿‍🦰 👨‍🦱 👨🏿‍🦱 🦹🏿‍♂️ +👾 🙇 💁 🙅 🙆 🙋 🙎 🙍 +🐵 🙈 🙉 🙊 +❤️ 💔 💌 💕 💞 💓 💗 💖 💘 💝 💟 💜 💛 💚 💙 +✋🏿 💪🏿 👐🏿 🙌🏿 👏🏿 🙏🏿 +👨‍👩‍👦 👨‍👩‍👧‍👦 👨‍👨‍👦 👩‍👩‍👧 👨‍👦 👨‍👧‍👦 👩‍👦 👩‍👧‍👦 +🚾 🆒 🆓 🆕 🆖 🆗 🆙 🏧 +0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 + +# Regional Indicator Symbols +# +# Regional Indicator Symbols can be displayed differently across +# fonts, and have a number of special behaviors + +🇺🇸🇷🇺🇸 🇦🇫🇦🇲🇸 +🇺🇸🇷🇺🇸🇦🇫🇦🇲 +🇺🇸🇷🇺🇸🇦 + +# Unicode Numbers +# +# Strings which contain unicode numbers; if the code is localized, it should see the input as numeric + +123 +١٢٣ + +# Right-To-Left Strings +# +# Strings which contain text that should be rendered RTL if possible (e.g. Arabic, Hebrew) + +ثم نفس سقطت وبالتحديد،, جزيرتي باستخدام أن دنو. إذ هنا؟ الستار وتنصيب كان. أهّل ايطاليا، بريطانيا-فرنسا قد أخذ. سليمان، إتفاقية بين ما, يذكر الحدود أي بعد, معاملة بولندا، الإطلاق عل إيو. +בְּרֵאשִׁית, בָּרָא אֱלֹהִים, אֵת הַשָּׁמַיִם, וְאֵת הָאָרֶץ +הָיְתָהtestالصفحات التّحول +﷽ +ﷺ +مُنَاقَشَةُ سُبُلِ اِسْتِخْدَامِ اللُّغَةِ فِي النُّظُمِ الْقَائِمَةِ وَفِيم يَخُصَّ التَّطْبِيقَاتُ الْحاسُوبِيَّةُ، +الكل في المجمو عة (5) + +# Ogham Text +# +# The only unicode alphabet to use a space which isn't empty but should still act like a space. + +᚛ᚄᚓᚐᚋᚒᚄ ᚑᚄᚂᚑᚏᚅ᚜ +᚛                 ᚜ + +# Trick Unicode +# +# Strings which contain unicode with unusual properties (e.g. Right-to-left override) (c.f. http://www.unicode.org/charts/PDF/U2000.pdf) + +‪‪test‪ +‫test‫ +
test
 +test⁠test‫ +⁦test⁧ + +# Zalgo Text +# +# Strings which contain "corrupted" text. The corruption will not appear in non-HTML text, however. (via http://www.eeemo.net) + +Ṱ̺̺̕o͞ ̷i̲̬͇̪͙n̝̗͕v̟̜̘̦͟o̶̙̰̠kè͚̮̺̪̹̱̤ ̖t̝͕̳̣̻̪͞h̼͓̲̦̳̘̲e͇̣̰̦̬͎ ̢̼̻̱̘h͚͎͙̜̣̲ͅi̦̲̣̰̤v̻͍e̺̭̳̪̰-m̢iͅn̖̺̞̲̯̰d̵̼̟͙̩̼̘̳ ̞̥̱̳̭r̛̗̘e͙p͠r̼̞̻̭̗e̺̠̣͟s̘͇̳͍̝͉e͉̥̯̞̲͚̬͜ǹ̬͎͎̟̖͇̤t͍̬̤͓̼̭͘ͅi̪̱n͠g̴͉ ͏͉ͅc̬̟h͡a̫̻̯͘o̫̟̖͍̙̝͉s̗̦̲.̨̹͈̣ +̡͓̞ͅI̗̘̦͝n͇͇͙v̮̫ok̲̫̙͈i̖͙̭̹̠̞n̡̻̮̣̺g̲͈͙̭͙̬͎ ̰t͔̦h̞̲e̢̤ ͍̬̲͖f̴̘͕̣è͖ẹ̥̩l͖͔͚i͓͚̦͠n͖͍̗͓̳̮g͍ ̨o͚̪͡f̘̣̬ ̖̘͖̟͙̮c҉͔̫͖͓͇͖ͅh̵̤̣͚͔á̗̼͕ͅo̼̣̥s̱͈̺̖̦̻͢.̛̖̞̠̫̰ +̗̺͖̹̯͓Ṯ̤͍̥͇͈h̲́e͏͓̼̗̙̼̣͔ ͇̜̱̠͓͍ͅN͕͠e̗̱z̘̝̜̺͙p̤̺̹͍̯͚e̠̻̠͜r̨̤͍̺̖͔̖̖d̠̟̭̬̝͟i̦͖̩͓͔̤a̠̗̬͉̙n͚͜ ̻̞̰͚ͅh̵͉i̳̞v̢͇ḙ͎͟-҉̭̩̼͔m̤̭̫i͕͇̝̦n̗͙ḍ̟ ̯̲͕͞ǫ̟̯̰̲͙̻̝f ̪̰̰̗̖̭̘͘c̦͍̲̞͍̩̙ḥ͚a̮͎̟̙͜ơ̩̹͎s̤.̝̝ ҉Z̡̖̜͖̰̣͉̜a͖̰͙̬͡l̲̫̳͍̩g̡̟̼̱͚̞̬ͅo̗͜.̟ +̦H̬̤̗̤͝e͜ ̜̥̝̻͍̟́w̕h̖̯͓o̝͙̖͎̱̮ ҉̺̙̞̟͈W̷̼̭a̺̪͍į͈͕̭͙̯̜t̶̼̮s̘͙͖̕ ̠̫̠B̻͍͙͉̳ͅe̵h̵̬͇̫͙i̹͓̳̳̮͎̫̕n͟d̴̪̜̖ ̰͉̩͇͙̲͞ͅT͖̼͓̪͢h͏͓̮̻e̬̝̟ͅ ̤̹̝W͙̞̝͔͇͝ͅa͏͓͔̹̼̣l̴͔̰̤̟͔ḽ̫.͕ +Z̮̞̠͙͔ͅḀ̗̞͈̻̗Ḷ͙͎̯̹̞͓G̻O̭̗̮ + +# Unicode Upsidedown +# +# Strings which contain unicode with an "upsidedown" effect (via http://www.upsidedowntext.com) + +˙ɐnbᴉlɐ ɐuƃɐɯ ǝɹolop ʇǝ ǝɹoqɐl ʇn ʇunpᴉpᴉɔuᴉ ɹodɯǝʇ poɯsnᴉǝ op pǝs 'ʇᴉlǝ ƃuᴉɔsᴉdᴉpɐ ɹnʇǝʇɔǝsuoɔ 'ʇǝɯɐ ʇᴉs ɹolop ɯnsdᴉ ɯǝɹo˥ +00˙Ɩ$- + +# Unicode font +# +# Strings which contain bold/italic/etc. versions of normal characters + +The quick brown fox jumps over the lazy dog +𝐓𝐡𝐞 𝐪𝐮𝐢𝐜𝐤 𝐛𝐫𝐨𝐰𝐧 𝐟𝐨𝐱 𝐣𝐮𝐦𝐩𝐬 𝐨𝐯𝐞𝐫 𝐭𝐡𝐞 𝐥𝐚𝐳𝐲 𝐝𝐨𝐠 +𝕿𝖍𝖊 𝖖𝖚𝖎𝖈𝖐 𝖇𝖗𝖔𝖜𝖓 𝖋𝖔𝖝 𝖏𝖚𝖒𝖕𝖘 𝖔𝖛𝖊𝖗 𝖙𝖍𝖊 𝖑𝖆𝖟𝖞 𝖉𝖔𝖌 +𝑻𝒉𝒆 𝒒𝒖𝒊𝒄𝒌 𝒃𝒓𝒐𝒘𝒏 𝒇𝒐𝒙 𝒋𝒖𝒎𝒑𝒔 𝒐𝒗𝒆𝒓 𝒕𝒉𝒆 𝒍𝒂𝒛𝒚 𝒅𝒐𝒈 +𝓣𝓱𝓮 𝓺𝓾𝓲𝓬𝓴 𝓫𝓻𝓸𝔀𝓷 𝓯𝓸𝔁 𝓳𝓾𝓶𝓹𝓼 𝓸𝓿𝓮𝓻 𝓽𝓱𝓮 𝓵𝓪𝔃𝔂 𝓭𝓸𝓰 +𝕋𝕙𝕖 𝕢𝕦𝕚𝕔𝕜 𝕓𝕣𝕠𝕨𝕟 𝕗𝕠𝕩 𝕛𝕦𝕞𝕡𝕤 𝕠𝕧𝕖𝕣 𝕥𝕙𝕖 𝕝𝕒𝕫𝕪 𝕕𝕠𝕘 +𝚃𝚑𝚎 𝚚𝚞𝚒𝚌𝚔 𝚋𝚛𝚘𝚠𝚗 𝚏𝚘𝚡 𝚓𝚞𝚖𝚙𝚜 𝚘𝚟𝚎𝚛 𝚝𝚑𝚎 𝚕𝚊𝚣𝚢 𝚍𝚘𝚐 +⒯⒣⒠ ⒬⒰⒤⒞⒦ ⒝⒭⒪⒲⒩ ⒡⒪⒳ ⒥⒰⒨⒫⒮ ⒪⒱⒠⒭ ⒯⒣⒠ ⒧⒜⒵⒴ ⒟⒪⒢ + +# Script Injection +# +# Strings which attempt to invoke a benign script injection; shows vulnerability to XSS + + +<script>alert('123');</script> + + +"> +'> +> + +< / script >< script >alert(123)< / script > + onfocus=JaVaSCript:alert(123) autofocus +" onfocus=JaVaSCript:alert(123) autofocus +' onfocus=JaVaSCript:alert(123) autofocus +<script>alert(123)</script> +ript>alert(123)ript> +--> +";alert(123);t=" +';alert(123);t=' +JavaSCript:alert(123) +;alert(123); +src=JaVaSCript:prompt(132) +">javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +javascript:alert(1); +'`"><\x3Cscript>javascript:alert(1) +'`"><\x00script>javascript:alert(1) +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +ABC
DEF +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +test +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +`"'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> +"`'> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +XXX + + + +<a href=http://foo.bar/#x=`y></a><img alt="`><img src=x:x onerror=javascript:alert(1)></a>"> +<!--[if]><script>javascript:alert(1)</script --> +<!--[if<img src=x onerror=javascript:alert(1)//]> --> +<script src="/\%(jscript)s"></script> +<script src="\\%(jscript)s"></script> +<IMG """><SCRIPT>alert("XSS")</SCRIPT>"> +<IMG SRC=javascript:alert(String.fromCharCode(88,83,83))> +<IMG SRC=# onmouseover="alert('xxs')"> +<IMG SRC= onmouseover="alert('xxs')"> +<IMG onmouseover="alert('xxs')"> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC=javascript:alert('XSS')> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +<IMG SRC="jav ascript:alert('XSS');"> +perl -e 'print "<IMG SRC=java\0script:alert(\"XSS\")>";' > out +<IMG SRC="  javascript:alert('XSS');"> +<SCRIPT/XSS SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<BODY onload!#$%&()*~+-_.,:;?@[/|\]^`=alert("XSS")> +<SCRIPT/SRC="http://ha.ckers.org/xss.js"></SCRIPT> +<<SCRIPT>alert("XSS");//<</SCRIPT> +<SCRIPT SRC=http://ha.ckers.org/xss.js?< B > +<SCRIPT SRC=//ha.ckers.org/.j> +<IMG SRC="javascript:alert('XSS')" +<iframe src=http://ha.ckers.org/scriptlet.html < +\";alert('XSS');// +<u oncopy=alert()> Copy me</u> +<i onwheel=alert(1)> Scroll over me </i> +<plaintext> +http://a/%%30%30 +</textarea><script>alert(123)</script> + +# SQL Injection +# +# Strings which can cause a SQL injection if inputs are not sanitized + +1;DROP TABLE users +1'; DROP TABLE users-- 1 +' OR 1=1 -- 1 +' OR '1'='1 +'; EXEC sp_MSForEachTable 'DROP TABLE ?'; -- + +% +_ + +# Server Code Injection +# +# Strings which can cause user to run code on server as a privileged user (c.f. https://news.ycombinator.com/item?id=7665153) + +- +-- +--version +--help +$USER +/dev/null; touch /tmp/blns.fail ; echo +`touch /tmp/blns.fail` +$(touch /tmp/blns.fail) +@{[system "touch /tmp/blns.fail"]} + +# Command Injection (Ruby) +# +# Strings which can call system commands within Ruby/Rails applications + +eval("puts 'hello world'") +System("ls -al /") +`ls -al /` +Kernel.exec("ls -al /") +Kernel.exit(1) +%x('ls -al /') + +# XXE Injection (XML) +# +# String which can reveal system files when parsed by a badly configured XML parser + +<?xml version="1.0" encoding="ISO-8859-1"?><!DOCTYPE foo [ <!ELEMENT foo ANY ><!ENTITY xxe SYSTEM "file:///etc/passwd" >]><foo>&xxe;</foo> + +# Unwanted Interpolation +# +# Strings which can be accidentally expanded into different strings if evaluated in the wrong context, e.g. used as a printf format string or via Perl or shell eval. Might expose sensitive data from the program doing the interpolation, or might just represent the wrong string. + +$HOME +$ENV{'HOME'} +%d +%s%s%s%s%s +{0} +%*.*s +%@ +%n +File:/// + +# File Inclusion +# +# Strings which can cause user to pull in files that should not be a part of a web server + +../../../../../../../../../../../etc/passwd%00 +../../../../../../../../../../../etc/hosts + +# Known CVEs and Vulnerabilities +# +# Strings that test for known vulnerabilities + +() { 0; }; touch /tmp/blns.shellshock1.fail; +() { _; } >_[$($())] { touch /tmp/blns.shellshock2.fail; } +<<< %s(un='%s') = %u ++++ATH0 + +# MSDOS/Windows Special Filenames +# +# Strings which are reserved characters in MSDOS/Windows + +CON +PRN +AUX +CLOCK$ +NUL +A: +ZZ: +COM1 +LPT1 +LPT2 +LPT3 +COM2 +COM3 +COM4 + +# IRC specific strings +# +# Strings that may occur on IRC clients that make security products freak out + +DCC SEND STARTKEYLOGGER 0 0 0 + +# Scunthorpe Problem +# +# Innocuous strings which may be blocked by profanity filters (https://en.wikipedia.org/wiki/Scunthorpe_problem) + +Scunthorpe General Hospital +Penistone Community Church +Lightwater Country Park +Jimmy Clitheroe +Horniman Museum +shitake mushrooms +RomansInSussex.co.uk +http://www.cum.qc.ca/ +Craig Cockburn, Software Specialist +Linda Callahan +Dr. Herman I. Libshitz +magna cum laude +Super Bowl XXX +medieval erection of parapets +evaluate +mocha +expression +Arsenal canal +classic +Tyson Gay +Dick Van Dyke +basement + +# Human injection +# +# Strings which may cause human to reinterpret worldview + +If you're reading this, you've been in a coma for almost 20 years now. We're trying a new technique. We don't know where this message will end up in your dream, but we hope it works. Please wake up, we miss you. + +# Terminal escape codes +# +# Strings which punish the fools who use cat/type on this file + +Roses are red, violets are blue. Hope you enjoy terminal hue +But now...for my greatest trick... +The quick brown fox... [Beeeep] + +# iOS Vulnerabilities +# +# Strings which crashed iMessage in various versions of iOS + +Powerلُلُصّبُلُلصّبُررً ॣ ॣh ॣ ॣ冗 +🏳0🌈️ +జ్ఞ‌ా + +# Persian special characters +# +# This is a four characters string which includes Persian special characters (گچپژ) + +گچپژ + +# jinja2 injection +# +# first one is supposed to raise "MemoryError" exception +# second, obviously, prints contents of /etc/passwd + +{% print 'x' * 64 * 1024**3 %} +{{ "".__class__.__mro__[2].__subclasses__()[40]("/etc/passwd").read() }} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java new file mode 100644 index 000000000..2b125f4f3 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java @@ -0,0 +1,557 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.bot.dialogs.prompts.PromptOptions; + + +import org.junit.Assert; +import org.junit.Test; + +public class ComponentDialogTests { + + @Test + public void CallDialogInParentComponent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + // DialogState state = dialogState.get(turnContext, () -> new + // DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + + ComponentDialog childComponent = new ComponentDialog("childComponent"); + + class Step1 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("Child started.").join(); + return stepContext.beginDialog("parentDialog", "test"); + } + } + + class Step2 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext() + .sendActivity(String.format("Child finished. Value: %s", stepContext.getResult())); + return stepContext.endDialog(); + } + } + + WaterfallStep[] childStep = new WaterfallStep[] {new Step1(), new Step2() }; + + childComponent.addDialog(new WaterfallDialog("childDialog", Arrays.asList(childStep))); + + ComponentDialog parentComponent = new ComponentDialog("parentComponent"); + parentComponent.addDialog(childComponent); + + class ParentStep implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(String.format("Parent called.", stepContext.getResult())); + return stepContext.endDialog(stepContext.getOptions()); + } + } + WaterfallStep[] parentStep = new WaterfallStep[] {new ParentStep() }; + + parentComponent.addDialog(new WaterfallDialog("parentDialog", Arrays.asList(parentStep))); + + dialogs.add(parentComponent); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("parentComponent", null).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + + return CompletableFuture.completedFuture(null); + }).send("Hi").assertReply("Child started.").assertReply("Parent called.") + .assertReply("Child finished. Value: test").startTest().join(); + } + + @Test + public void BasicWaterfallTest() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicWaterfallTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(createWaterfall()); + try { + dialogs.add(new NumberPrompt<Integer>("number", Integer.class)); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("test-waterfall", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void TelemetryBasicWaterfallTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + Assert.assertEquals(MyBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryHeterogeneousLoggerTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.findDialog("test-waterfall").setTelemetryClient(new MyBotTelemetryClient()); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryAddWaterfallTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + testComponentDialog.addDialog(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + } + + @Test + public void TelemetryNullUpdateAfterAddTest() throws UnsupportedDataTypeException { + TestComponentDialog testComponentDialog = new TestComponentDialog(); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + + testComponentDialog.setTelemetryClient(new MyBotTelemetryClient()); + testComponentDialog.addDialog(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + testComponentDialog.setTelemetryClient(null); + + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("test-waterfall").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("number").getTelemetryClient().getClass()); + Assert.assertEquals(NullBotTelemetryClient.class, + testComponentDialog.findDialog("C").getTelemetryClient().getClass()); + } + + @Test + public void BasicComponentDialogTest() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicComponentDialogTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + try { + dialogs.add(new TestComponentDialog()); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("TestComponentDialog", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void NestedComponentDialogTest() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("BasicComponentDialogTest", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + + try { + dialogs.add(new TestNestedComponentDialog()); + } catch (UnsupportedDataTypeException e) { + e.printStackTrace(); + } + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("TestNestedComponentDialog", null); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int value = (int) results.getResult(); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + + // step 1 + .assertReply("Enter a number.") + + // step 2 + .send("42") + .assertReply("Thanks for '42'") + .assertReply("Enter another number.") + + // step 3 and step 1 again (nested component) + .send("64") + .assertReply("Got '64'.") + .assertReply("Enter a number.") + + // step 2 again (from the nested component) + .send("101") + .assertReply("Thanks for '101'") + .assertReply("Enter another number.") + + // driver code + .send("5") + .assertReply("Bot received the number '5'.") + .startTest() + .join(); + } + + @Test + public void CallDialogDefinedInParentComponent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + Map<String, String> options = new HashMap<String, String>(); + options.put("value", "test"); + + ComponentDialog childComponent = new ComponentDialog("childComponent"); + + class Step1 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("Child started."); + return stepContext.beginDialog("parentDialog", options); + } + } + + class Step2 implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + Assert.assertEquals("test", (String) stepContext.getResult()); + stepContext.getContext().sendActivity("Child finished."); + return stepContext.endDialog(); + } + } + + WaterfallStep[] childActions = new WaterfallStep[] {new Step1(), new Step2() }; + + childComponent.addDialog(new WaterfallDialog("childDialog", Arrays.asList(childActions))); + + ComponentDialog parentComponent = new ComponentDialog("parentComponent"); + parentComponent.addDialog(childComponent); + + class ParentAction implements WaterfallStep { + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + Map<String, String> stepOptions = (Map<String, String>) stepContext.getOptions(); + Assert.assertNotNull(stepOptions); + Assert.assertTrue(stepOptions.containsKey("value")); + stepContext.getContext().sendActivity( + String.format("Parent called with: {%s}", stepOptions.get("value"))); + return stepContext.endDialog(stepOptions.get("value")); + } + } + + WaterfallStep[] parentActions = new WaterfallStep[] { + new ParentAction() + }; + + parentComponent.addDialog(new WaterfallDialog("parentDialog", Arrays.asList(parentActions))); + new TestFlow(adapter, (turnContext) -> { + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(parentComponent); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.beginDialog("parentComponent", null).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + turnContext.sendActivity(MessageFactory.text("Done")); + } + return CompletableFuture.completedFuture(null); + }) + .send("Hi") + .assertReply("Child started.") + .assertReply("Parent called with: test") + .assertReply("Child finished.") + .startTest(); + } + + // private static TestFlow CreateTestFlow(WaterfallDialog waterfallDialog) { + // var convoState = new ConversationState(new MemoryStorage()); + // var dialogState = convoState.CreateProperty<DialogState>("dialogState"); + + // var adapter = new TestAdapter() + // .Use(new AutoSaveStateMiddleware(convoState)); + + // var testFlow = new TestFlow(adapter, (turnContext) -> { + // var state = dialogState.Get(turnContext, () -> new DialogState()); + // var dialogs = new DialogSet(dialogState); + + // dialogs.Add(new CancelledComponentDialog(waterfallDialog)); + + // var dc = dialogs.CreateContext(turnContext); + + // var results = dc.ContinueDialog(cancellationToken); + // if (results.Status == DialogTurnStatus.Empty) { + // results = dc.BeginDialog("TestComponentDialog", null); + // } + + // if (results.Status == DialogTurnStatus.Cancelled) { + // turnContext.SendActivity(MessageFactory.Text($"Component dialog cancelled (result value is {results.Result?.toString()}).")); + // } else if (results.Status == DialogTurnStatus.Complete) { + // var value = (int)results.Result; + // turnContext.SendActivity(MessageFactory.Text($"Bot received the number '{value}'.")); + // } + // }); + // return testFlow; + // } + + private static WaterfallDialog createWaterfall() { + class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + return stepContext.prompt("number", options); + } + } + + class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext() + .sendActivity(MessageFactory.text(String.format("Thanks for '%d'", numberResult))).join(); + } + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter another number.")); + return stepContext.prompt("number", options); + } + } + + WaterfallStep[] steps = new WaterfallStep[] {new WaterfallStep1(), new WaterfallStep2() }; + return new WaterfallDialog("test-waterfall", Arrays.asList(steps)); + } + + private class MyBotTelemetryClient implements BotTelemetryClient { + private MyBotTelemetryClient() { + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + + } + + @Override + public void flush() { + + } + } + + private class TestComponentDialog extends ComponentDialog { + private TestComponentDialog() throws UnsupportedDataTypeException { + super("TestComponentDialog"); + addDialog(createWaterfall()); + addDialog(new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class)); + } + } + + private final class TestNestedComponentDialog extends ComponentDialog { + private TestNestedComponentDialog() throws UnsupportedDataTypeException { + super("TestNestedComponentDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new WaterfallStep1(), + new WaterfallStep2(), + new WaterfallStep3(), + }; + addDialog(new WaterfallDialog("test-waterfall", Arrays.asList(steps))); + addDialog(new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class)); + addDialog(new TestComponentDialog()); + } + class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + return stepContext.prompt("number", options); + } + } + + class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext() + .sendActivity(MessageFactory.text(String.format("Thanks for '%d'", numberResult))).join(); + } + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter another number.")); + return stepContext.prompt("number", options); + } + } + class WaterfallStep3 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("Got '%d'.", numberResult))); + } + return stepContext.beginDialog("TestComponentDialog", null); + } + } + + } + + // private class CancelledComponentDialog : ComponentDialog { + // public CancelledComponentDialog(Dialog waterfallDialog) { + // super("TestComponentDialog"); + // AddDialog(waterfallDialog); + // AddDialog(new NumberPrompt<int>("number", defaultLocale: Culture.English)); + // } + // } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java new file mode 100644 index 000000000..a03613d3f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogContainerTests.java @@ -0,0 +1,113 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +import org.junit.Assert; +import org.junit.Test; + +public class DialogContainerTests { + + @Test + public void DialogContainer_GetVersion() { + TestContainer ds = new TestContainer(); + String version1 = ds.getInternalVersion_Test(); + Assert.assertNotNull(version1); + + TestContainer ds2 = new TestContainer(); + String version2 = ds.getInternalVersion_Test(); + Assert.assertNotNull(version2); + Assert.assertEquals(version1, version2); + + DialogTestFunction testFunction = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld = new LamdbaDialog("Lamdba1", testFunction); + ld.setId("A"); + + ds2.getDialogs().add(ld); + String version3 = ds2.getInternalVersion_Test(); + Assert.assertNotNull(version3); + Assert.assertNotEquals(version2, version3); + + String version4 = ds2.getInternalVersion_Test(); + Assert.assertNotNull(version3); + Assert.assertEquals(version3, version4); + + TestContainer ds3 = new TestContainer(); + DialogTestFunction testFunction2 = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld2 = new LamdbaDialog("Lamdba1", testFunction2); + ld2.setId("A"); + ds3.getDialogs().add(ld2); + + String version5 = ds3.getInternalVersion_Test(); + Assert.assertNotNull(version5); + Assert.assertEquals(version5, version4); + + ds3.setProperty("foobar"); + String version6 = ds3.getInternalVersion_Test(); + Assert.assertNotNull(version6); + Assert.assertNotEquals(version6, version5); + + TestContainer ds4 = new TestContainer(); + ds4.setProperty("foobar"); + + DialogTestFunction testFunction3 = testFunction1 -> { + return CompletableFuture.completedFuture(null); + }; + LamdbaDialog ld3 = new LamdbaDialog("Lamdba1", testFunction3); + ld3.setId("A"); + + ds4.getDialogs().add(ld3); + String version7 = ds4.getInternalVersion_Test(); + Assert.assertNotNull(version7); + Assert.assertEquals(version7, version6); + } + + public class TestContainer extends DialogContainer { + + private String property; + + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options) { + return dc.endDialog(); + } + + @Override + public DialogContext createChildContext(DialogContext dc) { + return dc; + } + + public String getInternalVersion_Test() { + return getInternalVersion(); + } + + @Override + protected String getInternalVersion() { + StringBuilder result = new StringBuilder(); + result.append(super.getInternalVersion()); + if (getProperty() != null) { + result.append(getProperty()); + } + return result.toString(); + } + + /** + * @return the Property value as a String. + */ + public String getProperty() { + return this.property; + } + + /** + * @param withProperty The Property value. + */ + public void setProperty(String withProperty) { + this.property = withProperty; + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java new file mode 100644 index 000000000..a2f19afb5 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -0,0 +1,524 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.SendActivitiesHandler; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ResourceResponse; +import com.microsoft.bot.schema.ResultPair; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; +import org.junit.Test; + +public class DialogManagerTests { + + // An App D for a parent bot. + private final String _parentBotId = UUID.randomUUID().toString(); + + // An App D for a skill bot. + private final String _skillBotId = UUID.randomUUID().toString(); + + // Captures an EndOfConversation if it was sent to help with assertions. + private Activity _eocSent; + + // Property to capture the DialogManager turn results and do assertions. + private DialogManagerResult _dmTurnResult; + + /** + * Enum to handle different skill test cases. + */ + public enum SkillFlowTestCase { + /** + * DialogManager is executing on a root bot with no skills (typical standalone + * bot). + */ + RootBotOnly, + + /** + * DialogManager is executing on a root bot handling replies from a skill. + */ + RootBotConsumingSkill, + + /** + * DialogManager is executing in a skill that is called from a root and calling + * another skill. + */ + MiddleSkill, + + /** + * DialogManager is executing in a skill that is called from a parent (a root or + * another skill) but doesn't call another skill. + */ + LeafSkill + } + + @Test + public void DialogManager_ConversationState_PersistedAcrossTurns() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_AlternateProperty() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId, "dialogState", null, null) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_ConversationState_ClearedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(adaptiveDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("John") + .assertReply("Hello John, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void DialogManager_UserState_PersistedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog adaptiveDialog = CreateTestDialog("user.name"); + + CreateFlow(adaptiveDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(adaptiveDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + @Test + public void + DialogManager_UserState_NestedDialogs_PersistedAcrossConversations() { + String firstConversationId = UUID.randomUUID().toString(); + String secondConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); + + Dialog outerAdaptiveDialog = CreateTestDialog("user.name"); + + ComponentDialog componentDialog = new ComponentDialog(null); + componentDialog.addDialog(outerAdaptiveDialog); + + CreateFlow(componentDialog, storage, firstConversationId) + .send("hi") + .assertReply("Hello, what is your name?") + .send("Carlos") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + + CreateFlow(componentDialog, storage, secondConversationId) + .send("hi") + .assertReply("Hello Carlos, nice to meet you!") + .startTest() + .join(); + } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Leaf() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Parent() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_OnErrorEvent_Root() { + // TestUtilities.RunTestScript(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_DialogSet() { + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState) + // .Use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger(traceActivity: false))); + + // var rootDialog = new AdaptiveDialog() { + // Triggers new ArrayList<OnCondition>() { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog>() { + // new SetProperty() { + // Property = "conversation.dialogId", + // Value = "test" + // }, + // new BeginDialog() { + // Dialog = "=conversation.dialogId" + // }, + // new BeginDialog() { + // Dialog = "test" + // } + // } + // } + // } + // }; + + // var dm = new DialogManager(rootDialog); + // dm.Dialogs.Add(new SimpleDialog() { Id = "test" }); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .assertReply("simple") + // .assertReply("simple") + // .startTest(); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_ContainerRegistration() { + // var root = new AdaptiveDialog("root") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { new AdaptiveDialog("inner") } + // } + // } + // }; + + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState); + + // // The inner adaptive dialog should be registered on the DialogManager after OnTurn + // var dm = new DialogManager(root); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .startTest(); + + // Assert.NotNull(dm.Dialogs.Find("inner")); + // } + + // @Test + // public CompletableFuture<Void> DialogManager_ContainerRegistration_DoubleNesting() { + // // Create the following dialog tree + // // Root (adaptive) -> inner (adaptive) -> innerinner(adaptive) -> helloworld (SendActivity) + // var root = new AdaptiveDialog("root") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { + // new AdaptiveDialog("inner") { + // Triggers new ArrayList<OnCondition> { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog> { + // new AdaptiveDialog("innerinner") { + // Triggers new ArrayList<OnCondition>() { + // new OnBeginDialog() { + // Actions new ArrayList<Dialog>() { + // new SendActivity("helloworld") + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + // } + // }; + + // var storage = new MemoryStorage(); + // var convoState = new ConversationState(storage); + // var userState = new UserState(storage); + + // var adapter = new TestAdapter(); + // adapter + // .UseStorage(storage) + // .UseBotState(userState, convoState); + + // // The inner adaptive dialog should be registered on the DialogManager after OnTurn + // var dm = new DialogManager(root); + + // new TestFlow(adapter, (turnContext) -> { + // dm.OnTurn(turnContext: cancellationToken); + // }) + // .SendConversationUpdate() + // .startTest(); + + // // Top level containers should be registered + // Assert.NotNull(dm.Dialogs.Find("inner")); + + // // Mid level containers should be registered + // Assert.NotNull(dm.Dialogs.Find("innerinner")); + + // // Leaf nodes / non-contaners should not be registered + // Assert.DoesNotContain(dm.Dialogs.GetDialogs(), d -> d.GetType() == typeof(SendActivity)); + // } + + // public CompletableFuture<Void> HandlesBotAndSkillsTestCases(SkillFlowTestCase testCase, boolean shouldSendEoc) { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: testCase, locale: "en-GB").send("Hi") + // .assertReply("Hello, what is your name?") + // .send("SomeName") + // .assertReply("Hello SomeName, nice to meet you!") + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Complete, _dmTurnResult.TurnResult.Status); + + // if (shouldSendEoc) { + // Assert.NotNull(_eocSent); + // Assert.Equal(ActivityTypes.EndOfConversation, _eocSent.Type); + // Assert.Equal("SomeName", _eocSent.Value); + // Assert.Equal("en-GB", _eocSent.Locale); + // } else { + // Assert.Null(_eocSent); + // } + // } + + // @Test + // public CompletableFuture<Void> SkillHandlesEoCFromParent() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var eocActivity = new Activity(ActivityTypes.EndOfConversation); + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send("hi") + // .assertReply("Hello, what is your name?") + // .send(eocActivity) + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Cancelled, _dmTurnResult.TurnResult.Status); + // } + + // @Test + // public CompletableFuture<Void> SkillHandlesRepromptFromParent() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send("hi") + // .assertReply("Hello, what is your name?") + // .send(repromptEvent) + // .assertReply("Hello, what is your name?") + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Waiting, _dmTurnResult.TurnResult.Status); + // } + + // @Test + // public CompletableFuture<Void> SkillShouldReturnEmptyOnRepromptWithNoDialog() { + // var firstConversationId = Guid.NewGuid().toString(); + // var storage = new MemoryStorage(); + + // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + + // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + + // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) + // .send(repromptEvent) + // .startTest(); + + // Assert.Equal(DialogTurnStatus.Empty, _dmTurnResult.TurnResult.Status); + // } + + private Dialog CreateTestDialog(String property) { + return new AskForNameDialog(property.replace(".", ""), property); + } + + private TestFlow CreateFlow(Dialog dialog, Storage storage, String conversationId) { + return this.CreateFlow(dialog, storage, conversationId, null, SkillFlowTestCase.RootBotOnly, null); + } + + private TestFlow CreateFlow(Dialog dialog, Storage storage, String conversationId, String dialogStateProperty, + SkillFlowTestCase testCase, String locale) { + if (testCase == null) { + testCase = SkillFlowTestCase.RootBotOnly; + } + ConversationState convoState = new ConversationState(storage); + UserState userState = new UserState(storage); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(conversationId, "User1", "Bot")); + adapter.useStorage(storage) + .useBotState(userState, convoState) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + if (!StringUtils.isBlank(locale)) { + adapter.setLocale(locale); + } + final SkillFlowTestCase finalTestCase = testCase; + DialogManager dm = new DialogManager(dialog, dialogStateProperty); + return new TestFlow(adapter, (turnContext) -> { + if (finalTestCase != SkillFlowTestCase.RootBotOnly) { + throw new NotImplementedException("CreateFlow is only capable of RootBotOnly test for now."); + // Create a skill ClaimsIdentity and put it in TurnState so SkillValidation.IsSkillClaim() returns true. + // ClaimsIdentity claimsIdentity = new ClaimsIdentity(); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); + // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); + // turnContext.TurnState.Add(BotAdapter.BotIdentityKey, claimsIdentity); + + // if (testCase == SkillFlowTestCase.RootBotConsumingSkill) { + // // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. + // // This emulates a response coming to a root bot through SkillHandler. + // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = AuthenticationConstants.ToChannelFromBotOAuthScope }); + // } + + // if (testCase == SkillFlowTestCase.MiddleSkill) { + // // Simulate the SkillConversationReference with a parent Bot D stored in TurnState. + // // This emulates a response coming to a skill from another skill through SkillHandler. + // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = _parentBotId }); + // } + } + + turnContext.onSendActivities(new TestSendActivities()); + + // Capture the last DialogManager turn result for assertions. + _dmTurnResult = dm.onTurn(turnContext).join(); + + return CompletableFuture.completedFuture(null); + }); + } + + class TestSendActivities implements SendActivitiesHandler { + @Override + public CompletableFuture<ResourceResponse[]> invoke(TurnContext context, List<Activity> activities, + Supplier<CompletableFuture<ResourceResponse[]>> next) { + for (Activity activity : activities) { + if (activity.getType() == ActivityTypes.END_OF_CONVERSATION) { + _eocSent = activity; + break; + } + } + return next.get(); + } + } + + private class AskForNameDialog extends ComponentDialog implements DialogDependencies { + private final String property; + + private AskForNameDialog(String id, String property) { + super(id); + addDialog(new TextPrompt("prompt")); + this.property = property; + } + + @Override + public List<Dialog> getDependencies() { + return new ArrayList<Dialog>(getDialogs().getDialogs()); + } + + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext outerDc, Object options) { + + ResultPair<String> value = outerDc.getState().tryGetValue(property, String.class); + if (value.getLeft()) { + outerDc.getContext().sendActivity(String.format("Hello %s, nice to meet you!", value.getRight())); + return outerDc.endDialog(value.getRight()); + } + + PromptOptions pOptions = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Hello, what is your name?"); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Hello, what is your name?"); + pOptions.setPrompt(prompt); + pOptions.setRetryPrompt(retryPrompt); + + return outerDc.beginDialog("prompt", pOptions); + } + + @Override + public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext outerDc, + DialogReason reason, Object result) { + outerDc.getState().setValue(property, result); + outerDc.getContext().sendActivity(String.format("Hello %s, nice to meet you!", result)).join(); + return outerDc.endDialog(result); + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java new file mode 100644 index 000000000..4ba2e3253 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogSetTests.java @@ -0,0 +1,211 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Map; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TestUtilities; +import com.microsoft.bot.builder.TurnContext; + +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Assert; +import org.junit.Test; + +public class DialogSetTests { + + @Test + public void DialogSet_ConstructorValid() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + new DialogSet(dialogStateProperty); + } + + @Test + public void DialogSet_ConstructorNullProperty() { + Assert.assertThrows(IllegalArgumentException.class, () -> new DialogSet(null)); + } + + @Test + public void DialogSet_CreateContext() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty); + TurnContext context = TestUtilities.createEmptyContext(); + ds.createContext(context); + } + + @Test + public void DialogSet_NullCreateContext() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty); + ds.createContext(null).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void DialogSet_AddWorks() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + WaterfallDialog dialogA = new WaterfallDialog("A", null); + WaterfallDialog dialogB = new WaterfallDialog("B", null); + DialogSet ds = new DialogSet(dialogStateProperty).add(dialogA).add(dialogB); + + Assert.assertNotNull(ds.find("A")); + Assert.assertNotNull(ds.find("B")); + Assert.assertNull(ds.find("C")); + } + + @Test + public void DialogSet_GetVersion() { + DialogSet ds = new DialogSet(); + String version1 = ds.getVersion(); + Assert.assertNotNull(version1); + + DialogSet ds2 = new DialogSet(); + String version2 = ds.getVersion(); + Assert.assertNotNull(version2); + Assert.assertEquals(version1, version2); + + ds2.add(new LamdbaDialog("A", null)); + String version3 = ds2.getVersion(); + Assert.assertNotNull(version3); + Assert.assertNotEquals(version2, version3); + + String version4 = ds2.getVersion(); + Assert.assertNotNull(version3); + Assert.assertEquals(version3, version4); + + DialogSet ds3 = new DialogSet().add(new LamdbaDialog("A", null)); + + String version5 = ds3.getVersion(); + Assert.assertNotNull(version5); + Assert.assertEquals(version5, version4); + } + + @Test + public void DialogSet_TelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + + MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient(); + ds.setTelemetryClient(botTelemetryClient); + + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_NullTelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + + ds.setTelemetryClient(null); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_AddTelemetrySet() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty).add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)); + + ds.setTelemetryClient(new MyBotTelemetryClient()); + ds.add(new WaterfallDialog("C", null)); + + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("C").getTelemetryClient().getClass().getSimpleName()); + } + + @Test + public void DialogSet_HeterogeneousLoggers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogStateProperty = convoState.createProperty("dialogState"); + DialogSet ds = new DialogSet(dialogStateProperty) + .add(new WaterfallDialog("A", null)) + .add(new WaterfallDialog("B", null)) + .add(new WaterfallDialog("C", null)); + + // Make sure we can (after Adding) the TelemetryClient and "sticks" + ds.find("C").setTelemetryClient(new MyBotTelemetryClient()); + + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("A").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(NullBotTelemetryClient.class.getSimpleName(), + ds.find("B").getTelemetryClient().getClass().getSimpleName()); + Assert.assertEquals(MyBotTelemetryClient.class.getSimpleName(), + ds.find("C").getTelemetryClient().getClass().getSimpleName()); + } + + private final class MyBotTelemetryClient implements BotTelemetryClient { + private MyBotTelemetryClient() { + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackAvailability is not implemented"); + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + throw new NotImplementedException("trackDependency is not implemented"); + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackEvent is not implemented"); + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackException is not implemented"); + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + throw new NotImplementedException("trackTrace is not implemented"); + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackDialogView is not implemented"); + } + + @Override + public void flush() { + throw new NotImplementedException("flush is not implemented"); + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java new file mode 100644 index 000000000..ec38ca1a7 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import static org.junit.Assert.fail; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; +import com.microsoft.bot.dialogs.memory.PathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtAtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.AtPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.DollarPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.HashPathResolver; +import com.microsoft.bot.dialogs.memory.pathresolvers.PercentPathResolver; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; +import org.junit.Assert; + +/** + * Test for the DialogStateManager. + */ +public class DialogStateManagerTests { + + @Rule + public TestName name = new TestName(); + + @Test + public void testMemoryScopeNullChecks() { + DialogTestFunction testFunction = dialogContext -> { + DialogStateManagerConfiguration configuration = dialogContext.getState().getConfiguration(); + for (MemoryScope scope : configuration.getMemoryScopes()) { + try { + scope.getMemory(null); + fail(String.format("Should have thrown exception with null for getMemory %s", + scope.getClass().getName())); + } catch (Exception ex) { + } + try { + scope.setMemory(null, new Object()); + fail(String.format("Should have thrown exception with null for setMemory %s", + scope.getClass().getName())); + } catch (Exception ex) { + + } + } + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void testPathResolverNullChecks() { + DialogsComponentRegistration registration = new DialogsComponentRegistration(); + + for (PathResolver resolver : registration.getPathResolvers()) { + try { + resolver.transformPath(null); + fail(String.format( + "Should have thrown exception with null for matches()" + resolver.getClass().getName())); + + } catch (Exception ex) { + + } + } + } + + @Test + public void testMemorySnapshot() { + DialogTestFunction testFunction = dialogContext -> { + JsonNode snapshot = dialogContext.getState().getMemorySnapshot(); + DialogStateManager dsm = new DialogStateManager(dialogContext, null); + for (MemoryScope memoryScope : dsm.getConfiguration().getMemoryScopes()) { + if (memoryScope.getIncludeInSnapshot()) { + Assert.assertNotNull(snapshot.get(memoryScope.getName())); + } else { + Assert.assertNull(snapshot.get(memoryScope.getName())); + } + } + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void testPathResolverTransform() { + // dollar tests + Assert.assertEquals("$", new DollarPathResolver().transformPath("$")); + Assert.assertEquals("$23", new DollarPathResolver().transformPath("$23")); + Assert.assertEquals("$$", new DollarPathResolver().transformPath("$$")); + Assert.assertEquals("dialog.foo", new DollarPathResolver().transformPath("$foo")); + Assert.assertEquals("dialog.foo.bar", new DollarPathResolver().transformPath("$foo.bar")); + Assert.assertEquals("dialog.foo.bar[0]", new DollarPathResolver().transformPath("$foo.bar[0]")); + + // hash tests + Assert.assertEquals("#", new HashPathResolver().transformPath("#")); + Assert.assertEquals("#23", new HashPathResolver().transformPath("#23")); + Assert.assertEquals("##", new HashPathResolver().transformPath("##")); + Assert.assertEquals("turn.recognized.intents.foo", new HashPathResolver().transformPath("#foo")); + Assert.assertEquals("turn.recognized.intents.foo.bar", new HashPathResolver().transformPath("#foo.bar")); + Assert.assertEquals("turn.recognized.intents.foo.bar[0]", new HashPathResolver().transformPath("#foo.bar[0]")); + + // @ test + Assert.assertEquals("@", new AtPathResolver().transformPath("@")); + Assert.assertEquals("@23", new AtPathResolver().transformPath("@23")); + Assert.assertEquals("@@foo", new AtPathResolver().transformPath("@@foo")); + Assert.assertEquals("turn.recognized.entities.foo.first()", new AtPathResolver().transformPath("@foo")); + Assert.assertEquals("turn.recognized.entities.foo.first().bar", new AtPathResolver().transformPath("@foo.bar")); + + // @@ teest + Assert.assertEquals("@@", new AtAtPathResolver().transformPath("@@")); + Assert.assertEquals("@@23", new AtAtPathResolver().transformPath("@@23")); + Assert.assertEquals("@@@@", new AtAtPathResolver().transformPath("@@@@")); + Assert.assertEquals("turn.recognized.entities.foo", new AtAtPathResolver().transformPath("@@foo")); + + // % config tests + Assert.assertEquals("%", new PercentPathResolver().transformPath("%")); + Assert.assertEquals("%23", new PercentPathResolver().transformPath("%23")); + Assert.assertEquals("%%", new PercentPathResolver().transformPath("%%")); + Assert.assertEquals("class.foo", new PercentPathResolver().transformPath("%foo")); + Assert.assertEquals("class.foo.bar", new PercentPathResolver().transformPath("%foo.bar")); + Assert.assertEquals("class.foo.bar[0]", new PercentPathResolver().transformPath("%foo.bar[0]")); + } + + @Test + public void testSimpleValues() { + DialogTestFunction testFunction = dc -> { + // simple value types + dc.getState().setValue("UseR.nuM", 15); + dc.getState().setValue("uSeR.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("user.num", 0, Integer.class)); + + dc.getState().setValue("UsEr.StR", "string1"); + dc.getState().setValue("usER.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("USer.str", "", String.class)); + + // simple value types + dc.getState().setValue("ConVErsation.nuM", 15); + dc.getState().setValue("ConVErSation.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("conversation.num", 0, Integer.class)); + + dc.getState().setValue("ConVErsation.StR", "string1"); + dc.getState().setValue("CoNVerSation.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("conversation.str", "", String.class)); + + // simple value types + dc.getState().setValue("tUrn.nuM", 15); + dc.getState().setValue("turN.NuM", 25); + Assert.assertEquals(25, (int) dc.getState().getValue("turn.num", 0, Integer.class)); + + dc.getState().setValue("tuRn.StR", "string1"); + dc.getState().setValue("TuRn.STr", "string2"); + Assert.assertEquals("string2", dc.getState().getValue("turn.str", "", String.class)); + + return CompletableFuture.completedFuture(null); + }; + + createDialogContext(testFunction).startTest().join(); + } + + @Test + public void TestEntitiesRetrieval() { + DialogTestFunction testFunction = dc -> { + + ObjectMapper mapper = new ObjectMapper(); + + String[] array = new String[] { + "test1", + "test2", + "test3" + }; + + String[] array2 = new String[] { + "testx", + "testy", + "testz" + }; + + String[][] arrayarray = new String[][] { + array2, + array + }; + + JsonNode arrayNode = mapper.valueToTree(array); + JsonNode arrayArrayNode = mapper.valueToTree(arrayarray); + + dc.getState().setValue("turn.recognized.entities.single", arrayNode); + dc.getState().setValue("turn.recognized.entities.double", arrayArrayNode); + + Assert.assertEquals("test1", dc.getState().getValue("@single", new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("@double", new String(), String.class)); + Assert.assertEquals("test1", dc.getState().getValue("turn.recognized.entities.single.First()", + new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("turn.recognized.entities.double.First()", + new String(), String.class)); + + + + // arrayarray = new JArray(); + ArrayNode secondArray = mapper.createArrayNode(); + ArrayNode array1Node = mapper.createArrayNode(); + ObjectNode node1 = mapper.createObjectNode(); + node1.put("name", "test1"); + ObjectNode node2 = mapper.createObjectNode(); + node2.put("name", "test2"); + ObjectNode node3 = mapper.createObjectNode(); + node3.put("name", "test3"); + array1Node.addAll(Arrays.asList(node1, node2, node3)); + + ArrayNode array2Node = mapper.createArrayNode(); + ObjectNode node1a = mapper.createObjectNode(); + node1a.put("name", "testx"); + ObjectNode node2a = mapper.createObjectNode(); + node2a.put("name", "testy"); + ObjectNode node3a = mapper.createObjectNode(); + node3a.put("name", "testz"); + array2Node.addAll(Arrays.asList(node1a, node2a, node3a)); + secondArray.addAll(Arrays.asList(array2Node, array1Node)); + dc.getState().setValue("turn.recognized.entities.single", array1Node); + dc.getState().setValue("turn.recognized.entities.double", secondArray); + + Assert.assertEquals("test1", dc.getState().getValue("@single.name", new String(), String.class)); + Assert.assertEquals("test1", dc.getState().getValue("turn.recognized.entities.single.First().name", + new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("@double.name", new String(), String.class)); + Assert.assertEquals("testx", dc.getState().getValue("turn.recognized.entities.double.First().name", + new String(), String.class)); + return CompletableFuture.completedFuture(null); + + }; + + createDialogContext(testFunction).startTest().join(); + } + + + private TestFlow createDialogContext(DialogTestFunction handler) { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(name.getMethodName(), "User1", "Bot")) + .useStorage(new MemoryStorage()).useBotState(new UserState(new MemoryStorage())) + .useBotState(new ConversationState(new MemoryStorage())); + + DialogManager dm = new DialogManager(new LamdbaDialog(name.getMethodName(), handler), name.getMethodName()); + // dm.getInitialTurnState().add(new ResourceExplorer()); + return new TestFlow(adapter, (turnContext -> { + dm.onTurn(turnContext); + return CompletableFuture.completedFuture(null); + })).sendConverationUpdate(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java new file mode 100644 index 000000000..e425793ab --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.concurrent.CompletableFuture; + +/** + * An inteface to run the test. + */ +interface DialogTestFunction { + /** + * Method to run the test. + * @param dc DialogContext to run the test against. + * @return a CompletableFuture + */ + CompletableFuture<Void> runTest(DialogContext dc); +} + +/** + * Dialog that can process a test. + */ +public class LamdbaDialog extends Dialog { + + private DialogTestFunction func; + + /** + * + * @param testName The name of the test being performed + * @param function The function that will perform the test. + */ + public LamdbaDialog(String testName, DialogTestFunction function) { + super(testName); + func = function; + } + + /** + * Override the beginDialog method and call our test function here. + */ + @Override + public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options) { + func.runTest(dc).join(); + return dc.endDialog(); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java new file mode 100644 index 000000000..fbf000797 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java @@ -0,0 +1,125 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.scopes.BotStateMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ConversationMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogClassMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogContextMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.DialogMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.ThisMemoryScope; +import com.microsoft.bot.dialogs.memory.scopes.UserMemoryScope; + +import org.javatuples.Pair; + +import org.junit.Assert; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +public class MemoryScopeTests { + + @Rule + public TestName testName = new TestName(); + + public TestFlow CreateDialogContext(DialogTestFunction handler) { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference(testName.getMethodName(), "User1", "Bot")); + adapter.useStorage(new MemoryStorage()).useBotState(new UserState(new MemoryStorage())) + .useBotState(new ConversationState(new MemoryStorage())); + DialogManager dm = new DialogManager(new LamdbaDialog(testName.getMethodName(), handler), null); + return new TestFlow(adapter, (turnContext) -> { + return dm.onTurn(turnContext).thenAccept(null); + }).sendConverationUpdate(); + } + + @Test + public void SimpleMemoryScopesTest() { + DialogTestFunction testFunction = dc -> { + DialogStateManager dsm = dc.getState(); + for (MemoryScope memoryScope : dsm.getConfiguration().getMemoryScopes()) { + if (!(memoryScope instanceof ThisMemoryScope || memoryScope instanceof DialogMemoryScope + || memoryScope instanceof ClassMemoryScope || memoryScope instanceof DialogClassMemoryScope + || memoryScope instanceof DialogContextMemoryScope)) { + Object memory = memoryScope.getMemory(dc); + Assert.assertNotNull(memory); + ObjectPath.setPathValue(memory, "test", 15); + memory = memoryScope.getMemory(dc); + Assert.assertEquals(15, (int) ObjectPath.getPathValue(memory, "test", Integer.class)); + ObjectPath.setPathValue(memory, "test", 25); + memory = memoryScope.getMemory(dc); + Assert.assertEquals(25, (int) ObjectPath.getPathValue(memory, "test", Integer.class)); + memory = memoryScope.getMemory(dc); + ObjectPath.setPathValue(memory, "source", "destination"); + ObjectPath.setPathValue(memory, "{source}", 24); + Assert.assertEquals(24, (int) ObjectPath.getPathValue(memory, "{source}", Integer.class)); + ObjectPath.removePathValue(memory, "{source}"); + Assert.assertNull(ObjectPath.tryGetPathValue(memory, "{source}", Integer.class)); + ObjectPath.removePathValue(memory, "source"); + Assert.assertNull(ObjectPath.tryGetPathValue(memory, "{source}", Integer.class)); + } + } + return CompletableFuture.completedFuture(null); + }; + CreateDialogContext(testFunction).startTest(); + } + + @Test + public void BotStateMemoryScopeTest() { + DialogTestFunction testFunction = dc -> { + DialogStateManager dsm = dc.getState(); + Storage storage = dc.getContext().getTurnState().get(MemoryStorage.class); + UserState userState = dc.getContext().getTurnState().get(UserState.class); + ConversationState conversationState = dc.getContext().getTurnState().get(ConversationState.class); + CustomState customState = new CustomState(storage); + + dc.getContext().getTurnState().add(customState); + + List<Pair<BotState, MemoryScope>> stateScopes = new ArrayList<Pair<BotState, MemoryScope>>(); + stateScopes.add(Pair.with(userState, new UserMemoryScope())); + stateScopes.add(Pair.with(conversationState, new ConversationMemoryScope())); + stateScopes.add(Pair.with(customState, + new BotStateMemoryScope<CustomState>(CustomState.class, "CustomState"))); + + for (Pair<BotState, MemoryScope> stateScope : stateScopes) { + final String name = "test-name"; + final String value = "test-value"; + stateScope.getValue0().createProperty(name).set(dc.getContext(), value); + + Object memory = stateScope.getValue1().getMemory(dc); + + Assert.assertEquals(value, ObjectPath.getPathValue(memory, name, String.class)); + } + return CompletableFuture.completedFuture(null); + }; + CreateDialogContext(testFunction).startTest(); + } + + public class CustomState extends BotState { + public CustomState(Storage storage) { + super(storage, "Not the name of the type"); + } + + @Override + public String getStorageKey(TurnContext turnContext) { + // TODO Auto-generated method stub + return "botstate/custom/etc"; + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java new file mode 100644 index 000000000..cb6221dcf --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java @@ -0,0 +1,506 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.schema.Serialization; +import java.io.IOException; +import org.junit.Assert; +import org.junit.Test; + +public class ObjectPathTests { + @Test + public void typed_OnlyDefaultTest() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.bool = true; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options() { }; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void typed_OnlyOverlay() { + Options defaultOptions = new Options(); + + Options overlay = new Options(); + overlay.lastName = "Smith"; + overlay.firstName = "Fred"; + overlay.age = 22; + overlay.bool = true; + overlay.location = new Location(); + overlay.location.latitude = 1.2312312F; + overlay.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, overlay.firstName); + Assert.assertEquals(result.age, overlay.age); + Assert.assertEquals(result.bool, overlay.bool); + Assert.assertEquals(result.location.latitude, overlay.location.latitude, .01); + Assert.assertEquals(result.location.longitude, overlay.location.longitude, .01); + } + + @Test + public void typed_FullOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options(); + overlay.lastName = "Grant"; + overlay.firstName = "Eddit"; + overlay.age = 32; + overlay.bool = true; + overlay.location = new Location(); + overlay.location.latitude = 2.2312312F; + overlay.location.longitude = 2.234234F; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, overlay.firstName); + Assert.assertEquals(result.age, overlay.age); + Assert.assertEquals(result.bool, overlay.bool); + Assert.assertEquals(result.location.latitude, overlay.location.latitude, .01); + Assert.assertEquals(result.location.longitude, overlay.location.longitude, .01); + } + + @Test + public void typed_PartialOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options overlay = new Options(); + overlay.lastName = "Grant"; + + Options result = ObjectPath.merge(defaultOptions, overlay); + + Assert.assertEquals(result.lastName, overlay.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void anonymous_OnlyDefaultTest() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Options overlay = new Options() { }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.getClass().getDeclaredField("lastName").get(defaultOptions)); + Assert.assertEquals(result.firstName, defaultOptions.getClass().getDeclaredField("firstName").get(defaultOptions)); + Assert.assertEquals(result.age, defaultOptions.getClass().getDeclaredField("age").get(defaultOptions)); + Assert.assertEquals(result.bool, defaultOptions.getClass().getDeclaredField("bool").get(defaultOptions)); + + Object loc = defaultOptions.getClass().getDeclaredField("location").get(defaultOptions); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_OnlyOverlay() throws NoSuchFieldException, IllegalAccessException { + Options defaultOptions = new Options() { }; + + Object overlay = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, overlay.getClass().getDeclaredField("firstName").get(overlay)); + Assert.assertEquals(result.age, overlay.getClass().getDeclaredField("age").get(overlay)); + Assert.assertEquals(result.bool, overlay.getClass().getDeclaredField("bool").get(overlay)); + + Object loc = overlay.getClass().getDeclaredField("location").get(overlay); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_FullOverlay() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Object overlay = new Object() { + public String lastName = "Grant"; + public String firstName = "Eddit"; + public Integer age = 32; + public Boolean bool = false; + public Object location = new Object() { + public Float latitude = 2.2312312F; + public Float longitude = 2.234234F; + }; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, overlay.getClass().getDeclaredField("firstName").get(overlay)); + Assert.assertEquals(result.age, overlay.getClass().getDeclaredField("age").get(overlay)); + Assert.assertEquals(result.bool, overlay.getClass().getDeclaredField("bool").get(overlay)); + + Object loc = overlay.getClass().getDeclaredField("location").get(overlay); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void anonymous_PartialOverlay() throws NoSuchFieldException, IllegalAccessException { + Object defaultOptions = new Object() { + public String lastName = "Smith"; + public String firstName = "Fred"; + public Integer age = 22; + public Boolean bool = true; + public Object location = new Object() { + public Float latitude = 1.2312312F; + public Float longitude = 3.234234F; + }; + }; + + Object overlay = new Object() { + public String lastName = "Grant"; + }; + + Options result = ObjectPath.merge(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.getClass().getDeclaredField("lastName").get(overlay)); + Assert.assertEquals(result.firstName, defaultOptions.getClass().getDeclaredField("firstName").get(defaultOptions)); + Assert.assertEquals(result.age, defaultOptions.getClass().getDeclaredField("age").get(defaultOptions)); + Assert.assertEquals(result.bool, defaultOptions.getClass().getDeclaredField("bool").get(defaultOptions)); + + Object loc = defaultOptions.getClass().getDeclaredField("location").get(defaultOptions); + Assert.assertEquals(result.location.latitude.floatValue(), + ((Float) loc.getClass().getDeclaredField("latitude").get(loc)).floatValue(), .01); + Assert.assertEquals(result.location.longitude.floatValue(), ((Float) loc.getClass().getDeclaredField("longitude").get(loc)).floatValue(), .01); + } + + @Test + public void jsonNode_OnlyDefaultTest() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options()); + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.get("lastName").asText()); + Assert.assertEquals(result.firstName, defaultOptions.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), defaultOptions.get("age").asInt()); + Assert.assertEquals(result.bool, defaultOptions.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, defaultOptions.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, defaultOptions.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_OnlyOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options()); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, overlay.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), overlay.get("age").asInt()); + Assert.assertEquals(result.bool, overlay.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, overlay.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, overlay.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_FullOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Grant"; + firstName = "Eddit"; + age = 32; + bool = false; + location = new Location() {{ + latitude = 2.2312312F; + longitude = 2.234234F; + }}; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, overlay.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), overlay.get("age").asInt()); + Assert.assertEquals(result.bool, overlay.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, overlay.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, overlay.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void jsonNode_PartialOverlay() { + JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ + lastName = "Smith"; + firstName = "Fred"; + age = 22; + bool = true; + location = new Location() {{ + latitude = 1.2312312F; + longitude = 3.234234F; + }}; + }}); + + JsonNode overlay = Serialization.objectToTree(new Options() {{ + lastName = "Grant"; + }}); + + + Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); + + Assert.assertEquals(result.lastName, overlay.get("lastName").asText()); + Assert.assertEquals(result.firstName, defaultOptions.get("firstName").asText()); + Assert.assertEquals(result.age.intValue(), defaultOptions.get("age").asInt()); + Assert.assertEquals(result.bool, defaultOptions.get("bool").asBoolean()); + Assert.assertEquals( + result.location.latitude, defaultOptions.get("location").findValue("latitude").asDouble(), .01); + Assert.assertEquals( + result.location.longitude, defaultOptions.get("location").findValue("longitude").asDouble(), .01); + } + + @Test + public void nullStartObject() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(null, defaultOptions, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void nullOverlay() { + Options defaultOptions = new Options(); + defaultOptions.lastName = "Smith"; + defaultOptions.firstName = "Fred"; + defaultOptions.age = 22; + defaultOptions.location = new Location(); + defaultOptions.location.latitude = 1.2312312F; + defaultOptions.location.longitude = 3.234234F; + + Options result = ObjectPath.merge(defaultOptions, null, Options.class); + + Assert.assertEquals(result.lastName, defaultOptions.lastName); + Assert.assertEquals(result.firstName, defaultOptions.firstName); + Assert.assertEquals(result.age, defaultOptions.age); + Assert.assertEquals(result.bool, defaultOptions.bool); + Assert.assertEquals(result.location.latitude, defaultOptions.location.latitude, .01); + Assert.assertEquals(result.location.longitude, defaultOptions.location.longitude, .01); + } + + @Test + public void tryGetPathValue() throws IOException { + PathTest test = new PathTest(); + test.test = "test"; + + test.options = new Options(); + test.options.age = 22; + test.options.firstName = "joe"; + test.options.lastName = "blow"; + test.options.bool = true; + + test.bar = new Bar(); + test.bar.numIndex = 2; + test.bar.strIndex = "FirstName"; + test.bar.objIndex = "options"; + test.bar.options = new Options(); + test.bar.options.age = 1; + test.bar.options.firstName = "joe"; + test.bar.options.lastName = "blow"; + test.bar.options.bool = false; + test.bar.numbers = new int[] { 1, 2, 3, 4, 5 }; + + // test with pojo + { + Assert.assertEquals(test, ObjectPath.getPathValue(test, "", PathTest.class)); + Assert.assertEquals(test.test, ObjectPath.getPathValue(test, "test", String.class)); + Assert.assertEquals( + test.bar.options.age, + ObjectPath.getPathValue(test, "bar.options.age", Integer.class) + ); + + Options options = ObjectPath.tryGetPathValue(test, "options", Options.class); + Assert.assertNotNull(options); + Assert.assertEquals(test.options.age, options.age); + Assert.assertEquals(test.options.firstName, options.firstName); + + Options barOptions = ObjectPath.tryGetPathValue(test, "bar.options", Options.class); + Assert.assertNotNull(barOptions); + Assert.assertEquals(test.bar.options.age, barOptions.age); + Assert.assertEquals(test.bar.options.firstName, barOptions.firstName); + + int[] numbers = ObjectPath.tryGetPathValue(test, "bar.numbers", int[].class); + Assert.assertEquals(5, numbers.length); + + int number = ObjectPath.tryGetPathValue(test, "bar.numbers[1]", Integer.class); + Assert.assertEquals(2, number); + + number = ObjectPath.tryGetPathValue(test, "bar['options'].Age", Integer.class); + Assert.assertEquals(1, number); + + number = ObjectPath.tryGetPathValue(test, "bar[\"options\"].Age", Integer.class); + Assert.assertEquals(1, number); + + number = ObjectPath.tryGetPathValue(test, "bar.numbers[bar.numIndex]", Integer.class); + Assert.assertEquals(3, number); + + number = ObjectPath + .tryGetPathValue(test, "bar.numbers[bar[bar.objIndex].Age]", Integer.class); + Assert.assertEquals(2, number); + + String name = ObjectPath + .tryGetPathValue(test, "bar.options[bar.strIndex]", String.class); + Assert.assertEquals("joe", name); + + int age = ObjectPath.tryGetPathValue(test, "bar[bar.objIndex].Age", Integer.class); + Assert.assertEquals(1, age); + } + + // test with json + { + String json = Serialization.toString(test); + JsonNode jtest = Serialization.jsonToTree(json); + + Assert.assertEquals(jtest, ObjectPath.getPathValue(test, "", JsonNode.class)); + } + } + + // Test classes + // + // Note: This is different from the support dotnet provides due to Java + // not having the same notion of dynamic and anonymous types. Jackson + // itself does not support anonymous inner class deserialization. So our + // support only extends to POJO's which conform to a Bean (public + // members or accessors). + public static class PathTest { + public String test; + public Options options; + public Bar bar; + } + + public static class Location { + public Float latitude; + public Float longitude; + } + + public static class Options { + public String firstName; + public String lastName; + public Integer age; + public Boolean bool; + public Location location; + } + + public static class Bar { + public Integer numIndex; + public String strIndex; + public String objIndex; + public Options options; + public int[] numbers; + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java new file mode 100644 index 000000000..8361acb70 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptCultureModelTests.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import com.microsoft.bot.dialogs.prompts.PromptCultureModel; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; + +import org.javatuples.Pair; +import org.junit.Assert; +import org.junit.Test; + + + +public class PromptCultureModelTests { + + @Test + public void MapCulturesToNearest() { + + List<Pair<String, String>> testData = new ArrayList<Pair<String, String>>(); + testData.add(Pair.with("en-us", "en-us")); + testData.add(Pair.with("en-US", "en-us")); + testData.add(Pair.with("en-Us", "en-us")); + testData.add(Pair.with("EN", "en-us")); + testData.add(Pair.with("en", "en-us")); + testData.add(Pair.with("es-es", "es-es")); + testData.add(Pair.with("es-ES", "es-es")); + testData.add(Pair.with("es-Es", "es-es")); + testData.add(Pair.with("ES", "es-es")); + testData.add(Pair.with("es", "es-es")); + + for (Pair<String, String> pair : testData) { + ShouldCorrectlyMapToNearesLanguage(pair.getValue0(), pair.getValue1()); + } + + } + + public void ShouldCorrectlyMapToNearesLanguage(String localeVariation, String expectedResult) { + String result = PromptCultureModels.mapToNearestLanguage(localeVariation); + Assert.assertEquals(expectedResult, result); + } + + @Test + public void ShouldReturnAllSupportedCultures() { + PromptCultureModel[] expected = new PromptCultureModel[] { + // Bulgarian, + // Chinese, + // Dutch, + PromptCultureModels.ENGLISH, + // French, + // German, + // Hindi, + // Italian, + // Japanese, + // Korean, + // Portuguese, + PromptCultureModels.SPANISH + // Swedish, + // Turkish + }; + + PromptCultureModel[] supportedCultures = PromptCultureModels.getSupportedCultures(); + + List<PromptCultureModel> expectedList = Arrays.asList(expected); + List<PromptCultureModel> supportedList = Arrays.asList(supportedCultures); + for (PromptCultureModel promptCultureModel : expectedList) { + Assert.assertTrue(supportedList.contains(promptCultureModel)); + } + + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java new file mode 100644 index 000000000..ef71e46aa --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java @@ -0,0 +1,203 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidator; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Test; + +public class PromptValidatorContextTests { + + @Test + public void PromptValidatorContextEnd() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + return CompletableFuture.completedFuture(true); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("John") + .assertReply("John is a great name!") + .send("Hi again") + .assertReply("Please type your name.") + .send("1") + .assertReply("1 is a great name!") + .startTest() + .join(); + } + + private class WaterfallStep1 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions prompt = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please type your name."); + prompt.setPrompt(activity); + return stepContext.prompt("namePrompt", prompt); + } + + } + + private class WaterfallStep2 implements WaterfallStep { + + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + String name = (String)stepContext.getResult(); + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("%s is a great name!", name))).join(); + return stepContext.endDialog(); + } + } + + @Test + public void PromptValidatorContextRetryEnd() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + String result = promptContext.getRecognized().getValue(); + if (result.length() > 3) { + return CompletableFuture.completedFuture(true); + } else { + promptContext.getContext().sendActivity( + MessageFactory.text("Please send a name that is longer than 3 characters.")); + } + + return CompletableFuture.completedFuture(false); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + + // Create TextPrompt with dialogId "namePrompt" and custom validator + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters.") + .send("John") + .assertReply("John is a great name!") + .startTest() + .join(); + } + + @Test + public void PromptValidatorNumberOfAttempts() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<String> validator = (promptContext) -> { + String result = promptContext.getRecognized().getValue(); + if (result.length() > 3) { + Activity succeededMessage = MessageFactory.text(String.format("You got it at the %nth try!", + promptContext.getAttemptCount())); + promptContext.getContext().sendActivity(succeededMessage); + return CompletableFuture.completedFuture(true); + } else { + promptContext.getContext().sendActivity( + MessageFactory.text("Please send a name that is longer than 3 characters.")); + } + + return CompletableFuture.completedFuture(false); + }; + + dialogs.add(new TextPrompt("namePrompt", validator)); + + WaterfallStep[] steps = new WaterfallStep[] + { + new WaterfallStep1(), + new WaterfallStep2() + }; + + dialogs.add(new WaterfallDialog("nameDialog", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("nameDialog").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please type your name.") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 1") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 2") + .send("hi") + .assertReply("Please send a name that is longer than 3 characters. 3") + .send("John") + .assertReply("You got it at the 4th try!") + .assertReply("John is a great name!") + .startTest(); + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java new file mode 100644 index 000000000..80553d51e --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; + +import org.junit.Assert; +import org.junit.Test; + +public class ReplaceDialogTests { + + @Test + public void ReplaceDialogNoBranch() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("prompt one") + .send("hello") + .assertReply("prompt two") + .send("hello") + .assertReply("prompt three") + .send("hello") + .assertReply("*** WaterfallDialog End ***") + .startTest() + .join(); + } + + @Test + public void ReplaceDialogBranch() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("prompt one") + .send("hello") + .assertReply("prompt two") + .send("replace") + .assertReply("*** WaterfallDialog End ***") + .assertReply("prompt four") + .send("hello") + .assertReply("prompt five") + .send("hello") + .startTest() + .join(); + } + + @Test + public void ReplaceDialogTelemetryClientNotNull() { + FirstDialog dialog = new FirstDialog(); + + MemoryStorage storage = new MemoryStorage(); + UserState userState = new UserState(storage); + ConversationState conversationState = new ConversationState(storage); + TestAdapter adapter = new TestAdapter() + .useStorage(storage) + .useBotState(userState, conversationState); + DialogManager dialogManager = new DialogManager(dialog, null); + new TestFlow(adapter, (turnContext) -> { + Assert.assertNotNull(dialog.getTelemetryClient()); + dialogManager.onTurn(turnContext).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .startTest(); + } + + private class FirstDialog extends ComponentDialog { + public FirstDialog() { + super("FirstDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new ActionOne(), + new ActionTwo(), + new ReplaceAction(), + new ActionThree(), + new LastAction(), + }; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new SecondDialog()); + addDialog(new WaterfallWithEndDialog("WaterfallWithEndDialog", steps)); + + setInitialDialogId("WaterfallWithEndDialog"); + } + + private class ActionOne implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt one")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ActionTwo implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt two")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ReplaceAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if ((String) stepContext.getResult() == "replace") { + return stepContext.replaceDialog("SecondDialog"); + } else { + return stepContext.next(null); + } + } + } + private class ActionThree implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt three")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class LastAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + return stepContext.endDialog(); + } + } + + private class WaterfallWithEndDialog extends WaterfallDialog { + private WaterfallWithEndDialog(String id, WaterfallStep[] steps) { + super(id, Arrays.asList(steps)); + } + + @Override + public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance instance, + DialogReason reason) { + turnContext.sendActivity(MessageFactory.text("*** WaterfallDialog End ***")).join(); + return super.endDialog(turnContext, instance, reason); + } + } + } + + private class SecondDialog extends ComponentDialog { + private SecondDialog() { + super("SecondDialog"); + WaterfallStep[] steps = new WaterfallStep[] { + new ActionFour(), + new ActionFive(), + new LastAction(), + }; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(steps))); + + setInitialDialogId("WaterfallDialog"); + } + + private class ActionFour implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt four")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class ActionFive implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("prompt five")); + return stepContext.prompt("TextPrompt", options); + } + } + + private class LastAction implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + return stepContext.endDialog(); + } + + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java new file mode 100644 index 000000000..9d2f05703 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/TestLocale.java @@ -0,0 +1,171 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.dialogs.prompts.PromptCultureModel; + +public class TestLocale { + + private String validLocale; + + private String capEnding; + + private String titleEnding; + + private String capTwoLetter; + + private String lowerTwoLetter; + + private String expectedPrompt; + + private String inputThatResultsInOne; + + private String inputThatResultsInZero; + + private PromptCultureModel culture; + + public TestLocale( + PromptCultureModel cultureModel, + String expectedPrompt, + String inputThatResultsInOne, + String inputThatResultsInZero) { + if (cultureModel.getLocale().length() != 5) { + throw new IllegalArgumentException("validLocale must be in format: es-es"); + } + + this.culture = cultureModel; + this.validLocale = cultureModel.getLocale().toString().toLowerCase(); + this.expectedPrompt = expectedPrompt; + this.inputThatResultsInOne = inputThatResultsInOne; + this.inputThatResultsInZero = inputThatResultsInZero; + + // es-ES + capEnding = getCapEnding(validLocale); + + // es-Es + titleEnding = getTitleEnding(validLocale); + + // ES + capTwoLetter = getCapTwoLetter(validLocale); + + // es + lowerTwoLetter = getLowerTwoLetter(validLocale); + } + + public String getSeparator() { + if (culture != null) { + return culture.getSeparator(); + } else { + return ""; + } + } + + public String getInlineOr() { + if (culture != null) { + return culture.getInlineOr(); + } else { + return ""; + } + } + + public String getInlineOrMore() { + if (culture != null) { + return culture.getInlineOrMore(); + } else { + return ""; + } + } + + + private String getCapEnding(String locale) { + return String.format("%s%s-%s%s", + locale.substring(0, 1), + locale.substring(1, 2), + locale.substring(3, 4).toUpperCase(), + locale.substring(4, 5).toUpperCase()); + } + + private String getTitleEnding(String locale) { + return String.format("%s%s-%s%s", + locale.substring(0, 1), + locale.substring(1, 2), + locale.substring(3, 4).toUpperCase(), + locale.substring(4, 5)); + } + + private String getCapTwoLetter(String locale) { + return String.format("%s%s", + locale.substring(0, 1).toUpperCase(), + locale.substring(1, 2).toUpperCase()); + } + + private String getLowerTwoLetter(String locale) { + return String.format("%s%s", + locale.substring(0, 1), + locale.substring(1, 2)); + } + + /** + * @return the ValidLocale value as a String. + */ + public String getValidLocale() { + return this.validLocale; + } + + /** + * @return the CapEnding value as a String. + */ + public String getCapEnding() { + return this.capEnding; + } + + /** + * @return the TitleEnding value as a String. + */ + public String getTitleEnding() { + return this.titleEnding; + } + + /** + * @return the CapTwoLetter value as a String. + */ + public String getCapTwoLetter() { + return this.capTwoLetter; + } + + /** + * @return the LowerTwoLetter value as a String. + */ + public String getLowerTwoLetter() { + return this.lowerTwoLetter; + } + + /** + * @return the ExpectedPrompt value as a String. + */ + public String getExpectedPrompt() { + return this.expectedPrompt; + } + + /** + * @return the InputThatResultsInOne value as a String. + */ + public String getInputThatResultsInOne() { + return this.inputThatResultsInOne; + } + + /** + * @return the InputThatResultsInZero value as a String. + */ + public String getInputThatResultsInZero() { + return this.inputThatResultsInZero; + } + + /** + * @return the Culture value as a PromptCultureModel. + */ + public PromptCultureModel getCulture() { + return this.culture; + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java new file mode 100644 index 000000000..188f1a2cb --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java @@ -0,0 +1,545 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.Severity; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.NotImplementedException; +import org.junit.Test; +import org.junit.Assert; + +public class WaterfallTests { + + public WaterfallDialog Create_Waterfall3() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall3_Step1(), + new Waterfall3_Step2() + }; + return new WaterfallDialog("test-waterfall-a", Arrays.asList(steps)); + } + + public WaterfallDialog Create_Waterfall4() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall4_Step1(), + new Waterfall4_Step2() + }; + return new WaterfallDialog("test-waterfall-b", Arrays.asList(steps)); + } + + public WaterfallDialog Create_Waterfall5() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall5_Step1(), + new Waterfall5_Step2() + }; + return new WaterfallDialog("test-waterfall-c", Arrays.asList(steps)); + } + + @Test + public void Waterfall() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + WaterfallStep[] steps = new WaterfallStep[] + {(step) -> { + step.getContext().sendActivity("step1"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step2"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step3"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, + }; + dialogs.add(new WaterfallDialog("test", Arrays.asList(steps))); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallStepParentIsWaterfallParent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + final String WATERFALL_PARENT_D = "waterfall-parent-test-dialog"; + ComponentDialog waterfallParent = new ComponentDialog(WATERFALL_PARENT_D); + + WaterfallStep[] steps = new WaterfallStep[] {(step) -> { + Assert.assertEquals(step.getParent().getActiveDialog().getId(), waterfallParent.getId()); + step.getContext().sendActivity("verified").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } }; + + waterfallParent.addDialog(new WaterfallDialog("test", Arrays.asList(steps))); + waterfallParent.setInitialDialogId("test"); + dialogs.add(waterfallParent); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog(WATERFALL_PARENT_D, null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("verified") + .startTest() + .join(); + } + + @Test + public void WaterfallWithCallback() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + WaterfallStep[] steps = new WaterfallStep[] {(step) -> { + step.getContext().sendActivity("step1"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step2"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, (step) -> { + step.getContext().sendActivity("step3"); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }, }; + WaterfallDialog waterfallDialog = new WaterfallDialog("test", Arrays.asList(steps)); + + dialogs.add(waterfallDialog); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallWithStepsNull() { + Assert.assertThrows(IllegalArgumentException.class, () -> new WaterfallDialog("test", null).addStep(null)); + } + + @Test + public void WaterfallWithClass() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + + dialogs.add(new MyWaterfallDialog("test")); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallPrompt() throws UnsupportedDataTypeException{ + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(Create_Waterfall2()); + NumberPrompt<Integer> numberPrompt = null; + try { + numberPrompt = new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, + Integer.class); + } catch (UnsupportedDataTypeException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + dialogs.add(numberPrompt); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-waterfall").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .assertReply("Enter a number.") + .send("hello again") + .assertReply("It must be a number") + .send("42") + .assertReply("Thanks for '42'") + .assertReply("step2") + .assertReply("Enter a number.") + .send("apple") + .assertReply("It must be a number") + .send("orange") + .assertReply("It must be a number") + .send("64") + .assertReply("Thanks for '64'") + .assertReply("step3") + .startTest() + .join(); + } + + @Test + public void WaterfallNested() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(Create_Waterfall3()); + dialogs.add(Create_Waterfall4()); + dialogs.add(Create_Waterfall5()); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-waterfall-a").join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .assertReply("step1.1") + .send("hello") + .assertReply("step1.2") + .send("hello") + .assertReply("step2") + .assertReply("step2.1") + .send("hello") + .assertReply("step2.2") + .startTest(); + } + + @Test + public void WaterfallDateTimePromptFirstInvalidThenValidInput() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + DialogSet dialogs = new DialogSet(dialogState); + + dialogs.add(new DateTimePrompt("dateTimePrompt", null, PromptCultureModels.ENGLISH_CULTURE)); + + WaterfallStep[] steps = new WaterfallStep[] { + (stepContext) -> { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Provide a date"); + return stepContext.prompt("dateTimePrompt", options); + }, + (stepContext) -> { + Assert.assertNotNull(stepContext); + return stepContext.endDialog(); + }, + }; + + dialogs.add(new WaterfallDialog("test-dateTimePrompt", Arrays.asList(steps))); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + new TestFlow(adapter, (turnContext) -> { + DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + dc.continueDialog().join(); + + if (!turnContext.getResponded()) { + dc.beginDialog("test-dateTimePrompt", null); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Provide a date") + .send("hello again") + .assertReply("Provide a date") + .send("Wednesday 4 oclock") + .startTest(); + } + + @Test + public void WaterfallCancel() { + final String id = "waterfall"; + final int index = 1; + + MyWaterfallDialog dialog = new MyWaterfallDialog(id); + MyBotTelemetryClient telemetryClient = new MyBotTelemetryClient("Waterfall2_Step2"); + dialog.setTelemetryClient(telemetryClient); + + DialogInstance dialogInstance = new DialogInstance(); + dialogInstance.setId(id); + Map<String, Object> stateMap = new HashMap<String, Object>(); + stateMap.put("stepIndex", index); + stateMap.put("instanceId", "(guid)"); + dialogInstance.setState(stateMap); + + dialog.endDialog( + new TurnContextImpl(new TestAdapter(), new Activity(ActivityTypes.MESSAGE)), + dialogInstance, + DialogReason.CANCEL_CALLED); + + Assert.assertTrue(telemetryClient.trackEventCallCount > 0); + } + + private WaterfallDialog Create_Waterfall2() { + WaterfallStep[] steps = new WaterfallStep[] + { + new Waterfall2_Step1(), + new Waterfall2_Step2(), + new Waterfall2_Step3() + }; + return new WaterfallDialog("test-waterfall", Arrays.asList(steps)); + } + + private class Waterfall2_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step1"); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + options.setRetryPrompt(MessageFactory.text("It must be a number")); + return stepContext.prompt("number", options); + } + } + + private class Waterfall2_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + } + + stepContext.getContext().sendActivity("step2"); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Enter a number.")); + options.setRetryPrompt(MessageFactory.text("It must be a number")); + return stepContext.prompt("number", options); + } + } + + private class Waterfall2_Step3 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + if (stepContext.getValues() != null) { + int numberResult = (int) stepContext.getResult(); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + } + + stepContext.getContext().sendActivity("step3"); + Map<String, Object> resultMap = new HashMap<String, Object>(); + resultMap.put("Value", "All Done!"); + return stepContext.endDialog(resultMap); + } + } + private class Waterfall3_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1")); + return stepContext.beginDialog("test-waterfall-b", null); + } + } + + private class Waterfall3_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2")); + return stepContext.beginDialog("test-waterfall-c", null); + } + } + + private class Waterfall4_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1.1")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall4_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step1.2")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall5_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2.1")); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall5_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("step2.2")); + return stepContext.endDialog(); + } + } + + private final class MyBotTelemetryClient implements BotTelemetryClient { + + private int trackEventCallCount = 0; + private String stepNameToMatch = ""; + + private MyBotTelemetryClient(String stepNameToMatch) { + this.stepNameToMatch = stepNameToMatch; + } + + @Override + public void trackAvailability(String name, OffsetDateTime timeStamp, Duration duration, String runLocation, + boolean success, String message, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackAvailability is not implemented"); + } + + @Override + public void trackDependency(String dependencyTypeName, String target, String dependencyName, String data, + OffsetDateTime startTime, Duration duration, String resultCode, boolean success) { + throw new NotImplementedException("trackDependency is not implemented"); + } + + @Override + public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) { + String stepName = properties.get("StepName"); + if (stepName != null && stepName.equals(stepNameToMatch)) { + trackEventCallCount++; + } + } + + @Override + public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackException is not implemented"); + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) { + throw new NotImplementedException("trackTrace is not implemented"); + } + + @Override + public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) { + throw new NotImplementedException("trackDialogView is not implemented"); + } + + @Override + public void flush() { + throw new NotImplementedException("flush is not implemented"); + } + } + + private class MyWaterfallDialog extends WaterfallDialog { + public MyWaterfallDialog(String id) { + super(id, null); + addStep(new Waterfall2_Step1()); + addStep(new Waterfall2_Step2()); + addStep(new Waterfall2_Step3()); + } + + private class Waterfall2_Step1 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step1").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + + private class Waterfall2_Step2 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step2").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + private class Waterfall2_Step3 implements WaterfallStep { + @Override + public CompletableFuture<DialogTurnResult> waterfallStep(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity("step3").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + } + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java new file mode 100644 index 000000000..03cb1e993 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoiceFactoryTests { + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), new Choice("blue")); + private static List<Choice> extraChoices = Arrays.asList(new Choice("red"), new Choice("green"), new Choice("blue"), new Choice("alpha")); + private static List<Choice> choicesWithActions = Arrays.asList( + new Choice("ImBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.IM_BACK); + setTitle("ImBack Action"); + setValue("ImBack Value"); + }}); + }}, + new Choice("MessageBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.MESSAGE_BACK); + setTitle("MessageBack Action"); + setValue("MessageBack Value"); + }}); + }}, + new Choice("PostBack") {{ + setAction(new CardAction() {{ + setType(ActionTypes.POST_BACK); + setTitle("PostBack Action"); + setValue("PostBack Value"); + }}); + }} + ); + + @Test + public void shouldRenderChoicesInline() { + Activity activity = ChoiceFactory.inline(colorChoices, "select from:"); + Assert.assertEquals("select from: (1) red, (2) green, or (3) blue", activity.getText()); + } + + @Test + public void shouldRenderChoicesAsAList() { + Activity activity = ChoiceFactory.list(colorChoices, "select from:"); + Assert.assertEquals("select from:\n\n 1. red\n 2. green\n 3. blue", activity.getText()); + } + + @Test + public void shouldRenderUnincludedNumbersChoicesAsAList() { + Activity activity = ChoiceFactory.list(colorChoices, "select from:", null, new ChoiceFactoryOptions() {{ setIncludeNumbers(false); }}); + Assert.assertEquals("select from:\n\n - red\n - green\n - blue", activity.getText()); + } + + @Test + public void shouldRenderChoicesAsSuggestedActions() { + Activity activity = ChoiceFactory.suggestedAction(colorChoices, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldRenderChoicesAsHeroCard() { + Activity activity = ChoiceFactory.heroCard(colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldAutomaticallyChooseRenderStyleBasedOnChannelType() { + Activity activity = ChoiceFactory.forChannel(Channels.EMULATOR, colorChoices, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("red", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("green", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("blue", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldChooseCorrectStylesForCortana() { + Activity activity = ChoiceFactory.forChannel(Channels.CORTANA, colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldChooseCorrectStylesForTeams() { + Activity activity = ChoiceFactory.forChannel(Channels.MSTEAMS, colorChoices, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("red", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("green", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("blue", heroCard.getButtons().get(2).getTitle()); + } + + @Test + public void shouldIncludeChoiceActionsInSuggestedActions() { + Activity activity = ChoiceFactory.suggestedAction(choicesWithActions, "select from:"); + Assert.assertEquals("select from:", activity.getText()); + Assert.assertNotNull(activity.getSuggestedActions()); + Assert.assertEquals(3, activity.getSuggestedActions().getActions().size()); + Assert.assertEquals(ActionTypes.IM_BACK, activity.getSuggestedActions().getActions().get(0).getType()); + Assert.assertEquals("ImBack Value", activity.getSuggestedActions().getActions().get(0).getValue()); + Assert.assertEquals("ImBack Action", activity.getSuggestedActions().getActions().get(0).getTitle()); + Assert.assertEquals(ActionTypes.MESSAGE_BACK, activity.getSuggestedActions().getActions().get(1).getType()); + Assert.assertEquals("MessageBack Value", activity.getSuggestedActions().getActions().get(1).getValue()); + Assert.assertEquals("MessageBack Action", activity.getSuggestedActions().getActions().get(1).getTitle()); + Assert.assertEquals(ActionTypes.POST_BACK, activity.getSuggestedActions().getActions().get(2).getType()); + Assert.assertEquals("PostBack Value", activity.getSuggestedActions().getActions().get(2).getValue()); + Assert.assertEquals("PostBack Action", activity.getSuggestedActions().getActions().get(2).getTitle()); + } + + @Test + public void shouldIncludeChoiceActionsInHeroCards() { + Activity activity = ChoiceFactory.heroCard(choicesWithActions, "select from:"); + + Assert.assertNotNull(activity.getAttachments()); + + HeroCard heroCard = (HeroCard)activity.getAttachments().get(0).getContent(); + + Assert.assertEquals(3, heroCard.getButtons().size()); + Assert.assertEquals(ActionTypes.IM_BACK, heroCard.getButtons().get(0).getType()); + Assert.assertEquals("ImBack Value", heroCard.getButtons().get(0).getValue()); + Assert.assertEquals("ImBack Action", heroCard.getButtons().get(0).getTitle()); + Assert.assertEquals(ActionTypes.MESSAGE_BACK, heroCard.getButtons().get(1).getType()); + Assert.assertEquals("MessageBack Value", heroCard.getButtons().get(1).getValue()); + Assert.assertEquals("MessageBack Action", heroCard.getButtons().get(1).getTitle()); + Assert.assertEquals(ActionTypes.POST_BACK, heroCard.getButtons().get(2).getType()); + Assert.assertEquals("PostBack Value", heroCard.getButtons().get(2).getValue()); + Assert.assertEquals("PostBack Action", heroCard.getButtons().get(2).getTitle()); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java new file mode 100644 index 000000000..82ac40997 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java @@ -0,0 +1,133 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; +import com.microsoft.bot.schema.Activity; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesChannelTests { + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithLineAnd13() { + boolean supports = Channel.supportsSuggestedActions(Channels.LINE, 13); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithLineAnd14() { + boolean supports = Channel.supportsSuggestedActions(Channels.LINE, 14); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithSkypeAnd10() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 10); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithSkypeAnd11() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 11); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithKikAnd20() { + boolean supports = Channel.supportsSuggestedActions(Channels.KIK, 20); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithKikAnd21() { + boolean supports = Channel.supportsSuggestedActions(Channels.SKYPE, 21); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithEmulatorAnd100() { + boolean supports = Channel.supportsSuggestedActions(Channels.EMULATOR, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsSuggestedActionsWithEmulatorAnd101() { + boolean supports = Channel.supportsSuggestedActions(Channels.EMULATOR, 101); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsSuggestedActionsWithDirectLineSpeechAnd100() { + boolean supports = Channel.supportsSuggestedActions(Channels.DIRECTLINESPEECH, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithDirectLineSpeechAnd99() { + boolean supports = Channel.supportsCardActions(Channels.DIRECTLINESPEECH, 99); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithLineAnd99() { + boolean supports = Channel.supportsCardActions(Channels.LINE, 99); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsCardActionsWithLineAnd100() { + boolean supports = Channel.supportsCardActions(Channels.LINE, 100); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithCortanaAnd100() { + boolean supports = Channel.supportsCardActions(Channels.CORTANA, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithSlackAnd100() { + boolean supports = Channel.supportsCardActions(Channels.SLACK, 100); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnTrueForSupportsCardActionsWithSkypeAnd100() { + boolean supports = Channel.supportsCardActions(Channels.SKYPE, 3); + Assert.assertTrue(supports); + } + + @Test + public void shouldReturnFalseForSupportsCardActionsWithSkypeAnd5() { + boolean supports = Channel.supportsCardActions(Channels.SKYPE, 5); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnFalseForHasMessageFeedWithCortana() { + boolean supports = Channel.hasMessageFeed(Channels.CORTANA); + Assert.assertFalse(supports); + } + + @Test + public void shouldReturnChannelIdFromContextActivity() { + Activity testActivity = new Activity() {{ setChannelId(Channels.FACEBOOK); }}; + TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); + String channelId = Channel.getChannelId(testContext); + Assert.assertEquals(Channels.FACEBOOK, channelId); + } + + @Test + public void shouldReturnEmptyFromContextActivityMissingChannel() { + Activity testActivity = new Activity() {{ setChannelId(null); }}; + TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); + String channelId = Channel.getChannelId(testContext); + Assert.assertNull(channelId); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java new file mode 100644 index 000000000..a55b4d31f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java @@ -0,0 +1,218 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.Arrays; +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesRecognizerTests { + + private static List<String> colorChoices = Arrays.asList("red", "green", "blue"); + private static List<String> overlappingChoices = Arrays + .asList("bread", "bread pudding", "pudding"); + + private static List<SortedValue> colorValues = Arrays.asList( + new SortedValue("red", 0), + new SortedValue("green", 1), + new SortedValue("blue", 2) + ); + + private static List<SortedValue> overlappingValues = Arrays.asList( + new SortedValue("bread", 0), + new SortedValue("bread pudding", 1), + new SortedValue("pudding", 2) + ); + + private static List<SortedValue> similarValues = Arrays.asList( + new SortedValue("option A", 0), + new SortedValue("option B", 1), + new SortedValue("option C", 2) + ); + + // + // FindChoices + // + + @Test + public void shouldFindASimpleValueInAnSingleWordUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("red", colorValues); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 2, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + } + + @Test + public void shouldFindASimpleValueInAnUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("the red one please.", colorValues); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + } + + @Test + public void shouldFindMultipleValuesWithinAnUtterance() { + List<ModelResult<FoundValue>> found = Find.findValues("the red and blue ones please.", colorValues); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertValue(found.get(0), "red", 0, 1.0f); + assertValue(found.get(1), "blue", 2, 1.0f); + } + + @Test + public void shouldFindMultipleValuesThatOverlap() { + List<ModelResult<FoundValue>> found = Find.findValues("the bread pudding and bread please.", overlappingValues); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 16, "bread pudding"); + assertValue(found.get(0), "bread pudding", 1, 1.0f); + assertValue(found.get(1), "bread", 0, 1.0f); + } + + @Test + public void shouldCorrectlyDisambiguateBetweenVerySimilarValues() { + List<ModelResult<FoundValue>> found = Find.findValues("option B", similarValues, new FindChoicesOptions() {{ setAllowPartialMatches(true);}}); + Assert.assertEquals(1, found.size()); + assertValue(found.get(0), "option B", 1, 1.0f); + } + + @Test + public void shouldFindASingleChoiceInAnUtterance() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the red one please.", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesWithinAnUtterance() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the red and blue ones please.", colorChoices); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesThatOverlap() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings("the bread pudding and bread please.", overlappingChoices); + Assert.assertEquals(2, found.size()); + assertResult(found.get(0), 4, 16, "bread pudding"); + assertChoice(found.get(0), "bread pudding", 1, 1.0f, null); + assertChoice(found.get(1), "bread", 0, 1.0f, null); + } + + @Test + public void shouldAcceptNullUtteranceInFindChoices() { + List<ModelResult<FoundChoice>> found = Find.findChoicesFromStrings(null, colorChoices); + Assert.assertEquals(0, found.size()); + } + + // + // RecognizeChoices + // + + @Test + public void shouldFindAChoiceInAnUtteranceByName() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the red one please", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 6, "red"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByOrdinalPosition() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the first one please.", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 4, 8, "first"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesInAnUtteranceByOrdinalPosition() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("the first and third one please.", colorChoices); + Assert.assertEquals(2, found.size()); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByNumericalIndex_digit() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("1", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 0, "1"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindAChoiceInAnUtteranceByNumericalIndex_Text() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("one", colorChoices); + Assert.assertEquals(1, found.size()); + assertResult(found.get(0), 0, 2, "one"); + assertChoice(found.get(0), "red", 0, 1.0f, null); + } + + @Test + public void shouldFindMultipleChoicesInAnUtteranceByNumerical_index() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings("option one and 3.", colorChoices); + Assert.assertEquals(2, found.size()); + assertChoice(found.get(0), "red", 0, 1.0f, null); + assertChoice(found.get(1), "blue", 2, 1.0f, null); + } + + @Test + public void shouldAcceptNullUtteranceInRecognizeChoices() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings(null, colorChoices); + Assert.assertEquals(0, found.size()); + } + + @Test + public void shouldNOTFindAChoiceInAnUtteranceByOrdinalPosition_RecognizeOrdinalsFalseAndRecognizeNumbersFalse() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings( + "the first one please.", + colorChoices, + new FindChoicesOptions() {{ setRecognizeOrdinals(false); setRecognizeNumbers(false);}}); + Assert.assertEquals(0, found.size()); + } + + @Test + public void shouldNOTFindAChoiceInAnUtteranceByNumericalIndex_Text_RecognizeNumbersFalse() { + List<ModelResult<FoundChoice>> found = ChoiceRecognizers.recognizeChoicesFromStrings( + "one", + colorChoices, + new FindChoicesOptions() {{ setRecognizeNumbers(false);}}); + Assert.assertEquals(0, found.size()); + } + + // + // Helper methods + // + + private static <T> void assertResult(ModelResult<T> result, int start, int end, String text) { + Assert.assertEquals(start, result.getStart()); + Assert.assertEquals(end, result.getEnd()); + Assert.assertEquals(text, result.getText()); + } + + private static void assertValue(ModelResult<FoundValue> result, String value, int index, float score) { + Assert.assertEquals("value", result.getTypeName()); + Assert.assertNotNull(result.getResolution()); + FoundValue resolution = result.getResolution(); + Assert.assertEquals(value, resolution.getValue()); + Assert.assertEquals(index, resolution.getIndex()); + Assert.assertEquals(score, resolution.getScore(), .01f); + } + + private static void assertChoice(ModelResult<FoundChoice> result, String value, int index, float score, String synonym) { + Assert.assertEquals("choice", result.getTypeName()); + Assert.assertNotNull(result.getResolution()); + FoundChoice resolution = result.getResolution(); + Assert.assertEquals(value, resolution.getValue()); + Assert.assertEquals(index, resolution.getIndex()); + Assert.assertEquals(score, resolution.getScore(), .01f); + if (synonym != null) { + Assert.assertEquals(synonym, resolution.getSynonym()); + } + } +} \ No newline at end of file diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java new file mode 100644 index 000000000..57f72cecc --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesTokenizerTests.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.choices; + +import java.util.List; +import org.junit.Assert; +import org.junit.Test; + +public class ChoicesTokenizerTests { + @Test + public void shouldBreakOnSpaces() { + List<Token> tokens = new Tokenizer().tokenize("how now brown cow", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 2, "how"); + assertToken(tokens.get(1), 4, 6, "now"); + assertToken(tokens.get(2), 8, 12, "brown"); + assertToken(tokens.get(3), 14, 16, "cow"); + } + + @Test + public void shouldBreakOnPunctuation() { + List<Token> tokens = new Tokenizer().tokenize("how-now.brown:cow ?", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 2, "how"); + assertToken(tokens.get(1), 4, 6, "now"); + assertToken(tokens.get(2), 8, 12, "brown"); + assertToken(tokens.get(3), 14, 16, "cow"); + } + + @Test + public void shouldTokenizeSingleCharacterTokens() + { + List<Token> tokens = new Tokenizer().tokenize("a b c d", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 0, "a"); + assertToken(tokens.get(1), 2, 2, "b"); + assertToken(tokens.get(2), 4, 4, "c"); + assertToken(tokens.get(3), 6, 6, "d"); + } + + @Test + public void shouldReturnASingleToken() { + List<Token> tokens = new Tokenizer().tokenize("food", null); + Assert.assertEquals(1, tokens.size()); + assertToken(tokens.get(0), 0, 3, "food"); + } + + @Test + public void shouldReturnNoTokens() { + List<Token> tokens = new Tokenizer().tokenize(".?; -()", null); + Assert.assertEquals(0, tokens.size()); + } + + @Test + public void shouldReturnTheNormalizedAndOriginalTextForAToken() { + List<Token> tokens = new Tokenizer().tokenize("fOoD", null); + Assert.assertEquals(1, tokens.size()); + assertToken(tokens.get(0), 0, 3, "fOoD", "food"); + } + + @Test + public void shouldBreakOnEmojis() { + List<Token> tokens = new Tokenizer().tokenize("food \uD83D\uDCA5\uD83D\uDC4D\uD83D\uDE00", null); + Assert.assertEquals(4, tokens.size()); + assertToken(tokens.get(0), 0, 3, "food"); + assertToken(tokens.get(1), 5, 6, "\uD83D\uDCA5"); + assertToken(tokens.get(2), 7, 8, "\uD83D\uDC4D"); + assertToken(tokens.get(3), 9, 10, "\uD83D\uDE00"); + } + + private static void assertToken(Token token, int start, int end, String text) { + assertToken(token, start, end, text, null); + } + + private static void assertToken(Token token, int start, int end, String text, String normalized) { + Assert.assertEquals(start, token.getStart()); + Assert.assertEquals(end, token.getEnd()); + Assert.assertEquals(text, token.getText()); + Assert.assertEquals(normalized == null ? text : normalized, token.getNormalized()); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java new file mode 100644 index 000000000..160d798c9 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java @@ -0,0 +1,314 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Assert; +import org.junit.Test; + +public class ActivityPromptTests { + + public ActivityPromptTests(){ + + } + + @Test + public void ActivityPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt("", + new BasicActivityPromptValidator())); + } + + @Test + public void ActivityPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt(null, + new BasicActivityPromptValidator())); + } + + @Test + public void ActivityPromptWithNullValidatorShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new EventActivityPrompt("EventActivityPrompt", null)); + } + + @Test + public void BasicActivityPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add custom activity prompt to DialogSet. + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + // Create mock Activity for testing. + Activity eventActivity = new Activity() { { setType(ActivityTypes.EVENT); setValue(2); }}; + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + dc.prompt("EventActivityPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + + // ActivityPromptHandler handler = new ActivityPromptHandler(); + // handler.setDialogs(dialogs); + + new TestFlow(adapter, botLogic).send("hello").assertReply("please send an event.") + .send(eventActivity).assertReply("2").startTest().join(); + } + + @Test + public void ActivityPromptShouldSendRetryPromptIfValidationFailed() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", new EmptyPromptValidator()); + dialogs.add(eventPrompt); + + Activity eventActivity = new Activity(ActivityTypes.EVENT); + eventActivity.setValue(2); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Retrying - please send an event."); + options.setPrompt(activity); + options.setRetryPrompt(retryActivity); + dc.prompt("EventActivityPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } else if (results.getStatus() == DialogTurnStatus.WAITING) { + turnContext.sendActivity("Test complete."); + } + return CompletableFuture.completedFuture(null); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please send an event.") + .send("test") + .assertReply("Retrying - please send an event.") + .startTest(); + } + + @Test + public void ActivityPromptResumeDialogShouldPromptNotRetry() + { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", new EmptyPromptValidator()); + dialogs.add(eventPrompt); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + switch (turnContext.getActivity().getText()) { + case "begin": + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Retrying - please send an event."); + options.setPrompt(activity); + options.setRetryPrompt(retryActivity); + dc.prompt("EventActivityPrompt", options).join(); + break; + case "continue": + eventPrompt.continueDialog(dc).join(); + break; + case "resume": + eventPrompt.resumeDialog(dc, DialogReason.NEXT_CALLED).join(); + break; + default: + break; + } + return CompletableFuture.completedFuture(null); + }); + new TestFlow(adapter, botLogic) + .send("begin") + .assertReply("please send an event.") + .send("continue") + .assertReply("Retrying - please send an event.") + .send("resume") + // 'ResumeDialogAsync' of ActivityPrompt does NOT cause a Retry + .assertReply("please send an event.") + .startTest(); + } + + @Test + public void OnPromptOverloadWithoutIsRetryParamReturnsBasicActivityPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityWithoutRetryPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + // Create mock Activity for testing. + Activity eventActivity = new Activity(ActivityTypes.EVENT); + eventActivity.setValue(2); + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + dc.prompt("EventActivityWithoutRetryPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Activity content = (Activity) results.getResult(); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please send an event.") + .send(eventActivity) + .assertReply("2") + .startTest(); + } + + @Test + public void OnPromptErrorsWithNullContext() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityPrompt", + new BasicActivityPromptValidator()); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please send an event."); + options.setPrompt(activity); + eventPrompt.onPromptNullContext(options).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnPromptErrorsWithNullOptions() { + Assert.assertThrows( + IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + + EventActivityPrompt eventPrompt = new EventActivityPrompt("EventActivityWithoutRetryPrompt", + new BasicActivityPromptValidator()); + dialogs.add(eventPrompt); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + return eventPrompt.onPromptNullOptions(dc); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .startTest().join(); + + } catch (CompletionException ex) { + throw ex.getCause().getCause(); + } + }); + } + + class BasicActivityPromptValidator implements PromptValidator<Activity> { + + BasicActivityPromptValidator(){ + + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<Activity> promptContext) { + Assert.assertTrue(promptContext.getAttemptCount() > 0); + + Activity activity = promptContext.getRecognized().getValue(); + if (activity.getType() == ActivityTypes.EVENT) { + if ((int) activity.getValue() == 2) { + promptContext.getRecognized().setValue(MessageFactory.text(activity.getValue().toString())); + return CompletableFuture.completedFuture(true); + } + } else { + promptContext.getContext().sendActivity("Please send an 'event'-type Activity with a value of 2."); + } + + return CompletableFuture.completedFuture(false); + } + } + + class EmptyPromptValidator implements PromptValidator<Activity> { + + EmptyPromptValidator(){ + + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<Activity> promptContext) { + return CompletableFuture.completedFuture(false); + } + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java new file mode 100644 index 000000000..508ca84ad --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; + +import org.junit.Assert; +import org.junit.Test; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + + +public class AttachmentPromptTests { + @Test + public void AttachmentPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new AttachmentPrompt("")); + } + + @Test + public void AttachmentPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new AttachmentPrompt(null)); + } + + @Test + public void BasicAttachmentPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("BasicAttachmentPrompt","","")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add attachment prompt to DialogSet. + AttachmentPrompt attachmentPrompt = new AttachmentPrompt("AttachmentPrompt"); + dialogs.add(attachmentPrompt); + + // Create mock attachment for testing. + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + // Create incoming activity with attachment. + Activity activityWithAttachment = new Activity(ActivityTypes.MESSAGE); + ArrayList<Attachment> attachmentList = new ArrayList<Attachment>(); + attachmentList.add(attachment); + activityWithAttachment.setAttachments(attachmentList); + + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please add an attachment."); + dc.prompt("AttachmentPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<Attachment> attachments = (ArrayList<Attachment>) results.getResult(); + Activity content = MessageFactory.text((String) attachments.get(0).getContent()); + turnContext.sendActivity(content).join(); + } + + return CompletableFuture.completedFuture(null); + }); + + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please add an attachment.") + .send(activityWithAttachment) + .assertReply("some content") + .startTest(); + } + + @Test + public void RetryAttachmentPrompt() { + + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("RetryAttachmentPrompt","","")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add attachment prompt to DialogSet. + AttachmentPrompt attachmentPrompt = new AttachmentPrompt("AttachmentPrompt"); + dialogs.add(attachmentPrompt); + + // Create mock attachment for testing. + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + // Create incoming activity with attachment. + Activity activityWithAttachment = new Activity(ActivityTypes.MESSAGE); + ArrayList<Attachment> attachmentList = new ArrayList<Attachment>(); + attachmentList.add(attachment); + activityWithAttachment.setAttachments(attachmentList); + BotCallbackHandler botLogic = (turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("please add an attachment."); + dc.prompt("AttachmentPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<Attachment> attachments = (ArrayList<Attachment>) results.getResult(); + Activity content = MessageFactory.text((String) attachments.get(0).getContent()); + turnContext.sendActivity(content).join(); + } + return CompletableFuture.completedFuture(null); + }); + + new TestFlow(adapter, botLogic) + .send("hello") + .assertReply("please add an attachment.") + .send("hello again") + .assertReply("please add an attachment.") + .send(activityWithAttachment) + .assertReply("some content") + .startTest(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java new file mode 100644 index 000000000..6d5bd1f7a --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.TestLocale; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(Parameterized.class) +public class ChoicePromptLocaleVariationTests { + + String testCulture; + String inlineOr; + String inlineOrMore; + String separator; + + public ChoicePromptLocaleVariationTests(String testCulture, String inlineOr, String inlineOrMore, String separator) { + this.testCulture = testCulture; + this.inlineOr = inlineOr; + this.inlineOrMore = inlineOrMore; + this.separator = separator; + } + public static List<Object[]> getLocaleVariationTest() { + TestLocale[] testLocales = new TestLocale[2]; + testLocales[0] = new TestLocale(PromptCultureModels.ENGLISH, null, null, null); + testLocales[1] = new TestLocale(PromptCultureModels.SPANISH, null, null, null); + // testLocales[2] = new TestLocale(PromptCultureModels.DUTCH, null, null, null); + // testLocales[3] = new TestLocale(PromptCultureModels.BULGARIAN, null, null, null); + // testLocales[4] = new TestLocale(PromptCultureModels.FRENCH, null, null, null); + // testLocales[5] = new TestLocale(PromptCultureModels.HINDI, null, null, null); + // testLocales[6] = new TestLocale(PromptCultureModels.ITALIAN, null, null, null); + // testLocales[7] = new TestLocale(PromptCultureModels.JAPANESE, null, null, null); + // testLocales[8] = new TestLocale(PromptCultureModels.KOREAN, null, null, null); + // testLocales[9] = new TestLocale(PromptCultureModels.PORTUGUESE, null, null, null); + // testLocales[10] = new TestLocale(PromptCultureModels.CHINESE, null, null, null); + // testLocales[11] = new TestLocale(PromptCultureModels.SWEDISH, null, null, null); + // testLocales[12] = new TestLocale(PromptCultureModels.TURKISH, null, null, null); + + List<Object[]> resultList = new ArrayList<Object[]>(); + for (TestLocale testLocale : testLocales) { + resultList.add(new Object[] {testLocale.getValidLocale(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getCapEnding(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getTitleEnding(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getCapTwoLetter(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + resultList.add(new Object[] {testLocale.getLowerTwoLetter(), testLocale.getInlineOr(), + testLocale.getInlineOrMore(), testLocale.getSeparator() }); + } + + return resultList; + } + + + @Parameterized.Parameters + public static List<Object[]> data() { + return getLocaleVariationTest(); + + } + + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), + new Choice("blue")); + + @Test + public void testShouldRecognizeLocaleVariationsOfCorrectLocales() { + Assert.assertEquals(1, 1); + System.out.println("Testing: " + testCulture); + ShouldRecognizeLocaleVariationsOfCorrectLocales(this.testCulture, this.inlineOr, + this.inlineOrMore, this.separator); + } + + public void ShouldRecognizeLocaleVariationsOfCorrectLocales(String testCulture, String inlineOr, + String inlineOrMore, String separator) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, testCulture); + + dialogs.add(listPrompt); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(testCulture); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }).send(helloLocale).assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(); + testChoiceOption.setInlineOr(inlineOr); + testChoiceOption.setInlineOrMore(inlineOrMore); + testChoiceOption.setInlineSeparator(separator); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }).startTest().join(); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java new file mode 100644 index 000000000..4d99f3d9d --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -0,0 +1,833 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.SuggestedActions; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +public class ChoicePromptTests { + private static List<Choice> colorChoices = Arrays.asList(new Choice("red"), new Choice("green"), + new Choice("blue")); + + @Test + public void ChoicePromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ChoicePrompt("", null, null)); + } + + @Test + public void ChoicePromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ChoicePrompt(null, null, null)); + } + + @Test + public void ChoicePromptWithCardActionAndNoValueShouldNotFail() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + Choice choice = new Choice(); + CardAction action = new CardAction(); + action.setType(ActionTypes.IM_BACK); + action.setValue("value"); + action.setTitle("title"); + choice.setAction(action); + + PromptOptions options = new PromptOptions(); + List<Choice> choiceList = new ArrayList<Choice>(); + choiceList.add(choice); + options.setChoices(choiceList); + + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith(" (1) title")) + .startTest() + .join(); + } + + @Test + public void ShouldSendPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptAsAnInlineList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt eventPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color? (1) red, (2) green, or (3) blue") + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptAsANumberedList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.LIST); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color?\n\n 1. red\n 2. green\n 3. blue") + .startTest(); + } + + @Test + public void ShouldSendPromptUsingSuggestedActions() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.SUGGESTED_ACTION); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new + CardAction[] + { + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + } + }))) + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptUsingHeroCard() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateHeroCard( + new HeroCard() { + { + setText("favorite color?"); + setButtons(new ArrayList<CardAction>() { + { + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + }); + } + }); + } + }, 0)) + .startTest().join(); + } + + @Test + public void ShouldSendPromptUsingAppendedHeroCard() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + Attachment attachment = new Attachment(); + attachment.setContent("some content"); + attachment.setContentType("text/plain"); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setAttachments(new ArrayList<Attachment>() { { add(attachment); } }); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateHeroCard( + new HeroCard() { + { + setText("favorite color?"); + setButtons(new ArrayList<CardAction>() { + { + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }); + add(new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + }); + } + }); + } + }, 1)) + .startTest().join(); + } + + @Test + public void ShouldSendPromptWithoutAddingAList() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("favorite color?") + .startTest() + .join(); + } + + @Test + public void ShouldSendPromptWithoutAddingAListButAddingSsml() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setSpeak("spoken prompt"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSpeak("favorite color?", "spoken prompt")) + .startTest() + .join(); + } + + @Test + public void ShouldRecognizeAChoice() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + FoundChoice choiceResult = (FoundChoice) results.getResult(); + turnContext.sendActivities(MessageFactory.text(choiceResult.getValue())).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("red") + .assertReply("red") + .startTest() + .join(); + } + + // This is being left out for now due to it failing due to an issue in the Text Recognizers library. + // It should be worked out in the recognizers and then this test should be enabled again. + //@Test + public void ShouldNotRecognizeOtherText() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("your favorite color, please?"); + options.setRetryPrompt(retryActivity); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("what was that?") + .assertReply("your favorite color, please?") + .startTest() + .join(); + } + + @Test + public void ShouldCallCustomValidator() { + + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<FoundChoice> validator = (promptContext) -> { + promptContext.getContext().sendActivity(MessageFactory.text("validator called")); + return CompletableFuture.completedFuture(true); + }; + + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", validator, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validedStartsWith("favorite color?")) + .send("I'll take the red please.") + .assertReply("validator called") + .startTest() + .join(); + } + + @Test + public void ShouldUseChoiceStyleIfPresent() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.HEROCARD); + + dialogs.add(listPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + options.setStyle(ListStyle.SUGGESTED_ACTION); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new + CardAction[] + { + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("red"); + setTitle("red"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("green"); + setTitle("green"); + } + }, + new CardAction() { + { + setType(ActionTypes.IM_BACK); + setValue("blue"); + setTitle("blue"); + } + } + }))) + .startTest() + .join(); + } + + @Test + public void ShouldDefaultToEnglishLocaleNull() { + PerformShouldDefaultToEnglishLocale(null); + } + + @Test + public void ShouldDefaultToEnglishLocaleEmptyString() { + PerformShouldDefaultToEnglishLocale(""); + } + + @Test + public void ShouldDefaultToEnglishLocaleNotSupported() { + Assert.assertThrows(IllformedLocaleException.class, () -> { + try { + PerformShouldDefaultToEnglishLocale("not-supported"); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + public void PerformShouldDefaultToEnglishLocale(String locale) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + + dialogs.add(listPrompt); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(locale); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send(helloLocale) + .assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(); + testChoiceOption.setInlineOr(PromptCultureModels.ENGLISH.getInlineOr()); + testChoiceOption.setInlineOrMore(PromptCultureModels.ENGLISH.getInlineOrMore()); + testChoiceOption.setInlineSeparator(PromptCultureModels.ENGLISH.getSeparator()); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }) + .startTest() + .join(); + } + + @Test + public void ShouldAcceptAndRecognizeCustomLocaleDict() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + PromptCultureModel culture = new PromptCultureModel() { + { + setInlineOr(" customOr "); + setInlineOrMore(" customOrMore "); + setLocale("custom-custom"); + setSeparator("customSeparator"); + setNoInLanguage("customNo"); + setYesInLanguage("customYes"); + } + }; + + Map<String, ChoiceFactoryOptions> customDict = new HashMap<String, ChoiceFactoryOptions>(); + ChoiceFactoryOptions choiceOption = new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), + culture.getInlineOrMore(), + true); + customDict.put(culture.getLocale(), choiceOption); + + dialogs.add(new ChoicePrompt("ChoicePrompt", customDict, null, culture.getLocale())); + + Activity helloLocale = MessageFactory.text("hello"); + helloLocale.setLocale(culture.getLocale()); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("favorite color?"); + activity.setLocale(culture.getLocale()); + options.setPrompt(activity); + options.setChoices(colorChoices); + dc.prompt("ChoicePrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send(helloLocale) + .assertReply((activity) -> { + // Use ChoiceFactory to build the expected answer, manually + ChoiceFactoryOptions testChoiceOption = new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), + culture.getInlineOrMore(), + true); + + String expectedChoices = ChoiceFactory.inline(colorChoices, null, null, testChoiceOption).getText(); + Assert.assertEquals(String.format("favorite color?%s", expectedChoices), activity.getText()); + }) + .startTest() + .join(); + } + + class Validators { + public Validators() { + + } + private Consumer<Activity> validedStartsWith(String expected) { + return activity -> { + //Assert.IsAssignableFrom<MessageActivity>(activity); + Activity msg = (Activity) activity; + Assert.assertTrue(msg.getText().startsWith(expected)); + }; + } + + private Consumer<Activity> validateSuggestedActions(String expectedText, + SuggestedActions expectedSuggestedActions) { + return activity -> { + //Assert.IsAssignableFrom<MessageActivity>(activity); + Assert.assertEquals(expectedText, activity.getText()); + Assert.assertEquals(expectedSuggestedActions.getActions().size(), + activity.getSuggestedActions().getActions().size()); + + for (int i = 0; i < expectedSuggestedActions.getActions().size(); i++) { + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getType(), + activity.getSuggestedActions().getActions().get(i).getType()); + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getValue(), + activity.getSuggestedActions().getActions().get(i).getValue()); + Assert.assertEquals(expectedSuggestedActions.getActions().get(i).getTitle(), + activity.getSuggestedActions().getActions().get(i).getTitle()); + } + }; + } + + private Consumer<Activity> validateHeroCard(HeroCard expectedHeroCard, int index) { + return activity -> { + HeroCard attachedHeroCard = (HeroCard) activity.getAttachments().get(index).getContent(); + + Assert.assertEquals(expectedHeroCard.getTitle(), attachedHeroCard.getTitle()); + Assert.assertEquals(expectedHeroCard.getButtons().size(), attachedHeroCard.getButtons().size()); + for (int i = 0; i < expectedHeroCard.getButtons().size(); i++) { + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getType(), + attachedHeroCard.getButtons().get(i).getType()); + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getValue(), + attachedHeroCard.getButtons().get(i).getValue()); + Assert.assertEquals(expectedHeroCard.getButtons().get(i).getTitle(), + attachedHeroCard.getButtons().get(i).getTitle()); + } + }; + } + + private Consumer<Activity> validateSpeak(String expectedText, String expectedSpeak) { + return activity -> { + Activity msg = (Activity) activity; + Assert.assertEquals(expectedText, msg.getText()); + Assert.assertEquals(expectedSpeak, msg.getSpeak()); + }; + } + + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java new file mode 100644 index 000000000..51631bd8f --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java @@ -0,0 +1,200 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.javatuples.Triplet; +import org.junit.Test; + +public class ConfirmPromptLocTests { + + @Test + public void ConfirmPrompt_Activity_Locale_Default() { + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Default() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, "", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(null, "not-supported", "(1) Yes or (2) No", "Yes", "1"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Default_Number() { + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.English, "(1) Yes or (2) No", "2", "0"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, Culture.Spanish, "(1) Sí o (2) No", "2", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Default_Number() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, "", "(1) Yes or (2) No", "1", "1"); + ConfirmPrompt_Locale_Impl(null, "not-supported", "(1) Yes or (2) No", "1", "1"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Activity() { + ConfirmPrompt_Locale_Impl(Culture.English, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl(Culture.English, null, "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl(Culture.Spanish, null, "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl(Culture.Spanish, null, "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Activity_Locale_Illegal_Activity() { + ConfirmPrompt_Locale_Impl(null, null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("", null, "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("not-supported", null, "(1) Yes or (2) No", "Yes", "1"); + } + + @Test + public void ConfirmPrompt_Locale_Variations_English() { + ConfirmPrompt_Locale_Impl("en-us", "en-us", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-us", "en-us", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en-US", "en-US", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-US", "en-US", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en-Us", "en-Us", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en-Us", "en-Us", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("EN", "EN", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("EN", "EN", "(1) Yes or (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("en", "en", "(1) Yes or (2) No", "Yes", "1"); + ConfirmPrompt_Locale_Impl("en", "en", "(1) Yes or (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Locale_Variations_Spanish() { + ConfirmPrompt_Locale_Impl("es-es", "es-es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-es", "es-es", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es-ES", "es-ES", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-ES", "es-ES", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es-Es", "es-Es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es-Es", "es-Es", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("ES", "ES", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("ES", "ES", "(1) Sí o (2) No", "No", "0"); + ConfirmPrompt_Locale_Impl("es", "es", "(1) Sí o (2) No", "Sí", "1"); + ConfirmPrompt_Locale_Impl("es", "es", "(1) Sí o (2) No", "No", "0"); + } + + @Test + public void ConfirmPrompt_Locale_Override_ChoiceDefaults() { + ConfirmPrompt_Locale_Override_ChoiceDefaults("custom-custom", "(1) customYes customOr (2) customNo", + "customYes", "1"); + ConfirmPrompt_Locale_Override_ChoiceDefaults("custom-custom", "(1) customYes customOr (2) customNo", "customNo", + "0"); + } + + public void ConfirmPrompt_Locale_Override_ChoiceDefaults(String defaultLocale, String prompt, String utterance, + String expectedResponse) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ChoicePrompt listPrompt = new ChoicePrompt("ChoicePrompt", null, Culture.English); + listPrompt.setStyle(ListStyle.NONE); + + PromptCultureModel culture = new PromptCultureModel(); + culture.setInlineOr(" customOr "); + culture.setInlineOrMore(" customOrMore "); + culture.setLocale("custom-custom"); + culture.setSeparator("customSeparator"); + culture.setNoInLanguage("customNo"); + culture.setYesInLanguage("customYes"); + + Map<String, Triplet<Choice, Choice, ChoiceFactoryOptions>> customDict = new HashMap<String, Triplet<Choice, Choice, ChoiceFactoryOptions>>(); + customDict.put(culture.getLocale(), + new Triplet<Choice, Choice, ChoiceFactoryOptions>(new Choice(culture.getYesInLanguage()), + new Choice(culture.getNoInLanguage()), new ChoiceFactoryOptions(culture.getSeparator(), + culture.getInlineOr(), culture.getInlineOrMore(), true))); + // Prompt should default to English if locale is a non-supported value + dialogs.add(new ConfirmPrompt("ConfirmPrompt", customDict, null, defaultLocale)); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Prompt."); + options.setPrompt(activity); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("1")); + } else { + turnContext.sendActivity(MessageFactory.text("0")); + } + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest() + .join(); + } + + private void ConfirmPrompt_Locale_Impl(String activityLocale, String defaultLocale, String prompt, String utterance, + String expectedResponse) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("ConfirmPrompt_Locale_Impl", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Prompt should default to English if locale is a non-supported value + dialogs.add(new ConfirmPrompt("ConfirmPrompt", null, null, defaultLocale)); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Prompt."); + options.setPrompt(activity); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("1")); + } else { + turnContext.sendActivity(MessageFactory.text("0")); + } + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java new file mode 100644 index 000000000..d017e8996 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java @@ -0,0 +1,390 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +public class ConfirmPromptTests { + + @Test + public void ChoicePromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ConfirmPrompt("")); + } + + @Test + public void ConfirmPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new ConfirmPrompt(null)); + } + + @Test + public void ConfirmPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please confirm."); + options.setPrompt(activity); + + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("yes") + .assertReply("Confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptRetry() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptNoOptions() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply(" (1) Yes or (2) No") + .send("lala") + .assertReply(" (1) Yes or (2) No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsNumbers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(true); + eventPrompt.setChoiceOptions(choiceOptions); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("2") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsMultipleAttempts() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(true); + eventPrompt.setChoiceOptions(choiceOptions); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. (1) Yes or (2) No") + .send("lala") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("what") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. (1) Yes or (2) No") + .send("2") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ConfirmPromptChoiceOptionsNoNumbers() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + ChoiceFactoryOptions choiceOptions = new ChoiceFactoryOptions(); + choiceOptions.setIncludeNumbers(false); + choiceOptions.setInlineSeparator("~"); + eventPrompt.setChoiceOptions(choiceOptions); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity prompt = new Activity(ActivityTypes.MESSAGE); + prompt.setText("Please confirm."); + options.setPrompt(prompt); + Activity retryPrompt = new Activity(ActivityTypes.MESSAGE); + retryPrompt.setText("Please confirm, say 'yes' or 'no' or something like that."); + options.setRetryPrompt(retryPrompt); + dc.prompt("ConfirmPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if ((Boolean) results.getResult()) { + turnContext.sendActivity(MessageFactory.text("Confirmed.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Not confirmed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Please confirm. Yes or No") + .send("2") + .assertReply("Please confirm, say 'yes' or 'no' or something like that. Yes or No") + .send("no") + .assertReply("Not confirmed.") + .startTest() + .join(); + } + + @Test + public void ShouldUsePromptClassStyleProperty() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("is it true?"); + options.setPrompt(activity); + + dc.prompt("ConfirmPrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("is it true? (1) Yes or (2) No") + .startTest() + .join(); + } + + @Test + public void PromptOptionsStyleShouldOverridePromptClassStyleProperty() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + ConfirmPrompt eventPrompt = new ConfirmPrompt("ConfirmPrompt", null, Culture.English); + eventPrompt.setStyle(ListStyle.INLINE); + dialogs.add(eventPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("is it true?"); + options.setPrompt(activity); + options.setStyle(ListStyle.NONE); + dc.prompt("ConfirmPrompt", options).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("is it true?") + .startTest() + .join(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java new file mode 100644 index 000000000..bae5a24e4 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/DateTimePromptTests.java @@ -0,0 +1,192 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Test; + +public class DateTimePromptTests { + + @Test + public void BasicDateTimePrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", null, Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + //Activity reply = MessageFactory.text($"Timex:'{resolution.Timex}' Value:'{resolution.Value}'"); + Activity reply = MessageFactory.text(String.format("Timex:'%s' Value:'%s'", + resolution.get(0).getTimex(), + resolution.get(0).getValue())); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("5th December 2018 at 9am") + .assertReply("Timex:'2018-12-05T09' Value:'2018-12-05 09:00:00'") + .startTest() + .join(); + } + + @Test + public void MultipleResolutionsDateTimePrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", null, Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + + List<String> elements = new ArrayList<String>(); + + for (DateTimeResolution dateTimeResolution : resolution) { + if (!elements.contains(dateTimeResolution.getTimex())) { + elements.add(dateTimeResolution.getTimex()); + } + } + Activity reply = MessageFactory.text(String.join(" ", elements)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("Wednesday 4 oclock") + .assertReply("XXXX-WXX-3T04 XXXX-WXX-3T16") + .startTest() + .join(); + } + + @Test + public void DateTimePromptWithValidator() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("DateTimePromptWithValidator", "testuser", "testbot")) + .use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add custom activity prompt to DialogSet. + DateTimePrompt dateTimePrompt = new DateTimePrompt("DateTimePrompt", + new DateTimeValidator(), Culture.English); + // Create and add number prompt to DialogSet. + dialogs.add(dateTimePrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("What date would you like?"); + options.setPrompt(activity); + dc.prompt("DateTimePrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + ArrayList<DateTimeResolution> resolution = (ArrayList<DateTimeResolution>) results.getResult(); + //Activity reply = MessageFactory.text($"Timex:'{resolution.Timex}' Value:'{resolution.Value}'"); + Activity reply = MessageFactory.text(String.format("Timex:'%s' Value:'%s'", + resolution.get(0).getTimex(), + resolution.get(0).getValue())); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("What date would you like?") + .send("5th December 2018 at 9am") + .assertReply("Timex:'2018-12-05' Value:'2018-12-05'") + .startTest() + .join(); + } + + class DateTimeValidator implements PromptValidator<List<DateTimeResolution>> { + + DateTimeValidator(){ + } + + @Override + public CompletableFuture<Boolean> promptValidator(PromptValidatorContext<List<DateTimeResolution>> prompt) { + if (prompt.getRecognized().getSucceeded()) { + DateTimeResolution resolution = prompt.getRecognized().getValue().get(0); + + // re-write the resolution to just include the date part. + DateTimeResolution rewrittenResolution = new DateTimeResolution(); + rewrittenResolution.setTimex(resolution.getTimex().split("T")[0]); + rewrittenResolution.setValue(resolution.getValue().split(" ")[0]); + + List<DateTimeResolution> valueList = new ArrayList<DateTimeResolution>(); + valueList.add(rewrittenResolution); + prompt.getRecognized().setValue(valueList); + return CompletableFuture.completedFuture(true); + } + + return CompletableFuture.completedFuture(false); + } + } +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java new file mode 100644 index 000000000..844916fd9 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/EventActivityPrompt.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.schema.Activity; + +public class EventActivityPrompt extends ActivityPrompt { + + public EventActivityPrompt(String dialogId, PromptValidator<Activity> activityPromptTestValidator) { + super(dialogId, activityPromptTestValidator); + } + + public CompletableFuture<Void> onPromptNullContext(Object options) { + PromptOptions opt = (PromptOptions) options; + // should throw ArgumentNullException + return super.onPrompt(null, null, opt, false); + } + + public CompletableFuture<Void> onPromptNullOptions(DialogContext dc) { + // should throw ArgumentNullException + return super.onPrompt(dc.getContext(), null, null, false); + } + +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java new file mode 100644 index 000000000..6d3659faa --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptMock.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.dialogs.DialogContext; + +public class NumberPromptMock extends NumberPrompt<Integer> { + + public NumberPromptMock(String dialogId, PromptValidator<Integer> validator, String defaultLocale) + throws UnsupportedDataTypeException { + super(dialogId, validator, defaultLocale, Integer.class); + } + + public CompletableFuture<Void> onPromptNullContext(Object options) { + PromptOptions opt = (PromptOptions) options; + + // should throw ArgumentNullException + return onPrompt(null, null, opt, false); + } + + public CompletableFuture<Void> onPromptNullOptions(DialogContext dc) { + // should throw ArgumentNullException + return onPrompt(dc.getContext(), null, null, false); + } + + public CompletableFuture<Void> onRecognizeNullContext() { + // should throw ArgumentNullException + onRecognize(null, null, null).join(); + return CompletableFuture.completedFuture(null); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java new file mode 100644 index 000000000..3ea7723a0 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java @@ -0,0 +1,635 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import javax.activation.UnsupportedDataTypeException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.recognizers.text.Culture; + +import org.junit.Assert; +import org.junit.Test; + +public class NumberPromptTests { + + @Test + public void NumberPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt<Integer>("", Integer.class)); + } + + @Test + public void NumberPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt<Integer>(null, Integer.class)); + } + + @Test + public void NumberPromptWithUnsupportedTypeShouldFail() { + Assert.assertThrows(UnsupportedDataTypeException.class, () -> new NumberPrompt<Short>("prompt", Short.class)); + } + + @Test + public void NumberPromptWithNullTurnContextShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Please send a number."); + options.setPrompt(activity); + numberPromptMock.onPromptNullContext(options).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnPromptErrorsWithNullOptions() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + DialogSet dialogs = new DialogSet(dialogState); + // Create and add custom activity prompt to DialogSet. + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + + dialogs.add(numberPromptMock); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + numberPromptMock.onPromptNullOptions(dc).join(); + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .startTest() + .join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OnRecognizeWithNullTurnContextShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + NumberPromptMock numberPromptMock = new NumberPromptMock("NumberPromptMock", null, null); + numberPromptMock.onRecognizeNullContext(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void NumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Bot received the number '42'.") + .startTest() + .join(); + } + + @Test + public void NumberPromptRetry() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("You must enter a number."); + options.setRetryPrompt(retryActivity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("hello") + .assertReply("You must enter a number.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void NumberPromptValidator() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + PromptValidator<Integer> validator = (promptContext) -> { + Integer result = promptContext.getRecognized().getValue(); + + if (result < 100 && result > 0) { + return CompletableFuture.completedFuture(true); + } + + return CompletableFuture.completedFuture(false); + }; + + // Create and add number prompt to DialogSet. + NumberPrompt<Integer> numberPrompt = new NumberPrompt<Integer>("NumberPrompt", validator, + PromptCultureModels.ENGLISH_CULTURE, Integer.class); + dialogs.add(numberPrompt); + + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("You must enter a positive number less than 100."); + options.setRetryPrompt(retryActivity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + int numberResult = (int) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("150") + .assertReply("You must enter a positive number less than 100.") + .send("64") + .assertReply("Bot received the number '64'.") + .startTest() + .join(); + } + + @Test + public void FloatNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Float> numberPrompt = new NumberPrompt<Float>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Float.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Float numberResult = (Float) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } + + @Test + public void LongNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Long> numberPrompt = new NumberPrompt<Long>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Long.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Long numberResult = (Long) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("42") + .assertReply("Bot received the number '42'.") + .startTest() + .join(); + } + + @Test + public void DoubleNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } + + @Test + public void CurrencyNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) .send("hello") + .assertReply("Enter a number.") + .send("$500") + .assertReply("Bot received the number '500'.") + .startTest() + .join(); + } + + @Test + public void AgeNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("i am 18 years old") + .assertReply("Bot received the number '18'.") + .startTest() + .join(); + } + + @Test + public void DimensionNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("I've run 5km") + .assertReply("Bot received the number '5'.") + .startTest() + .join(); + } + + @Test + public void TemperatureNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.ENGLISH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("The temperature is 32C") + .assertReply("Bot received the number '32'.") + .startTest() + .join(); + } + + @Test + public void CultureThruNumberPromptCtor() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.DUTCH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + Assert.assertTrue(3.14 == numberResult); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3,14") + .startTest() + .join(); + } + + @Test + public void CultureThruActivityNumberPrompt() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, + PromptCultureModels.DUTCH_CULTURE, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + Assert.assertTrue(3.14 == numberResult); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send(new Activity() { + { + setType(ActivityTypes.MESSAGE); + setText("3,14"); + setLocale(PromptCultureModels.DUTCH_CULTURE); + } + }) + .startTest() + .join(); + } + + @Test + public void NumberPromptDefaultsToEnUsLocale() throws UnsupportedDataTypeException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Create and add number prompt to DialogSet. + NumberPrompt<Double> numberPrompt = new NumberPrompt<Double>("NumberPrompt", null, null, Double.class); + dialogs.add(numberPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter a number."); + options.setPrompt(activity); + dc.prompt("NumberPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + Double numberResult = (Double) results.getResult(); + turnContext.sendActivity( + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter a number.") + .send("3.14") + .assertReply("Bot received the number '3.14'.") + .startTest() + .join(); + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java new file mode 100644 index 000000000..1c06ab1ec --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java @@ -0,0 +1,325 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.IllformedLocaleException; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.function.Consumer; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TraceTranscriptLogger; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.ChoiceFactoryOptions; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.choices.ListStyle; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.SuggestedActions; +import com.microsoft.recognizers.text.Culture; + +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; +import org.junit.Test; + +public class TextPromptTests { + + @Test + public void TextPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new TextPrompt("")); + } + + @Test + public void TextPromptWithNullIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new TextPrompt(null)); + } + + @Test + public void TextPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("textPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt"); + + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("some text") + .assertReply("Bot received the text 'some text'.").startTest().join(); + } + + @Test + public void TextPromptWithNaughtyStrings() throws FileNotFoundException { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("textPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt"); + + dialogs.add(textPrompt); + File f = new File(ClassLoader.getSystemClassLoader().getResource("naughtyStrings.txt").getFile()); + + FileReader fr = new FileReader(f); + BufferedReader br = new BufferedReader(fr); + String naughtyString = ""; + do { + naughtyString = GetNextNaughtyString(br); + try { + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(textResult); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("Enter some text.") + .send(naughtyString) + .assertReply(naughtyString) + .startTest() + .join(); + } + catch (Exception e) { + // If the input message is empty after a .Trim() operation, character the comparison will fail + // because the reply message will be a Message Activity with null as Text, this is expected behavior + String message = e.getMessage(); + boolean messageIsBlank = e.getMessage() + .contains("should match expected") + && naughtyString.equals(" "); + boolean messageIsEmpty = e.getMessage().contains("should match expected") + && StringUtils.isBlank(naughtyString); + if (!(messageIsBlank || messageIsEmpty)) { + throw e; + } + } + } + while (!StringUtils.isEmpty(naughtyString)); + try { + fr.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Test + public void TextPromptValidator() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("TextPromptValidator"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() <= 3) { + promptContext.getContext() + .sendActivity(MessageFactory.text("Make sure the text is greater than three characters.")); + return CompletableFuture.completedFuture(false); + } else { + return CompletableFuture.completedFuture(true); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("Make sure the text is greater than three characters.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + @Test + public void TextPromptWithRetryPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("TextPromptWithRetryPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() >= 3) { + return CompletableFuture.completedFuture(true); + } else { + return CompletableFuture.completedFuture(false); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Make sure the text is greater than three characters."); + options.setRetryPrompt(retryActivity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("Make sure the text is greater than three characters.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + @Test + public void TextPromptValidatorWithMessageShouldNotSendRetryPrompt() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor<DialogState> dialogState = convoState + .createProperty("TextPromptValidatorWithMessageShouldNotSendRetryPrompt"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)) + .use(new TranscriptLoggerMiddleware(new TraceTranscriptLogger())); + + PromptValidator<String> validator = (promptContext) -> { + String value = promptContext.getRecognized().getValue(); + if (value.length() <= 3) { + promptContext.getContext() + .sendActivity(MessageFactory.text("The text should be greater than 3 chars.")); + return CompletableFuture.completedFuture(false); + } else { + return CompletableFuture.completedFuture(true); + } + }; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + TextPrompt textPrompt = new TextPrompt("TextPrompt", validator); + // Create and add number prompt to DialogSet. + dialogs.add(textPrompt); + new TestFlow(adapter, (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + PromptOptions options = new PromptOptions(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Enter some text."); + options.setPrompt(activity); + Activity retryActivity = new Activity(ActivityTypes.MESSAGE); + retryActivity.setText("Make sure the text is greater than three characters."); + options.setRetryPrompt(retryActivity); + dc.prompt("TextPrompt", options).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + String textResult = (String) results.getResult(); + Activity reply = MessageFactory.text(String.format("Bot received the text '%s'.", textResult)); + turnContext.sendActivity(reply).join(); + } + return CompletableFuture.completedFuture(null); + }).send("hello").assertReply("Enter some text.").send("hi") + .assertReply("The text should be greater than 3 chars.").send("hello") + .assertReply("Bot received the text 'hello'.").startTest().join(); + } + + private static String GetNextNaughtyString(BufferedReader reader) { + String textLine; + try { + while ((textLine = reader.readLine()) != null) { + if (!textLine.startsWith("#")) { + return textLine; + } + } + } catch (IOException e) { + e.printStackTrace(); + } + + return ""; + } +} + diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java index e2b0b4223..cb0ff880d 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.integration; import org.slf4j.LoggerFactory; @@ -34,7 +37,7 @@ public ClasspathPropertiesConfiguration() { /** * Returns a value for the specified property name. - * + * * @param key The property name. * @return The property value. */ @@ -42,4 +45,12 @@ public ClasspathPropertiesConfiguration() { public String getProperty(String key) { return properties.getProperty(key); } + + /** + * @return The Properties value. + */ + @Override + public Properties getProperties() { + return this.properties; + } } diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java index 07a7bac93..dda35a4a8 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java @@ -3,15 +3,24 @@ package com.microsoft.bot.integration; +import java.util.Properties; + /** * Provides read-only access to configuration properties. */ public interface Configuration { /** * Returns a value for the specified property name. - * + * * @param key The property name. * @return The property value. */ String getProperty(String key); + + /** + * Returns the Properties in the Configuration. + * + * @return The Properties in the Configuration. + */ + Properties getProperties(); } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java index 06151c02f..73f9466ec 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java @@ -26,6 +26,10 @@ private Serialization() { objectMapper = new ObjectMapper(); objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); objectMapper.findAndRegisterModules(); + + // NOTE: Undetermined if we should accommodate non-public fields. The normal + // Bean pattern, and Jackson default, is for public fields or accessors. + //objectMapper.setVisibility(PropertyAccessor.FIELD, Visibility.ANY); } /** @@ -62,6 +66,48 @@ public static <T> T safeGetAs(Object obj, Class<T> classType) throws JsonProcess return objectMapper.treeToValue(node, classType); } + + /** + * @param obj The Object to clone + * @return Object The cloned Object + */ + public static Object clone(Object obj) { + if (obj == null) { + return null; + } + + JsonNode node = objectMapper.valueToTree(obj); + try { + return objectMapper.treeToValue(node, obj.getClass()); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return null; + } + } + + /** + * @param <T> The Type of the Class + * @param src The source JsonNode + * @param cls The Class to Map + * @return the result of the mapping + */ + public static <T> T treeToValue(JsonNode src, Class<T> cls) { + try { + return objectMapper.treeToValue(src, cls); + } catch (JsonProcessingException e) { + return null; + } + } + + /** + * Convert Object to JsonNode. + * @param obj The object to convert. + * @return The JsonNode for the object tree. + */ + public static JsonNode objectToTree(Object obj) { + return objectMapper.valueToTree(obj); + } + /** * Deserializes an object to a type as a future to ease CompletableFuture * chaining. @@ -117,4 +163,77 @@ public static String toString(Object source) throws JsonProcessingException { public static JsonNode jsonToTree(String json) throws IOException { return objectMapper.readTree(json); } + + + /** + * @param s The string to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(String s) { + return objectMapper.getNodeFactory().textNode(s); + } + + + /** + * @param i The int to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(int i) { + return objectMapper.getNodeFactory().numberNode(i); + } + + + /** + * @param l The long to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(long l) { + return objectMapper.getNodeFactory().numberNode(l); + } + + + /** + * @param f The float to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(float f) { + return objectMapper.getNodeFactory().numberNode(f); + } + + + /** + * @param d The double to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(double d) { + return objectMapper.getNodeFactory().numberNode(d); + } + + + /** + * @param s The short to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(short s) { + return objectMapper.getNodeFactory().numberNode(s); + } + + + /** + * @param b The boolean to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(boolean b) { + return objectMapper.getNodeFactory().booleanNode(b); + } + + + /** + * @param b The byte to convert to a JsonNode + * @return JsonNode + */ + public static JsonNode asNode(byte b) { + return objectMapper.getNodeFactory().numberNode(b); + } } + diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java index b5037fd47..164cbdb39 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInConstants.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java index 70e5c44b3..5297b20a6 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/MeetingParticipantInfo.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema.teams; import com.fasterxml.jackson.annotation.JsonInclude; diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java index 61bf75290..61f15ff75 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; import com.fasterxml.jackson.core.JsonProcessingException; diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java index 618cc930f..4e56f9905 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/CardActionTest.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.schema; import org.junit.Assert; diff --git a/pom.xml b/pom.xml index c1d6d2b91..303a30fa7 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ <maven.compiler.target>1.8</maven.compiler.target> <maven.compiler.source>1.8</maven.compiler.source> <checkstyle.version>3.1.0</checkstyle.version> - <pmd.version>3.12.0</pmd.version> + <pmd.version>3.13.0</pmd.version> <repo.id>MyGet</repo.id> <repo.url>https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/</repo.url> </properties> diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java index 0cd816047..bc874c9c0 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java index 8e48890e6..b614f22d4 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java index 9385a5d82..bdb00d86b 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java @@ -1,3 +1,6 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + package com.microsoft.bot.sample.teamsactionpreview.models; import com.fasterxml.jackson.annotation.JsonAnyGetter; diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java index a08b27333..9cb519aba 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java @@ -8,7 +8,7 @@ import com.microsoft.bot.builder.MessageFactory; import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.teams.TeamsActivityHandler; -import com.microsoft.bot.integration.Async; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.integration.Configuration; import com.microsoft.bot.sample.teamstaskmodule.models.AdaptiveCardTaskFetchValue; import com.microsoft.bot.sample.teamstaskmodule.models.CardTaskFetchValue; From fb991ab487f809737826f3469b3599c5d78269a3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 29 Jan 2021 08:26:32 -0600 Subject: [PATCH 048/221] Bump guava from 24.1-jre to 24.1.1-jre in /libraries/bot-dialogs (#926) Bumps [guava](https://github.com/google/guava) from 24.1-jre to 24.1.1-jre. - [Release notes](https://github.com/google/guava/releases) - [Commits](https://github.com/google/guava/commits) Signed-off-by: dependabot[bot] <support@github.com> Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- libraries/bot-dialogs/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index 25da5f40d..94560c4bf 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -82,7 +82,7 @@ <dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> - <version>24.1-jre</version> + <version>24.1.1-jre</version> </dependency> <dependency> <groupId>org.javatuples</groupId> From fa525c6ac4daa38dad0dd6cb6724931ac01aa0cd Mon Sep 17 00:00:00 2001 From: tracyboehrer <tracyboehrer@users.noreply.github.com> Date: Sat, 30 Jan 2021 11:18:48 -0600 Subject: [PATCH 049/221] Sample 05.multi-turn-prompt (#927) * Initial 05.multi-turn-prompt * Sample 05.multi-turn-prompt --- libraries/bot-dialogs/pom.xml | 2 +- .../com/microsoft/bot/dialogs/Dialog.java | 159 +++++-- .../microsoft/bot/dialogs/DialogContext.java | 2 +- .../bot/dialogs/WaterfallDialog.java | 7 +- .../bot/dialogs/choices/ChoiceFactory.java | 11 + .../bot/dialogs/prompts/AttachmentPrompt.java | 2 +- .../bot/dialogs/prompts/ChoicePrompt.java | 11 +- .../bot/dialogs/prompts/ConfirmPrompt.java | 2 +- .../bot/dialogs/prompts/DateTimePrompt.java | 2 +- .../bot/dialogs/prompts/NumberPrompt.java | 31 +- .../microsoft/bot/dialogs/prompts/Prompt.java | 4 +- .../bot/dialogs/prompts/TextPrompt.java | 2 +- .../bot/dialogs/ComponentDialogTests.java | 4 +- .../microsoft/bot/dialogs/WaterfallTests.java | 4 +- .../dialogs/prompts/NumberPromptTests.java | 2 +- samples/05.multi-turn-prompt/LICENSE | 21 + samples/05.multi-turn-prompt/README.md | 90 ++++ .../new-rg-parameters.json | 42 ++ .../preexisting-rg-parameters.json | 39 ++ .../template-with-new-rg.json | 191 ++++++++ .../template-with-preexisting-rg.json | 158 +++++++ samples/05.multi-turn-prompt/pom.xml | 244 ++++++++++ .../sample/multiturnprompt/Application.java | 48 ++ .../bot/sample/multiturnprompt/DialogBot.java | 55 +++ .../sample/multiturnprompt/UserProfile.java | 13 + .../multiturnprompt/UserProfileDialog.java | 224 ++++++++++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../multiturnprompt/ApplicationTest.java | 19 + .../translation/TranslationMiddleware.java | 2 +- 33 files changed, 1768 insertions(+), 77 deletions(-) create mode 100644 samples/05.multi-turn-prompt/LICENSE create mode 100644 samples/05.multi-turn-prompt/README.md create mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json create mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/05.multi-turn-prompt/pom.xml create mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java create mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java create mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java create mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java create mode 100644 samples/05.multi-turn-prompt/src/main/resources/application.properties create mode 100644 samples/05.multi-turn-prompt/src/main/resources/log4j2.json create mode 100644 samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/05.multi-turn-prompt/src/main/webapp/index.html create mode 100644 samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index 94560c4bf..1357cbf29 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -70,7 +70,7 @@ <dependency> <groupId>com.microsoft.bot</groupId> <artifactId>bot-builder</artifactId> - <version>4.6.0-preview8</version> + <version>${project.version}</version> </dependency> <dependency> <groupId>com.microsoft.bot</groupId> diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 6aced56e8..59974a94a 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.microsoft.bot.builder.BotTelemetryClient; import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.StatePropertyAccessor; import com.microsoft.bot.builder.TurnContext; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -15,11 +16,13 @@ * Base class for all dialogs. */ public abstract class Dialog { + /** - * A {@link DialogTurnResult} that indicates that the current dialog is - * still active and waiting for input from the user next turn. + * A {@link DialogTurnResult} that indicates that the current dialog is still active and waiting + * for input from the user next turn. */ - public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING); + public static final DialogTurnResult END_OF_TURN = new DialogTurnResult( + DialogTurnStatus.WAITING); @JsonIgnore private BotTelemetryClient telemetryClient; @@ -29,6 +32,7 @@ public abstract class Dialog { /** * Initializes a new instance of the Dialog class. + * * @param dialogId The ID to assign to this dialog. */ public Dialog(String dialogId) { @@ -38,6 +42,7 @@ public Dialog(String dialogId) { /** * Gets id for the dialog. + * * @return Id for the dialog. */ public String getId() { @@ -49,6 +54,7 @@ public String getId() { /** * Sets id for the dialog. + * * @param withId Id for the dialog. */ public void setId(String withId) { @@ -57,6 +63,7 @@ public void setId(String withId) { /** * Gets the {@link BotTelemetryClient} to use for logging. + * * @return The BotTelemetryClient to use for logging. */ public BotTelemetryClient getTelemetryClient() { @@ -65,6 +72,7 @@ public BotTelemetryClient getTelemetryClient() { /** * Sets the {@link BotTelemetryClient} to use for logging. + * * @param withTelemetryClient The BotTelemetryClient to use for logging. */ public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { @@ -74,10 +82,9 @@ public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { /** * Called when the dialog is started and pushed onto the dialog stack. * - * @param dc The {@link DialogContext} for the current turn of - * conversation. - * @return If the task is successful, the result indicates whether the dialog is still - * active after the turn has been processed by the dialog. + * @param dc The {@link DialogContext} for the current turn of conversation. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. */ public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc) { return beginDialog(dc, null); @@ -86,25 +93,25 @@ public CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc) { /** * Called when the dialog is started and pushed onto the dialog stack. * - * @param dc The {@link DialogContext} for the current turn of - * conversation. + * @param dc The {@link DialogContext} for the current turn of conversation. * @param options Initial information to pass to the dialog. - * @return If the task is successful, the result indicates whether the dialog is still - * active after the turn has been processed by the dialog. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. */ - public abstract CompletableFuture<DialogTurnResult> beginDialog(DialogContext dc, Object options); + public abstract CompletableFuture<DialogTurnResult> beginDialog( + DialogContext dc, Object options + ); /** - * Called when the dialog is _continued_, where it is the active dialog and the - * user replies with a new activity. + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. * - * <p>If this method is *not* overridden, the dialog automatically ends when the user replies.</p> + * <p>If this method is *not* overridden, the dialog automatically ends when the user + * replies.</p> * - * @param dc The {@link DialogContext} for the current turn of - * conversation. - * @return If the task is successful, the result indicates whether the dialog is still - * active after the turn has been processed by the dialog. The result may also contain a - * return value. + * @param dc The {@link DialogContext} for the current turn of conversation. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. The result may also contain a return value. */ public CompletableFuture<DialogTurnResult> continueDialog(DialogContext dc) { // By default just end the current dialog. @@ -115,16 +122,17 @@ public CompletableFuture<DialogTurnResult> continueDialog(DialogContext dc) { * Called when a child dialog completed this turn, returning control to this dialog. * * <p>Generally, the child dialog was started with a call to - * {@link #beginDialog(DialogContext, Object)} However, if the - * {@link DialogContext#replaceDialog(String)} method - * is called, the logical child dialog may be different than the original.</p> + * {@link #beginDialog(DialogContext, Object)} However, if the {@link + * DialogContext#replaceDialog(String)} method is called, the logical child dialog may be + * different than the original.</p> * - * <p>If this method is *not* overridden, the dialog automatically ends when the user replies.</p> + * <p>If this method is *not* overridden, the dialog automatically ends when the user + * replies.</p> * - * @param dc The dialog context for the current turn of the conversation. + * @param dc The dialog context for the current turn of the conversation. * @param reason Reason why the dialog resumed. - * @return If the task is successful, the result indicates whether this dialog is still - * active after this dialog turn has been processed. + * @return If the task is successful, the result indicates whether this dialog is still active + * after this dialog turn has been processed. */ public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext dc, DialogReason reason) { return resumeDialog(dc, reason, null); @@ -134,43 +142,53 @@ public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext dc, Dialog * Called when a child dialog completed this turn, returning control to this dialog. * * <p>Generally, the child dialog was started with a call to - * {@link #beginDialog(DialogContext, Object)} However, if the - * {@link DialogContext#replaceDialog(String, Object)} method - * is called, the logical child dialog may be different than the original.</p> + * {@link #beginDialog(DialogContext, Object)} However, if the {@link + * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may + * be different than the original.</p> * - * <p>If this method is *not* overridden, the dialog automatically ends when the user replies.</p> + * <p>If this method is *not* overridden, the dialog automatically ends when the user + * replies.</p> * - * @param dc The dialog context for the current turn of the conversation. + * @param dc The dialog context for the current turn of the conversation. * @param reason Reason why the dialog resumed. - * @param result Optional, value returned from the dialog that was called. The type of the - * value returned is dependent on the child dialog. - * @return If the task is successful, the result indicates whether this dialog is still - * active after this dialog turn has been processed. + * @param result Optional, value returned from the dialog that was called. The type of the value + * returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog is still active + * after this dialog turn has been processed. */ - public CompletableFuture<DialogTurnResult> resumeDialog(DialogContext dc, DialogReason reason, Object result) { + public CompletableFuture<DialogTurnResult> resumeDialog( + DialogContext dc, DialogReason reason, Object result + ) { // By default just end the current dialog and return result to parent. return dc.endDialog(result); } /** * Called when the dialog should re-prompt the user for input. + * * @param turnContext The context object for this turn. - * @param instance State information for this dialog. + * @param instance State information for this dialog. * @return A CompletableFuture representing the asynchronous operation. */ - public CompletableFuture<Void> repromptDialog(TurnContext turnContext, DialogInstance instance) { + public CompletableFuture<Void> repromptDialog( + TurnContext turnContext, DialogInstance instance + ) { // No-op by default return CompletableFuture.completedFuture(null); } /** * Called when the dialog is ending. + * * @param turnContext The context object for this turn. - * @param instance State information associated with the instance of this dialog on the dialog stack. - * @param reason Reason why the dialog ended. + * @param instance State information associated with the instance of this dialog on the + * dialog stack. + * @param reason Reason why the dialog ended. * @return A CompletableFuture representing the asynchronous operation. */ - public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + public CompletableFuture<Void> endDialog( + TurnContext turnContext, DialogInstance instance, DialogReason reason + ) { // No-op by default return CompletableFuture.completedFuture(null); } @@ -178,7 +196,9 @@ public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance /** * Gets a unique String which represents the version of this dialog. If the version changes * between turns the dialog system will emit a DialogChanged event. - * @return Unique String which should only change when dialog has changed in a way that should restart the dialog. + * + * @return Unique String which should only change when dialog has changed in a way that should + * restart the dialog. */ @JsonIgnore public String getVersion() { @@ -188,8 +208,9 @@ public String getVersion() { /** * Called when an event has been raised, using `DialogContext.emitEvent()`, by either the * current dialog or a dialog that the current dialog started. + * * @param dc The dialog context for the current turn of conversation. - * @param e The event being raised. + * @param e The event being raised. * @return True if the event is handled by the current dialog and bubbling should stop. */ public CompletableFuture<Boolean> onDialogEvent(DialogContext dc, DialogEvent e) { @@ -222,8 +243,9 @@ public CompletableFuture<Boolean> onDialogEvent(DialogContext dc, DialogEvent e) * dialogs from performing their default processing.</p> * * @param dc The dialog context for the current turn of conversation. - * @param e The event being raised. - * @return Whether the event is handled by the current dialog and further processing should stop. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should + * stop. */ protected CompletableFuture<Boolean> onPreBubbleEvent(DialogContext dc, DialogEvent e) { return CompletableFuture.completedFuture(false); @@ -232,12 +254,14 @@ protected CompletableFuture<Boolean> onPreBubbleEvent(DialogContext dc, DialogEv /** * Called after an event was bubbled to all parents and wasn't handled. * - * <p>This is a good place to perform default processing logic for an event. Returning `true` will + * <p>This is a good place to perform default processing logic for an event. Returning `true` + * will * prevent any processing of the event by child dialogs.</p> * * @param dc The dialog context for the current turn of conversation. - * @param e The event being raised. - * @return Whether the event is handled by the current dialog and further processing should stop. + * @param e The event being raised. + * @return Whether the event is handled by the current dialog and further processing should + * stop. */ protected CompletableFuture<Boolean> onPostBubbleEvent(DialogContext dc, DialogEvent e) { return CompletableFuture.completedFuture(false); @@ -245,9 +269,46 @@ protected CompletableFuture<Boolean> onPostBubbleEvent(DialogContext dc, DialogE /** * Computes an id for the Dialog. + * * @return The id. */ protected String onComputeId() { return this.getClass().getName(); } + + /** + * Creates a dialog stack and starts a dialog, pushing it onto the stack. + * + * @param dialog The dialog to start. + * @param turnContext The context for the current turn of the conversation. + * @param accessor The StatePropertyAccessor accessor with which to manage the state of the + * dialog stack. + * @return A Task representing the asynchronous operation. + */ + public static CompletableFuture<Void> run( + Dialog dialog, + TurnContext turnContext, + StatePropertyAccessor<DialogState> accessor + ) { + DialogSet dialogSet = new DialogSet(accessor); + dialogSet.add(dialog); + dialogSet.setTelemetryClient(dialog.getTelemetryClient()); + + return dialogSet.createContext(turnContext) + .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog)) + .thenApply(result -> null); + } + + private static CompletableFuture<DialogTurnResult> continueOrStart( + DialogContext dialogContext, Dialog dialog + ) { + return dialogContext.continueDialog() + .thenCompose(result -> { + if (result.getStatus() == DialogTurnStatus.EMPTY) { + return dialogContext.beginDialog(dialog.getId(), null); + } + + return CompletableFuture.completedFuture(result); + }); + } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 3a0a51306..7416e814b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -200,7 +200,7 @@ public CompletableFuture<DialogTurnResult> beginDialog(String dialogId, Object o // Look up dialog Dialog dialog = findDialog(dialogId); if (dialog == null) { - Async.completeExceptionally(new Exception(String.format( + return Async.completeExceptionally(new Exception(String.format( "DialogContext.beginDialog(): A dialog with an id of '%s' wasn't found." + " The dialog must be included in the current or parent DialogSet." + " For example, if subclassing a ComponentDialog you can call AddDialog()" diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java index aedde17ba..1ac77aef5 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java @@ -4,7 +4,6 @@ package com.microsoft.bot.dialogs; import java.util.ArrayList; -import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -38,9 +37,9 @@ public class WaterfallDialog extends Dialog { * @param dialogId The dialog ID. * @param actions Optional actions to be defined by the caller. */ - public WaterfallDialog(String dialogId, Collection<WaterfallStep> actions) { + public WaterfallDialog(String dialogId, List<WaterfallStep> actions) { super(dialogId); - steps = actions != null ? new ArrayList<WaterfallStep>(actions) : new ArrayList<WaterfallStep>(); + steps = actions != null ? actions : new ArrayList<WaterfallStep>(); } /** @@ -126,7 +125,7 @@ public CompletableFuture<DialogTurnResult> continueDialog(DialogContext dc) { } // Don't do anything for non-message activities. - if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { return CompletableFuture.completedFuture(END_OF_TURN); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java index 7e484021e..1971e3d96 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceFactory.java @@ -11,6 +11,7 @@ import com.microsoft.bot.schema.HeroCard; import com.microsoft.bot.schema.InputHints; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; @@ -376,6 +377,16 @@ public static List<Choice> toChoices(List<String> choices) { return choices == null ? new ArrayList<>() : choices.stream().map(Choice::new).collect(Collectors.toList()); } + /** + * Returns a list of strings as a list of Choices. + * + * @param choices The strings to convert. + * @return A List of Choices. + */ + public static List<Choice> toChoices(String... choices) { + return toChoices(Arrays.asList(choices)); + } + private static List<CardAction> extractActions(List<Choice> choices) { if (choices == null) { choices = new ArrayList<>(); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java index 13fc79dbb..d7903050f 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java @@ -105,7 +105,7 @@ protected CompletableFuture<PromptRecognizerResult<List<Attachment>>> onRecogniz } PromptRecognizerResult<List<Attachment>> result = new PromptRecognizerResult<List<Attachment>>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { Activity message = turnContext.getActivity(); if (message.getAttachments() != null && message.getAttachments().size() > 0) { result.setSucceeded(true); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java index 533ce5f58..9b8bb126f 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java @@ -39,6 +39,15 @@ public class ChoicePrompt extends Prompt<FoundChoice> { private FindChoicesOptions recognizerOptions; private ChoiceFactoryOptions choiceOptions; + /** + * Initializes a new instance of the {@link ChoicePrompt} class. + * + * @param dialogId The ID to assign to this prompt. + */ + public ChoicePrompt(String dialogId) { + this(dialogId, null, null); + } + /** * Initializes a new instance of the {@link ChoicePrompt} class. * @@ -267,7 +276,7 @@ protected CompletableFuture<PromptRecognizerResult<FoundChoice>> onRecognize(Tur List<Choice> choices = options.getChoices() != null ? options.getChoices() : new ArrayList<Choice>(); PromptRecognizerResult<FoundChoice> result = new PromptRecognizerResult<FoundChoice>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { Activity activity = turnContext.getActivity(); String utterance = activity.getText(); if (StringUtils.isEmpty(utterance)) { diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java index 2a317d17a..bb7afe798 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -283,7 +283,7 @@ protected CompletableFuture<PromptRecognizerResult<Boolean>> onRecognize(TurnCon } PromptRecognizerResult<Boolean> result = new PromptRecognizerResult<Boolean>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { // Recognize utterance String utterance = turnContext.getActivity().getText(); if (StringUtils.isBlank(utterance)) { diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java index 9e971c486..83cd57306 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java @@ -136,7 +136,7 @@ protected CompletableFuture<Void> onPrompt(TurnContext turnContext, Map<String, PromptRecognizerResult<List<DateTimeResolution>> result = new PromptRecognizerResult<List<DateTimeResolution>>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { String utterance = turnContext.getActivity().getText(); if (StringUtils.isEmpty(utterance)) { return CompletableFuture.completedFuture(result); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java index 2446328f7..d95dd2be4 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java @@ -7,8 +7,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; -import javax.activation.UnsupportedDataTypeException; - import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.ActivityTypes; @@ -37,13 +35,29 @@ public class NumberPrompt<T> extends Prompt<T> { * {@link DialogSet} or {@link ComponentDialog} . * @param classOfNumber Type of <T> used to determine within the class what type was created for. This is required * due to type erasure in Java not allowing checking the type of <T> during runtime. - * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for <T>. + * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for <T>. */ public NumberPrompt(String dialogId, Class<T> classOfNumber) - throws UnsupportedDataTypeException { + throws IllegalArgumentException { this(dialogId, null, null, classOfNumber); } + /** + * Initializes a new instance of the {@link NumberPrompt{T}} class. + * + * @param dialogId Unique ID of the dialog within its parent + * {@link DialogSet} or {@link ComponentDialog} . + * @param validator Validator that will be called each time the user + * responds to the prompt. + * @param classOfNumber Type of <T> used to determine within the class what type was created for. This is required + * due to type erasure in Java not allowing checking the type of <T> during runtime. + * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for <T>. + */ + public NumberPrompt(String dialogId, PromptValidator<T> validator, Class<T> classOfNumber) + throws IllegalArgumentException { + this(dialogId, validator, null, classOfNumber); + } + /** * Initializes a new instance of the {@link NumberPrompt{T}} class. * @@ -54,18 +68,17 @@ public NumberPrompt(String dialogId, Class<T> classOfNumber) * @param defaultLocale Locale to use. * @param classOfNumber Type of <T> used to determine within the class what type was created for. This is required * due to type erasure in Java not allowing checking the type of <T> during runtime. - * @throws UnsupportedDataTypeException thrown if a type other than int, long, float, or double are used for <T>. + * @throws IllegalArgumentException thrown if a type other than int, long, float, or double are used for <T>. */ public NumberPrompt(String dialogId, PromptValidator<T> validator, String defaultLocale, Class<T> classOfNumber) - throws UnsupportedDataTypeException { - + throws IllegalArgumentException { super(dialogId, validator); this.defaultLocale = defaultLocale; this.classOfNumber = classOfNumber; if (!(classOfNumber.getSimpleName().equals("Long") || classOfNumber.getSimpleName().equals("Integer") || classOfNumber.getSimpleName().equals("Float") || classOfNumber.getSimpleName().equals("Double"))) { - throw new UnsupportedDataTypeException(String.format("NumberPrompt: Type argument %s <T> is not supported", + throw new IllegalArgumentException(String.format("NumberPrompt: Type argument %s <T> is not supported", classOfNumber.getSimpleName())); } } @@ -160,7 +173,7 @@ protected CompletableFuture<PromptRecognizerResult<T>> onRecognize(TurnContext t } PromptRecognizerResult<T> result = new PromptRecognizerResult<T>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { String utterance = turnContext.getActivity().getText(); if (StringUtils.isEmpty(utterance)) { return CompletableFuture.completedFuture(result); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java index b4130d560..aced3a4ab 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -151,7 +151,7 @@ public CompletableFuture<DialogTurnResult> continueDialog(DialogContext dc) { } // Don't do anything for non-message activities - if (dc.getContext().getActivity().getType() != ActivityTypes.MESSAGE) { + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } @@ -248,7 +248,7 @@ public CompletableFuture<Void> repromptDialog(TurnContext turnContext, DialogIns @Override protected CompletableFuture<Boolean> onPreBubbleEvent(DialogContext dc, DialogEvent e) { if (e.getName() == DialogEvents.ACTIVITY_RECEIVED - && dc.getContext().getActivity().getType() == ActivityTypes.MESSAGE) { + && dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { // Perform base recognition Map<String, Object> state = dc.getActiveDialog().getState(); PromptRecognizerResult<T> recognized = onRecognize(dc.getContext(), diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java index a26c9b2e5..20869b2a7 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java @@ -104,7 +104,7 @@ protected CompletableFuture<PromptRecognizerResult<String>> onRecognize(TurnCont } PromptRecognizerResult<String> result = new PromptRecognizerResult<String>(); - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { Activity message = turnContext.getActivity(); if (message.getText() != null) { result.setSucceeded(true); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java index 2b125f4f3..2a0bb2a98 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java @@ -114,8 +114,8 @@ public void BasicWaterfallTest() throws UnsupportedDataTypeException { dialogs.add(createWaterfall()); try { dialogs.add(new NumberPrompt<Integer>("number", Integer.class)); - } catch (UnsupportedDataTypeException e) { - e.printStackTrace(); + } catch (Throwable t) { + t.printStackTrace(); } DialogContext dc = dialogs.createContext(turnContext).join(); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java index 188f1a2cb..c9944e5fc 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java @@ -220,9 +220,9 @@ public void WaterfallPrompt() throws UnsupportedDataTypeException{ try { numberPrompt = new NumberPrompt<Integer>("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class); - } catch (UnsupportedDataTypeException e) { + } catch (Throwable t) { // TODO Auto-generated catch block - e.printStackTrace(); + t.printStackTrace(); } dialogs.add(numberPrompt); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java index 3ea7723a0..a8939f2a5 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java @@ -45,7 +45,7 @@ public void NumberPromptWithNullIdShouldFail() { @Test public void NumberPromptWithUnsupportedTypeShouldFail() { - Assert.assertThrows(UnsupportedDataTypeException.class, () -> new NumberPrompt<Short>("prompt", Short.class)); + Assert.assertThrows(IllegalArgumentException.class, () -> new NumberPrompt<Short>("prompt", Short.class)); } @Test diff --git a/samples/05.multi-turn-prompt/LICENSE b/samples/05.multi-turn-prompt/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/05.multi-turn-prompt/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/05.multi-turn-prompt/README.md b/samples/05.multi-turn-prompt/README.md new file mode 100644 index 000000000..e4375c425 --- /dev/null +++ b/samples/05.multi-turn-prompt/README.md @@ -0,0 +1,90 @@ +# Multi-turn prompt + +Bot Framework v4 multi-turn prompt bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use the prompts classes included in `botbuilder-dialogs`. This bot will ask for the user's name and age, then store the responses. It demonstrates a multi-turn dialog flow using a text prompt, a number prompt, and state accessors to store and retrieve values. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-multiturnprompt-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription "<azure-subscription>"` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "<botname>" --password "<appsecret>" --available-to-other-tenants` + +Replace `<botname>` and `<appsecret>` with your own values. + +`<botname>` is the unique name of your bot. +`<appsecret>` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for `<appid>`, `<appsecret>`, `<botname>`, and `<groupname>` in the following commands: + +#### To a new Resource Group +`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="<groupname>" botId="<botname>" appId="<appid>" appSecret="<appsecret>"` + +#### To an existing Resource Group +`az group deployment create --name "stateBotDeploy" --resource-group "<groupname>" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="<botname>" appId="<appid>" appSecret="<appsecret>"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="<groupname>" -Dbotname="<botname>"` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..ead339093 --- /dev/null +++ b/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..b6f5114fc --- /dev/null +++ b/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..dcd6260a5 --- /dev/null +++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..b790d2bdc --- /dev/null +++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/pom.xml b/samples/05.multi-turn-prompt/pom.xml new file mode 100644 index 000000000..7dfbebe83 --- /dev/null +++ b/samples/05.multi-turn-prompt/pom.xml @@ -0,0 +1,244 @@ +<?xml version="1.0" encoding="UTF-8" ?> + +<project xmlns="http://maven.apache.org/POM/4.0.0" + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> + <modelVersion>4.0.0</modelVersion> + + <groupId>com.microsoft.bot.sample</groupId> + <artifactId>bot-multiturnprompt</artifactId> + <version>sample</version> + <packaging>jar</packaging> + + <name>${project.groupId}:${project.artifactId}</name> + <description>This package contains the Multi-turn Prompt sample using Spring Boot.</description> + <url>http://maven.apache.org</url> + + <parent> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-parent</artifactId> + <version>2.4.0</version> + <relativePath/> + </parent> + + <licenses> + <license> + <name>MIT License</name> + <url>http://www.opensource.org/licenses/mit-license.php</url> + </license> + </licenses> + + <developers> + <developer> + <name>Bot Framework Development</name> + <email></email> + <organization>Microsoft</organization> + <organizationUrl>https://dev.botframework.com/</organizationUrl> + </developer> + </developers> + + <properties> + <java.version>1.8</java.version> + <maven.compiler.target>1.8</maven.compiler.target> + <maven.compiler.source>1.8</maven.compiler.source> + <start-class>com.microsoft.bot.sample.multiturnprompt.Application</start-class> + </properties> + + <dependencies> + <dependency> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-starter-test</artifactId> + <version>2.4.0</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <version>4.13.1</version> + <scope>test</scope> + </dependency> + <dependency> + <groupId>org.junit.vintage</groupId> + <artifactId>junit-vintage-engine</artifactId> + <scope>test</scope> + </dependency> + + <dependency> + <groupId>org.slf4j</groupId> + <artifactId>slf4j-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-api</artifactId> + <version>2.11.0</version> + </dependency> + <dependency> + <groupId>org.apache.logging.log4j</groupId> + <artifactId>log4j-core</artifactId> + <version>2.13.2</version> + </dependency> + + <dependency> + <groupId>com.microsoft.bot</groupId> + <artifactId>bot-integration-spring</artifactId> + <version>4.6.0-preview8</version> + <scope>compile</scope> + </dependency> + <dependency> + <groupId>com.microsoft.bot</groupId> + <artifactId>bot-dialogs</artifactId> + <version>4.6.0-preview8</version> + <scope>compile</scope> + </dependency> + </dependencies> + + <profiles> + <profile> + <id>build</id> + <activation> + <activeByDefault>true</activeByDefault> + </activation> + <build> + <resources> + <resource> + <directory>src/main/resources</directory> + <filtering>false</filtering> + </resource> + </resources> + <plugins> + <plugin> + <artifactId>maven-compiler-plugin</artifactId> + <version>3.8.1</version> + </plugin> + <plugin> + <artifactId>maven-war-plugin</artifactId> + <version>3.2.3</version> + <configuration> + <warSourceDirectory>src/main/webapp</warSourceDirectory> + </configuration> + </plugin> + <plugin> + <groupId>org.springframework.boot</groupId> + <artifactId>spring-boot-maven-plugin</artifactId> + <executions> + <execution> + <goals> + <goal>repackage</goal> + </goals> + <configuration> + <mainClass>com.microsoft.bot.sample.multiturnprompt.Application</mainClass> + </configuration> + </execution> + </executions> + </plugin> + <plugin> + <groupId>com.microsoft.azure</groupId> + <artifactId>azure-webapp-maven-plugin</artifactId> + <version>1.7.0</version> + <configuration> + <schemaVersion>V2</schemaVersion> + <resourceGroup>${groupname}</resourceGroup> + <appName>${botname}</appName> + <appSettings> + <property> + <name>JAVA_OPTS</name> + <value>-Dserver.port=80</value> + </property> + </appSettings> + <runtime> + <os>linux</os> + <javaVersion>jre8</javaVersion> + <webContainer>jre8</webContainer> + </runtime> + <deployment> + <resources> + <resource> + <directory>${project.basedir}/target</directory> + <includes> + <include>*.jar</include> + </includes> + </resource> + </resources> + </deployment> + </configuration> + </plugin> + </plugins> + </build> + </profile> + + <profile> + <id>publish</id> + <build> + <plugins> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-compiler-plugin</artifactId> + </plugin> + <plugin> + <artifactId>maven-war-plugin</artifactId> + <version>3.2.3</version> + <configuration> + <warSourceDirectory>src/main/webapp</warSourceDirectory> + </configuration> + </plugin> + + <plugin> + <groupId>org.sonatype.plugins</groupId> + <artifactId>nexus-staging-maven-plugin</artifactId> + <version>1.6.8</version> + <extensions>true</extensions> + <configuration> + <skipRemoteStaging>true</skipRemoteStaging> + <serverId>ossrh</serverId> + <nexusUrl>https://oss.sonatype.org/</nexusUrl> + <autoReleaseAfterClose>true</autoReleaseAfterClose> + </configuration> + </plugin> + + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-gpg-plugin</artifactId> + <executions> + <execution> + <id>sign-artifacts</id> + <phase>verify</phase> + <goals> + <goal>sign</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-source-plugin</artifactId> + <executions> + <execution> + <id>attach-sources</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <source>8</source> + <failOnError>false</failOnError> + </configuration> + <executions> + <execution> + <id>attach-javadocs</id> + <goals> + <goal>jar</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + </profile> + </profiles> +</project> diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java new file mode 100644 index 000000000..1324a2348 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multiturnprompt; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see DialogBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java new file mode 100644 index 000000000..d71d53ed6 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multiturnprompt; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; +import org.springframework.stereotype.Component; + +/** + * This IBot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +@Component +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + Dialog withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture<Void> onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture<Void> onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java new file mode 100644 index 000000000..a04f2f72b --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java @@ -0,0 +1,13 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multiturnprompt; + +import com.microsoft.bot.schema.Attachment; + +public class UserProfile { + public String transport; + public String name; + public Integer age; + public Attachment picture; +} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java new file mode 100644 index 000000000..302fe5d3a --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java @@ -0,0 +1,224 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multiturnprompt; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.prompts.AttachmentPrompt; +import com.microsoft.bot.dialogs.prompts.ChoicePrompt; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Attachment; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +@Component +public class UserProfileDialog extends ComponentDialog { + private StatePropertyAccessor<UserProfile> userProfileAccessor; + + public UserProfileDialog(UserState withUserState) { + super("UserProfileDialog"); + + userProfileAccessor = withUserState.createProperty("UserProfile"); + + WaterfallStep[] waterfallSteps = { + UserProfileDialog::transportStep, + UserProfileDialog::nameStep, + UserProfileDialog::nameConfirmStep, + UserProfileDialog::ageStep, + UserProfileDialog::pictureStep, + UserProfileDialog::confirmStep, + this::summaryStep + }; + + // Add named dialogs to the DialogSet. These names are saved in the dialog state. + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + addDialog(new TextPrompt("TextPrompt")); + addDialog(new NumberPrompt<Integer>("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class)); + addDialog(new ChoicePrompt("ChoicePrompt")); + addDialog(new ConfirmPrompt("ConfirmPrompt")); + addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator)); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private static CompletableFuture<DialogTurnResult> transportStep(WaterfallStepContext stepContext) { + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. + // Running a prompt here means the next WaterfallStep will be run when the user's response is received. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please enter your mode of transport.")); + promptOptions.setChoices(ChoiceFactory.toChoices("Car", "Bus", "Bicycle")); + + return stepContext.prompt("ChoicePrompt", promptOptions); + } + + private static CompletableFuture<DialogTurnResult> nameStep(WaterfallStepContext stepContext) { + stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue()); + + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please enter your name.")); + return stepContext.prompt("TextPrompt", promptOptions); + } + + private static CompletableFuture<DialogTurnResult> nameConfirmStep(WaterfallStepContext stepContext) { + stepContext.getValues().put("name", stepContext.getResult()); + + // We can send messages to the user at any point in the WaterfallStep. + return stepContext.getContext().sendActivity(MessageFactory.text(String.format("Thanks %s", stepContext.getResult()))) + .thenCompose(resourceResponse -> { + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Would you like to give your age?")); + return stepContext.prompt("ConfirmPrompt", promptOptions); + }); + } + + private static CompletableFuture<DialogTurnResult> ageStep(WaterfallStepContext stepContext) { + if ((Boolean)stepContext.getResult()) { + // User said "yes" so we will be prompting for the age. + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please enter your age.")); + promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150.")); + + return stepContext.prompt("NumberPrompt", promptOptions); + } + + // User said "no" so we will skip the next step. Give -1 as the age. + return stepContext.next(-1); + } + + private static CompletableFuture<DialogTurnResult> pictureStep(WaterfallStepContext stepContext) { + stepContext.getValues().put("age", (Integer) stepContext.getResult()); + + String msg = (Integer)stepContext.getValues().get("age") == -1 + ? "No age given." + : String.format("I have your age as %d.", (Integer)stepContext.getValues().get("age")); + + // We can send messages to the user at any point in the WaterfallStep. + return stepContext.getContext().sendActivity(MessageFactory.text(msg)) + .thenCompose(resourceResponse -> { + if (StringUtils.equals(stepContext.getContext().getActivity().getChannelId(), Channels.MSTEAMS)) { + // This attachment prompt example is not designed to work for Teams attachments, so skip it in this case + return stepContext.getContext().sendActivity(MessageFactory.text("Skipping attachment prompt in Teams channel...")) + .thenCompose(resourceResponse1 -> stepContext.next(null)); + } + + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please attach a profile picture (or type any message to skip).")); + promptOptions.setRetryPrompt(MessageFactory.text("The attachment must be a jpeg/png image file.")); + + return stepContext.prompt("AttachmentPrompt", promptOptions); + }); + } + + private static CompletableFuture<DialogTurnResult> confirmStep(WaterfallStepContext stepContext) { + List<Attachment> attachments = (List<Attachment>)stepContext.getResult(); + stepContext.getValues().put("picture", attachments == null ? null : attachments.get(0)); + + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Is this ok?")); + return stepContext.prompt("ConfirmPrompt", promptOptions); + } + + private CompletableFuture<DialogTurnResult> summaryStep(WaterfallStepContext stepContext) { + if ((Boolean)stepContext.getResult()) { + // Get the current profile object from user state. + return userProfileAccessor.get(stepContext.getContext(), () -> new UserProfile()) + .thenCompose(userProfile -> { + userProfile.transport = (String) stepContext.getValues().get("transport"); + userProfile.name = (String) stepContext.getValues().get("name"); + userProfile.age = (Integer) stepContext.getValues().get("age"); + userProfile.picture = (Attachment) stepContext.getValues().get("picture"); + + String msg = String.format( + "I have your mode of transport as %s and your name as %s", + userProfile.transport, userProfile.name + ); + + if (userProfile.age != -1) { + msg += String.format(" and your age as %s", userProfile.age); + } + + msg += "."; + + return stepContext.getContext().sendActivity(MessageFactory.text(msg)) + .thenApply(resourceResponse -> userProfile); + }) + .thenCompose(userProfile -> { + if (userProfile.picture != null) { + return stepContext.getContext().sendActivity( + MessageFactory.attachment(userProfile.picture, + "This is your profile picture." + )); + } + + return stepContext.getContext().sendActivity( + MessageFactory.text("A profile picture wasn't attached.") + ); + }) + .thenCompose(resourceResponse -> stepContext.endDialog()); + } + + // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end. + return stepContext.getContext().sendActivity(MessageFactory.text("Thanks. Your profile will not be kept.")) + .thenCompose(resourceResponse -> stepContext.endDialog()); + } + + private static CompletableFuture<Boolean> picturePromptValidator( + PromptValidatorContext<List<Attachment>> promptContext + ) { + if (promptContext.getRecognized().getSucceeded()) { + List<Attachment> attachments = promptContext.getRecognized().getValue(); + List<Attachment> validImages = new ArrayList<>(); + + for (Attachment attachment : attachments) { + if (StringUtils.equals( + attachment.getContentType(), "image/jpeg") || StringUtils.equals(attachment.getContentType(), "image/png") + ) { + validImages.add(attachment); + } + } + + promptContext.getRecognized().setValue(validImages); + + // If none of the attachments are valid images, the retry prompt should be sent. + return CompletableFuture.completedFuture(!validImages.isEmpty()); + } + else { + // We can return true from a validator function even if Recognized.Succeeded is false. + return promptContext.getContext().sendActivity("No attachments received. Proceeding without a profile picture...") + .thenApply(resourceResponse -> true); + } + } + + private static CompletableFuture<Boolean> agePromptValidator( + PromptValidatorContext<Integer> promptContext + ) { + // This condition is our validation rule. You can also change the value at this point. + return CompletableFuture.completedFuture( + promptContext.getRecognized().getSucceeded() + && promptContext.getRecognized().getValue() > 0 + && promptContext.getRecognized().getValue() < 150); + } +} diff --git a/samples/05.multi-turn-prompt/src/main/resources/application.properties b/samples/05.multi-turn-prompt/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/05.multi-turn-prompt/src/main/resources/log4j2.json b/samples/05.multi-turn-prompt/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF b/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml b/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ +<?xml version="1.0" encoding="UTF-8"?> +<servlet> + <servlet-name>dispatcher</servlet-name> + <servlet-class> + org.springframework.web.servlet.DispatcherServlet + </servlet-class> + <init-param> + <param-name>contextConfigLocation</param-name> + <param-value>/WEB-INF/spring/dispatcher-config.xml</param-value> + </init-param> + <load-on-startup>1</load-on-startup> +</servlet> \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/src/main/webapp/index.html b/samples/05.multi-turn-prompt/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/05.multi-turn-prompt/src/main/webapp/index.html @@ -0,0 +1,418 @@ +<!DOCTYPE html> +<html lang="en"> + +<head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <title>EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java b/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java new file mode 100644 index 000000000..ced035bd3 --- /dev/null +++ b/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.multiturnprompt; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java index b36653cdb..d8455fcad 100644 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java @@ -55,7 +55,7 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next return this.shouldTranslate(turnContext).thenCompose(translate -> { if (translate) { - if (turnContext.getActivity().getType() == ActivityTypes.MESSAGE) { + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { return this.translator.translate( turnContext.getActivity().getText(), TranslationSettings.DEFAULT_LANGUAGE) From 1d4f529150ce3b8a5f3e9b3d1ef88c1bd7b71663 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Feb 2021 13:54:13 -0600 Subject: [PATCH 050/221] Updated MSAL to 1.8.1 (#931) * Updated to MSAL 1.8.1 * Moved MSAL dependency to bot-connector --- libraries/bot-connector/pom.xml | 2 ++ pom.xml | 6 ------ 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index 17af92eb6..70b3c3eb4 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -95,7 +95,9 @@ com.microsoft.azure msal4j + 1.8.1 + com.fasterxml.jackson.module jackson-module-parameter-names diff --git a/pom.xml b/pom.xml index 303a30fa7..39ec82eda 100644 --- a/pom.xml +++ b/pom.xml @@ -240,12 +240,6 @@ test - - com.microsoft.azure - msal4j - 1.8.0 - - com.fasterxml.jackson.module jackson-module-parameter-names From d5c2811cc5d14b534ba2d3dff4018517ef3a2536 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 2 Feb 2021 09:27:32 -0600 Subject: [PATCH 051/221] BotAdapter continueConversation, with Activity (#934) --- .../com/microsoft/bot/builder/BotAdapter.java | 82 +++++++++++++++++-- 1 file changed, 76 insertions(+), 6 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index c9d2f64f7..ecd5690a2 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -269,9 +269,7 @@ public CompletableFuture continueConversation( ConversationReference reference, BotCallbackHandler callback ) { - CompletableFuture result = new CompletableFuture<>(); - result.completeExceptionally(new NotImplementedException("continueConversation")); - return result; + return Async.completeExceptionally(new NotImplementedException("continueConversation")); } /** @@ -296,8 +294,80 @@ public CompletableFuture continueConversation( String audience, BotCallbackHandler callback ) { - CompletableFuture result = new CompletableFuture<>(); - result.completeExceptionally(new NotImplementedException("continueConversation")); - return result; + return Async.completeExceptionally(new NotImplementedException("continueConversation")); + } + + /** + * Sends a proactive message to a conversation. + * + *

+ * Call this method to proactively send a message to a conversation. Most + * channels require a user to initiate a conversation with a bot before the bot + * can send activities to the user. + *

+ * + * @param botId The application ID of the bot. This parameter is ignored in single tenant + * the Adapters (Console, Test, etc) but is critical to the BotFrameworkAdapter + * which is multi-tenant aware. + * @param continuationActivity An Activity with the appropriate ConversationReference with + * which to continue the conversation. + * @param callback The method to call for the result bot turn. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture continueConversation( + String botId, + Activity continuationActivity, + BotCallbackHandler callback + ) { + return Async.completeExceptionally(new NotImplementedException("continueConversation")); + } + + /** + * Sends a proactive message to a conversation. + * + *

+ * Call this method to proactively send a message to a conversation. Most + * channels require a user to initiate a conversation with a bot before the bot + * can send activities to the user. + *

+ * + * @param claimsIdentity A ClaimsIdentity for the conversation. + * @param continuationActivity An Activity with the appropriate ConversationReference with + * which to continue the conversation. + * @param callback The method to call for the result bot turn. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture continueConversation( + ClaimsIdentity claimsIdentity, + Activity continuationActivity, + BotCallbackHandler callback + ) { + return Async.completeExceptionally(new NotImplementedException("continueConversation")); + } + + /** + * Sends a proactive message to a conversation. + * + *

+ * Call this method to proactively send a message to a conversation. Most + * channels require a user to initiate a conversation with a bot before the bot + * can send activities to the user. + *

+ * + * @param claimsIdentity A ClaimsIdentity for the conversation. + * @param continuationActivity An Activity with the appropriate ConversationReference with + * which to continue the conversation. + * @param audience A value signifying the recipient of the proactive + * message. + * @param callback The method to call for the result bot turn. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture continueConversation( + ClaimsIdentity claimsIdentity, + Activity continuationActivity, + String audience, + BotCallbackHandler callback + ) { + return Async.completeExceptionally(new NotImplementedException("continueConversation")); } } From 47e3e5c491e41625ee2ffedc7f7b0837886a342e Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 2 Feb 2021 10:14:55 -0600 Subject: [PATCH 052/221] Can provide custom oAuthScope to MicrosoftGovernmentAppCredentials (#935) --- .../bot/builder/BotFrameworkAdapter.java | 2 +- .../authentication/AppCredentials.java | 16 +++++++- .../MicrosoftAppCredentials.java | 19 ++++++++++ .../MicrosoftGovernmentAppCredentials.java | 37 +++++++++++++------ 4 files changed, 61 insertions(+), 13 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index dc07f0ad9..9cae8c24a 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -1482,7 +1482,7 @@ private CompletableFuture getAppCredentials(String appId, String return credentialProvider.getAppPassword(appId).thenApply(appPassword -> { AppCredentials credentials = channelProvider != null && channelProvider.isGovernment() - ? new MicrosoftGovernmentAppCredentials(appId, appPassword) + ? new MicrosoftGovernmentAppCredentials(appId, appPassword, scope) : new MicrosoftAppCredentials(appId, appPassword); appCredentialMap.put(cacheKey, credentials); return credentials; diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java index cf638239c..000f3db41 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java @@ -37,6 +37,7 @@ public abstract class AppCredentials implements ServiceClientCredentials { private String appId; private String authTenant; + private String authScope; private Authenticator authenticator; /** @@ -45,7 +46,20 @@ public abstract class AppCredentials implements ServiceClientCredentials { * @param withChannelAuthTenant Optional. The oauth token tenant. */ public AppCredentials(String withChannelAuthTenant) { + this(withChannelAuthTenant, AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE); + } + + /** + * Initializes a new instance of the AppCredentials class. + * + * @param withChannelAuthTenant Optional. The oauth token tenant. + * @param withOAuthScope The scope for the token. + */ + public AppCredentials(String withChannelAuthTenant, String withOAuthScope) { setChannelAuthTenant(withChannelAuthTenant); + authScope = StringUtils.isEmpty(withOAuthScope) + ? AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + : withOAuthScope; } /** @@ -179,7 +193,7 @@ AuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL_TEMPLATE, getChannelAuthTe * @return OAuth scope. */ public String oAuthScope() { - return AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + return authScope; } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java index c7f39b50f..801b9c04e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftAppCredentials.java @@ -57,6 +57,25 @@ public MicrosoftAppCredentials( setAppPassword(withAppPassword); } + /** + * Initializes a new instance of the MicrosoftAppCredentials class. + * + * @param withAppId The Microsoft app ID. + * @param withAppPassword The Microsoft app password. + * @param withChannelAuthTenant Optional. The oauth token tenant. + * @param withOAuthScope The scope for the token. + */ + public MicrosoftAppCredentials( + String withAppId, + String withAppPassword, + String withChannelAuthTenant, + String withOAuthScope + ) { + super(withChannelAuthTenant, withOAuthScope); + setAppId(withAppId); + setAppPassword(withAppPassword); + } + /** * Gets the app password for this credential. * diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java index 7fcbd695a..619689313 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java @@ -3,6 +3,8 @@ package com.microsoft.bot.connector.authentication; +import org.apache.commons.lang3.StringUtils; + /** * MicrosoftGovernmentAppCredentials auth implementation. */ @@ -14,7 +16,30 @@ public class MicrosoftGovernmentAppCredentials extends MicrosoftAppCredentials { * @param password The Microsoft app password. */ public MicrosoftGovernmentAppCredentials(String appId, String password) { - super(appId, password); + super( + appId, + password, + null, + GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + ); + } + + /** + * Initializes a new instance of the MicrosoftGovernmentAppCredentials class. + * + * @param appId The Microsoft app ID. + * @param password The Microsoft app password. + * @param oAuthScope The scope for the token. + */ + public MicrosoftGovernmentAppCredentials(String appId, String password, String oAuthScope) { + super( + appId, + password, + null, + StringUtils.isEmpty(oAuthScope) + ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + : oAuthScope + ); } /** @@ -35,14 +60,4 @@ public static MicrosoftGovernmentAppCredentials empty() { public String oAuthEndpoint() { return GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_LOGIN_URL; } - - /** - * Gets the Gov OAuth scope to use. - * - * @return The OAuth scope to use. - */ - @Override - public String oAuthScope() { - return GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; - } } From 6eeeb16804a34510548a6bdcdaab9fc7c1e64992 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 2 Feb 2021 10:34:45 -0600 Subject: [PATCH 053/221] Sample 06.using-cards (#933) * Sample 06.using-cards * Corrected package name in 06.using-cards Co-authored-by: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> --- .../microsoft/bot/dialogs/choices/Choice.java | 21 + .../microsoft/bot/schema/AnimationCard.java | 19 + .../com/microsoft/bot/schema/AudioCard.java | 19 + .../com/microsoft/bot/schema/CardAction.java | 34 +- .../java/com/microsoft/bot/schema/Fact.java | 16 + .../com/microsoft/bot/schema/HeroCard.java | 19 + .../com/microsoft/bot/schema/MediaUrl.java | 16 + .../com/microsoft/bot/schema/OAuthCard.java | 10 + .../com/microsoft/bot/schema/ReceiptCard.java | 28 ++ .../com/microsoft/bot/schema/SigninCard.java | 10 + .../microsoft/bot/schema/ThumbnailCard.java | 19 + .../microsoft/bot/schema/ThumbnailUrl.java | 14 + .../com/microsoft/bot/schema/VideoCard.java | 20 + pom.xml | 2 +- samples/06.using-cards/LICENSE | 21 + samples/06.using-cards/README.md | 102 +++++ .../new-rg-parameters.json | 42 ++ .../preexisting-rg-parameters.json | 39 ++ .../template-with-new-rg.json | 191 ++++++++ .../template-with-preexisting-rg.json | 158 +++++++ samples/06.using-cards/pom.xml | 244 ++++++++++ .../bot/sample/usingcards/Application.java | 48 ++ .../bot/sample/usingcards/Cards.java | 158 +++++++ .../bot/sample/usingcards/DialogBot.java | 53 +++ .../bot/sample/usingcards/MainDialog.java | 137 ++++++ .../bot/sample/usingcards/RichCardsBot.java | 48 ++ .../src/main/resources/adaptiveCard.json | 204 +++++++++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../06.using-cards/src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/usingcards/ApplicationTest.java | 19 + 33 files changed, 2159 insertions(+), 6 deletions(-) create mode 100644 samples/06.using-cards/LICENSE create mode 100644 samples/06.using-cards/README.md create mode 100644 samples/06.using-cards/deploymentTemplates/new-rg-parameters.json create mode 100644 samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 samples/06.using-cards/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/06.using-cards/pom.xml create mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java create mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java create mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java create mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java create mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java create mode 100644 samples/06.using-cards/src/main/resources/adaptiveCard.json create mode 100644 samples/06.using-cards/src/main/resources/application.properties create mode 100644 samples/06.using-cards/src/main/resources/log4j2.json create mode 100644 samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/06.using-cards/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/06.using-cards/src/main/webapp/index.html create mode 100644 samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java index 66321427d..153cebb20 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Choice.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.microsoft.bot.schema.CardAction; +import java.util.Arrays; import java.util.List; /** @@ -35,6 +36,26 @@ public Choice(String withValue) { value = withValue; } + /** + * Creates a Choice. + * @param withValue The value. + * @param withSynonyms The list of synonyms to recognize in addition to the value. + */ + public Choice(String withValue, List withSynonyms) { + value = withValue; + synonyms = withSynonyms; + } + + /** + * Creates a Choice. + * @param withValue The value. + * @param withSynonyms The list of synonyms to recognize in addition to the value. + */ + public Choice(String withValue, String... withSynonyms) { + value = withValue; + synonyms = Arrays.asList(withSynonyms); + } + /** * Gets the value to return when selected. * @return The value. diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java index 4eec4d1fc..c5a7538b7 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -193,6 +194,15 @@ public void setMedia(List withMedia) { this.media = withMedia; } + /** + * Set the media value. + * + * @param withMedia the media value to set + */ + public void setMedia(MediaUrl... withMedia) { + this.media = Arrays.asList(withMedia); + } + /** * Get the buttons value. * @@ -211,6 +221,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Get the shareable value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java index a4f966904..753f3ed6c 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -194,6 +195,15 @@ public void setMedia(List withMedia) { this.media = withMedia; } + /** + * Set the media value. + * + * @param withMedia the media value to set + */ + public void setMedia(MediaUrl... withMedia) { + this.media = Arrays.asList(withMedia); + } + /** * Get the buttons value. * @@ -212,6 +222,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Get the shareable value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java index aae76914b..7cea03a9e 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java @@ -102,13 +102,37 @@ public CardAction() { } /** - * Simplify creation of CardActions with string values. + * Creation of CardAction with string values. * - * @param input The value for both Title and Value. + * @param withInput The value for both Title and Value. */ - public CardAction(String input) { - setTitle(input); - setValue(input); + public CardAction(String withInput) { + setTitle(withInput); + setValue(withInput); + } + + /** + * Creation of CardAction with type and title. + * + * @param withType the type value to set. + * @param withTitle the title value to set. + */ + public CardAction(ActionTypes withType, String withTitle) { + setType(withType); + setTitle(withTitle); + } + + /** + * Creation of CardAction with type and title. + * + * @param withType the type value to set. + * @param withTitle the title value to set. + * @param withValue The value for both Title and Value. + */ + public CardAction(ActionTypes withType, String withTitle, String withValue) { + setType(withType); + setTitle(withTitle); + setValue(withValue); } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Fact.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Fact.java index ba8cbf0a9..83f1267e1 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Fact.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Fact.java @@ -27,6 +27,22 @@ public class Fact { @JsonInclude(JsonInclude.Include.NON_EMPTY) private String value; + /** + * Creates a new fact. + */ + public Fact() { + } + + /** + * Creates a new fact. + * @param withKey The key value. + * @param withValue The value + */ + public Fact(String withKey, String withValue) { + key = withKey; + value = withValue; + } + /** * Get the key value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java index 281a3f2cc..0242c659a 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -130,6 +131,15 @@ public void setImages(List withImages) { this.images = withImages; } + /** + * Set the images value. + * + * @param withImages the images value to set + */ + public void setImages(CardImage... withImages) { + this.images = Arrays.asList(withImages); + } + /** * Get the buttons value. * @@ -148,6 +158,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Get the tap value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MediaUrl.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MediaUrl.java index 740d74e1f..c2ae98711 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MediaUrl.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MediaUrl.java @@ -25,6 +25,22 @@ public class MediaUrl { @JsonInclude(JsonInclude.Include.NON_EMPTY) private String profile; + /** + * Creates a new MediaUrl. + */ + public MediaUrl() { + } + + /** + * Creates a new MediaUrl. + * + * @param withUrl The url value. + */ + public MediaUrl(String withUrl) { + url = withUrl; + + } + /** * Get the url value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java index 2c9e9dbe2..1fa140ddc 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -91,6 +92,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Creates an @{link Attachment} for this card. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java index 9e0b370c5..53c8fd226 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -108,6 +109,15 @@ public void setFacts(List withFacts) { this.facts = withFacts; } + /** + * Set the facts value. + * + * @param withFacts the facts value to set + */ + public void setFacts(Fact... withFacts) { + this.facts = Arrays.asList(withFacts); + } + /** * Get the items value. * @@ -126,6 +136,15 @@ public void setItems(List withItems) { this.items = withItems; } + /** + * Set the items value. + * + * @param withItems the items value to set + */ + public void setItems(ReceiptItem... withItems) { + this.items = Arrays.asList(withItems); + } + /** * Get the tap value. * @@ -216,6 +235,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Creates an @{link Attachment} for this card. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java index a9fbddefd..ea27727b2 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -66,6 +67,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Creates an @{link Attachment} for this card. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java index f65aa92c6..55d0e8029 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -113,6 +114,15 @@ public void setImages(List withImages) { this.images = withImages; } + /** + * Set the images value. + * + * @param withImages the images value to set + */ + public void setImages(CardImage... withImages) { + this.images = Arrays.asList(withImages); + } + /** * Sets the images list with a single image. * @param image The image to set as the list. @@ -139,6 +149,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Get the tap value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailUrl.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailUrl.java index c4e7868d8..4dd7f05c3 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailUrl.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailUrl.java @@ -24,6 +24,20 @@ public class ThumbnailUrl { @JsonInclude(JsonInclude.Include.NON_EMPTY) private String alt; + /** + * Creates a new ThumbnailUrl. + */ + public ThumbnailUrl() { + } + + /** + * Creates a new ThumbnailUrl. + * @param withUrl The url value to set. + */ + public ThumbnailUrl(String withUrl) { + url = withUrl; + } + /** * Get the url value. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java index 8819a78f2..b0e758057 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java @@ -7,6 +7,7 @@ import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; import java.util.List; /** @@ -156,6 +157,16 @@ public void setMedia(List withMedia) { this.media = withMedia; } + /** + * Media URLs for this card. When this field contains more than one URL, each + * URL is an alternative format of the same content. + * + * @param withMedia the media value to set + */ + public void setMedia(MediaUrl... withMedia) { + this.media = Arrays.asList(withMedia); + } + /** * Get the buttons value. * @@ -174,6 +185,15 @@ public void setButtons(List withButtons) { this.buttons = withButtons; } + /** + * Set the buttons value. + * + * @param withButtons the buttons value to set + */ + public void setButtons(CardAction... withButtons) { + this.buttons = Arrays.asList(withButtons); + } + /** * Get the shareable value. * diff --git a/pom.xml b/pom.xml index 39ec82eda..5980b9e4f 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ 1.8 1.8 3.1.0 - 3.13.0 + 3.14.0 MyGet https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ diff --git a/samples/06.using-cards/LICENSE b/samples/06.using-cards/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/06.using-cards/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/06.using-cards/README.md b/samples/06.using-cards/README.md new file mode 100644 index 000000000..ddfdce522 --- /dev/null +++ b/samples/06.using-cards/README.md @@ -0,0 +1,102 @@ +# Using Cards + +Bot Framework v4 using cards bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses rich cards to enhance your bot design. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-usingcards-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot + +Most channels support rich content. In this sample we explore the different types of rich cards your bot may use. A key to good bot design is to send interactive media, such as Rich Cards. There are several different types of Rich Cards, which are as follows: + +- Animation Card +- Audio Card +- Hero Card +- Receipt Card +- Sign In Card +- Thumbnail Card +- Video Card + +When [designing the user experience](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-design-user-experience?view=azure-bot-service-4.0#cards) developers should consider adding visual elements such as Rich Cards. + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Rich Cards](https://docs.microsoft.com/azure/bot-service/bot-builder-howto-add-media-attachments?view=azure-bot-service-4.0&tabs=csharp#send-a-hero-card) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json b/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..ead339093 --- /dev/null +++ b/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json b/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..b6f5114fc --- /dev/null +++ b/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..dcd6260a5 --- /dev/null +++ b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..b790d2bdc --- /dev/null +++ b/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/06.using-cards/pom.xml b/samples/06.using-cards/pom.xml new file mode 100644 index 000000000..85ab6bc90 --- /dev/null +++ b/samples/06.using-cards/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-usingcards + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Using Cards sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.usingcards.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.usingcards.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java new file mode 100644 index 000000000..5c08c5a98 --- /dev/null +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see RichCardsBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java new file mode 100644 index 000000000..d4acaa008 --- /dev/null +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.AnimationCard; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.AudioCard; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.CardImage; +import com.microsoft.bot.schema.Fact; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.MediaUrl; +import com.microsoft.bot.schema.OAuthCard; +import com.microsoft.bot.schema.ReceiptCard; +import com.microsoft.bot.schema.ReceiptItem; +import com.microsoft.bot.schema.SigninCard; +import com.microsoft.bot.schema.ThumbnailCard; +import com.microsoft.bot.schema.ThumbnailUrl; +import com.microsoft.bot.schema.VideoCard; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.concurrent.CompletionException; +import org.apache.commons.io.IOUtils; + +public class Cards { + public static Attachment createAdaptiveCardAttachment() { + Attachment adaptiveCardAttachment = new Attachment(); + + try ( + InputStream inputStream = adaptiveCardAttachment.getClass().getClassLoader() + .getResourceAsStream("adaptiveCard.json") + ) { + String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8); + + adaptiveCardAttachment.setContentType("application/vnd.microsoft.card.adaptive"); + adaptiveCardAttachment.setContent(new ObjectMapper().readValue(result, ObjectNode.class)); + + return adaptiveCardAttachment; + } catch (Throwable t) { + throw new CompletionException(t); + } + } + + public static HeroCard getHeroCard() { + HeroCard heroCard = new HeroCard(); + heroCard.setTitle("BotFramework Hero Card"); + heroCard.setSubtitle("Microsoft Bot Framework"); + heroCard.setText("Build and connect intelligent bots to interact with your users naturally wherever they are," + + " from text/sms to Skype, Slack, Office 365 mail and other popular services."); + heroCard.setImages(new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg")); + heroCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Get Started", "https://docs.microsoft.com/bot-framework")); + + return heroCard; + } + + public static ThumbnailCard getThumbnailCard() { + ThumbnailCard thumbnailCard = new ThumbnailCard(); + thumbnailCard.setTitle("BotFramework Thumbnail Card"); + thumbnailCard.setSubtitle("Microsoft Bot Framework"); + thumbnailCard.setText("Build and connect intelligent bots to interact with your users naturally wherever they are," + + " from text/sms to Skype, Slack, Office 365 mail and other popular services."); + thumbnailCard.setImages(new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg")); + thumbnailCard.setButtons(new CardAction( + ActionTypes.OPEN_URL, "Get Started", "https://docs.microsoft.com/bot-framework")); + + return thumbnailCard; + } + + public static ReceiptCard getReceiptCard() { + ReceiptCard receiptCard = new ReceiptCard(); + receiptCard.setTitle("John Doe"); + receiptCard.setFacts( + new Fact("Order Number", "1234"), new Fact("Payment Method", "VISA 5555-****")); + receiptCard.setItems( + new ReceiptItem(){{ + setTitle("Data Transfer"); + setPrice("$ 38.45"); + setQuantity("368"); + setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png")); + }}, + new ReceiptItem() {{ + setTitle("App Service"); + setPrice("$ 45.00"); + setQuantity("720"); + setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png")); + }} + ); + receiptCard.setTax("$ 7.50"); + receiptCard.setTotal("$ 90.95"); + receiptCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "More information") {{ + setImage( + "https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png"); + setValue("https://azure.microsoft.com/en-us/pricing/"); + }}); + + return receiptCard; + } + + public static SigninCard getSigninCard() { + SigninCard signinCard = new SigninCard(); + signinCard.setText("BotFramework Sign-in Card"); + signinCard.setButtons(new CardAction(ActionTypes.SIGNIN, "Sign-in", "https://login.microsoftonline.com/")); + return signinCard; + } + + public static AnimationCard getAnimationCard() { + AnimationCard animationCard = new AnimationCard(); + animationCard.setTitle("Microsoft Bot Framework"); + animationCard.setSubtitle("Animation Card"); + animationCard.setImage(new ThumbnailUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png")); + animationCard.setMedia(new MediaUrl("http://i.giphy.com/Ki55RUbOV5njy.gif")); + return animationCard; + } + + public static VideoCard getVideoCard() { + VideoCard videoCard = new VideoCard(); + videoCard.setTitle("Big Buck Bunny"); + videoCard.setSubtitle("by the Blender Institute"); + videoCard.setText("Big Buck Bunny (code-named Peach) is a short computer-animated comedy film by the Blender Institute," + + " part of the Blender Foundation. Like the foundation's previous film Elephants Dream," + + " the film was made using Blender, a free software application for animation made by the same foundation." + + " It was released as an open-source film under Creative Commons License Attribution 3.0."); + videoCard.setImage(new ThumbnailUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Big_buck_bunny_poster_big.jpg/220px-Big_buck_bunny_poster_big.jpg")); + videoCard.setMedia(new MediaUrl("http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4")); + videoCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Learn More", "https://peach.blender.org/")); + + return videoCard; + } + + public static AudioCard getAudioCard() { + AudioCard audioCard = new AudioCard(); + audioCard.setTitle("I am your father"); + audioCard.setSubtitle("Star Wars: Episode V - The Empire Strikes Back"); + audioCard.setText("The Empire Strikes Back (also known as Star Wars: Episode V – The Empire Strikes Back)" + + " is a 1980 American epic space opera film directed by Irvin Kershner. Leigh Brackett and" + + " Lawrence Kasdan wrote the screenplay, with George Lucas writing the film's story and serving" + + " as executive producer. The second installment in the original Star Wars trilogy, it was produced" + + " by Gary Kurtz for Lucasfilm Ltd. and stars Mark Hamill, Harrison Ford, Carrie Fisher, Billy Dee Williams," + + " Anthony Daniels, David Prowse, Kenny Baker, Peter Mayhew and Frank Oz."); + audioCard.setImage(new ThumbnailUrl("https://upload.wikimedia.org/wikipedia/en/3/3c/SW_-_Empire_Strikes_Back.jpg")); + audioCard.setMedia(new MediaUrl("http://www.wavlist.com/movies/004/father.wav")); + audioCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Read More", "https://en.wikipedia.org/wiki/The_Empire_Strikes_Back")); + + return audioCard; + } + + public static OAuthCard getOAuthCard() { + OAuthCard oauthCard = new OAuthCard(); + oauthCard.setText("BotFramework OAuth Card"); + oauthCard.setConnectionName("OAuth connection"); // Replace with the name of your Azure AD connection. + oauthCard.setButtons(new CardAction(ActionTypes.SIGNIN, "Sign In", "https://example.org/signin")); + return oauthCard; + } +} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java new file mode 100644 index 000000000..68aacb03d --- /dev/null +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + T withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java new file mode 100644 index 000000000..7507bc568 --- /dev/null +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.prompts.ChoicePrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.AttachmentLayoutTypes; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.springframework.stereotype.Component; + +@Component +public class MainDialog extends ComponentDialog { + public MainDialog() { + super("MainDialog"); + + WaterfallStep[] waterfallSteps = { + this::choiceCardStep, + this::showCardStep + }; + + // Define the main dialog and its related components. + addDialog(new ChoicePrompt("ChoicePrompt")); + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + // 1. Prompts the user if the user is not in the middle of a dialog. + // 2. Re-prompts the user when an invalid input is received. + private CompletableFuture choiceCardStep(WaterfallStepContext stepContext) { + // Create the PromptOptions which contain the prompt and re-prompt messages. + // PromptOptions also contains the list of choices available to the user. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("What card would you like to see? You can click or type the card name")); + promptOptions.setRetryPrompt(MessageFactory.text("That was not a valid choice, please select a card or number from 1 to 9.")); + promptOptions.setChoices(getChoices()); + + return stepContext.prompt("ChoicePrompt", promptOptions); + } + + // Send a Rich Card response to the user based on their choice. + // This method is only called when a valid prompt response is parsed from the user's response to the ChoicePrompt. + private CompletableFuture showCardStep(WaterfallStepContext stepContext) { + List attachments = new ArrayList<>(); + Activity reply = MessageFactory.attachment(attachments); + + switch (((FoundChoice) stepContext.getResult()).getValue()) { + case "Adaptive Card": + // Display an Adaptive Card + reply.getAttachments().add(Cards.createAdaptiveCardAttachment()); + break; + case "Animation Card": + // Display an AnimationCard. + reply.getAttachments().add(Cards.getAnimationCard().toAttachment()); + break; + case "Audio Card": + // Display an AudioCard + reply.getAttachments().add(Cards.getAudioCard().toAttachment()); + break; + case "Hero Card": + // Display a HeroCard. + reply.getAttachments().add(Cards.getHeroCard().toAttachment()); + break; + case "OAuth Card": + // Display an OAuthCard + reply.getAttachments().add(Cards.getOAuthCard().toAttachment()); + break; + case "Receipt Card": + // Display a ReceiptCard. + reply.getAttachments().add(Cards.getReceiptCard().toAttachment()); + break; + case "Signin Card": + // Display a SignInCard. + reply.getAttachments().add(Cards.getSigninCard().toAttachment()); + break; + case "Thumbnail Card": + // Display a ThumbnailCard. + reply.getAttachments().add(Cards.getThumbnailCard().toAttachment()); + break; + case "Video Card": + // Display a VideoCard + reply.getAttachments().add(Cards.getVideoCard().toAttachment()); + break; + default: + // Display a carousel of all the rich card types. + reply.setAttachmentLayout(AttachmentLayoutTypes.CAROUSEL); + reply.getAttachments().add(Cards.createAdaptiveCardAttachment()); + reply.getAttachments().add(Cards.getAnimationCard().toAttachment()); + reply.getAttachments().add(Cards.getAudioCard().toAttachment()); + reply.getAttachments().add(Cards.getHeroCard().toAttachment()); + reply.getAttachments().add(Cards.getOAuthCard().toAttachment()); + reply.getAttachments().add(Cards.getReceiptCard().toAttachment()); + reply.getAttachments().add(Cards.getSigninCard().toAttachment()); + reply.getAttachments().add(Cards.getThumbnailCard().toAttachment()); + reply.getAttachments().add(Cards.getVideoCard().toAttachment()); + break; + } + + // Send the card(s) to the user as an attachment to the activity + return stepContext.getContext().sendActivity(reply) + .thenCompose(resourceResponse -> stepContext.getContext().sendActivity( + // Give the user instructions about what to do next + MessageFactory.text("Type anything to see another card.") + )) + .thenCompose(resourceResponse -> stepContext.endDialog()); + } + + private List getChoices() { + return Arrays.asList( + new Choice("Adaptive Card", "adaptive"), + new Choice("Animation Card", "animation"), + new Choice("Audio Card", "audio"), + new Choice("Hero Card", "hero"), + new Choice("OAuth Card", "oauth"), + new Choice("Receipt Card", "receipt"), + new Choice("Signin Card", "signin"), + new Choice("Thumbnail Card", "thumbnail", "thumb"), + new Choice("Video Card", "video"), + new Choice("All cards", "all") + ); + } +} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java new file mode 100644 index 000000000..aede87551 --- /dev/null +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ChannelAccount; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; +import org.springframework.stereotype.Component; + +// RichCardsBot prompts a user to select a Rich Card and then returns the card +// that matches the user's selection. +@Component +public class RichCardsBot extends DialogBot { + + public RichCardsBot( + ConversationState withConversationState, + UserState withUserState, + MainDialog withDialog + ) { + super(withConversationState, withUserState, withDialog); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + Activity reply = MessageFactory.text("Welcome to CardBot." + + " This bot will show you different types of Rich Cards." + + " Please type anything to get started."); + + return turnContext.sendActivity(reply); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } +} diff --git a/samples/06.using-cards/src/main/resources/adaptiveCard.json b/samples/06.using-cards/src/main/resources/adaptiveCard.json new file mode 100644 index 000000000..1888ceefc --- /dev/null +++ b/samples/06.using-cards/src/main/resources/adaptiveCard.json @@ -0,0 +1,204 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "type": "AdaptiveCard", + "speak": "Your flight is confirmed for you and 3 other passengers from San Francisco to Amsterdam on Friday, October 10 8:30 AM", + "body": [ + { + "type": "TextBlock", + "text": "Passengers", + "weight": "bolder", + "isSubtle": false + }, + { + "type": "TextBlock", + "text": "Sarah Hum", + "separator": true + }, + { + "type": "TextBlock", + "text": "Jeremy Goldberg", + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "Evan Litvak", + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "2 Stops", + "weight": "bolder", + "spacing": "medium" + }, + { + "type": "TextBlock", + "text": "Fri, October 10 8:30 AM", + "weight": "bolder", + "spacing": "none" + }, + { + "type": "ColumnSet", + "separator": true, + "columns": [ + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "text": "San Francisco", + "isSubtle": true + }, + { + "type": "TextBlock", + "size": "extraLarge", + "color": "accent", + "text": "SFO", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": " " + }, + { + "type": "Image", + "url": "http://adaptivecards.io/content/airplane.png", + "size": "small", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "Amsterdam", + "isSubtle": true + }, + { + "type": "TextBlock", + "horizontalAlignment": "right", + "size": "extraLarge", + "color": "accent", + "text": "AMS", + "spacing": "none" + } + ] + } + ] + }, + { + "type": "TextBlock", + "text": "Non-Stop", + "weight": "bolder", + "spacing": "medium" + }, + { + "type": "TextBlock", + "text": "Fri, October 18 9:50 PM", + "weight": "bolder", + "spacing": "none" + }, + { + "type": "ColumnSet", + "separator": true, + "columns": [ + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "text": "Amsterdam", + "isSubtle": true + }, + { + "type": "TextBlock", + "size": "extraLarge", + "color": "accent", + "text": "AMS", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": " " + }, + { + "type": "Image", + "url": "http://adaptivecards.io/content/airplane.png", + "size": "small", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "San Francisco", + "isSubtle": true + }, + { + "type": "TextBlock", + "horizontalAlignment": "right", + "size": "extraLarge", + "color": "accent", + "text": "SFO", + "spacing": "none" + } + ] + } + ] + }, + { + "type": "ColumnSet", + "spacing": "medium", + "columns": [ + { + "type": "Column", + "width": "1", + "items": [ + { + "type": "TextBlock", + "text": "Total", + "size": "medium", + "isSubtle": true + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "$4,032.54", + "size": "medium", + "weight": "bolder" + } + ] + } + ] + } + ] +} diff --git a/samples/06.using-cards/src/main/resources/application.properties b/samples/06.using-cards/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/06.using-cards/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/06.using-cards/src/main/resources/log4j2.json b/samples/06.using-cards/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/06.using-cards/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF b/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml b/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/06.using-cards/src/main/webapp/index.html b/samples/06.using-cards/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/06.using-cards/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java b/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java new file mode 100644 index 000000000..0eb8407c0 --- /dev/null +++ b/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingcards; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From a408ebee442f4d8d026917cc1adc97ee464016df Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Tue, 2 Feb 2021 13:08:59 -0600 Subject: [PATCH 054/221] Sample: 44.prompt-users-for-input (#932) * Completed sample * Sample code * Correct readme and pom files. * Fix readme. * Delete settings.json Remove as this shouldn't be part of the source tree. Co-authored-by: tracyboehrer --- samples/44.prompt-users-for-input/LICENSE | 21 + samples/44.prompt-users-for-input/README.md | 90 ++++ .../new-rg-parameters.json | 42 ++ .../preexisting-rg-parameters.json | 39 ++ .../template-with-new-rg.json | 191 ++++++++ .../template-with-preexisting-rg.json | 158 +++++++ samples/44.prompt-users-for-input/pom.xml | 244 ++++++++++ .../promptusersforinput/Application.java | 48 ++ .../promptusersforinput/ConversationFlow.java | 32 ++ .../promptusersforinput/CustomPromptBot.java | 223 ++++++++++ .../promptusersforinput/UserProfile.java | 10 + .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../promptusersforinput/ApplicationTest.java | 19 + 17 files changed, 1571 insertions(+) create mode 100644 samples/44.prompt-users-for-input/LICENSE create mode 100644 samples/44.prompt-users-for-input/README.md create mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json create mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json create mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/44.prompt-users-for-input/pom.xml create mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java create mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java create mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java create mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java create mode 100644 samples/44.prompt-users-for-input/src/main/resources/application.properties create mode 100644 samples/44.prompt-users-for-input/src/main/resources/log4j2.json create mode 100644 samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/44.prompt-users-for-input/src/main/webapp/index.html create mode 100644 samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java diff --git a/samples/44.prompt-users-for-input/LICENSE b/samples/44.prompt-users-for-input/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/44.prompt-users-for-input/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/44.prompt-users-for-input/README.md b/samples/44.prompt-users-for-input/README.md new file mode 100644 index 000000000..1a7f0a8e7 --- /dev/null +++ b/samples/44.prompt-users-for-input/README.md @@ -0,0 +1,90 @@ +# Prompt users for input + +Bot Framework v4 Prompt Users for Input bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com). The bot maintains conversation state to track and direct the conversation and ask the user questions. The bot maintains user state to track the user's answers. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-promptusersforinput-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json new file mode 100644 index 000000000..ead339093 --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json @@ -0,0 +1,42 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "value": "" + }, + "groupName": { + "value": "" + }, + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "newAppServicePlanLocation": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json new file mode 100644 index 000000000..b6f5114fc --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "value": "" + }, + "appSecret": { + "value": "" + }, + "botId": { + "value": "" + }, + "botSku": { + "value": "" + }, + "newAppServicePlanName": { + "value": "" + }, + "newAppServicePlanSku": { + "value": { + "name": "S1", + "tier": "Standard", + "size": "S1", + "family": "S", + "capacity": 1 + } + }, + "appServicePlanLocation": { + "value": "" + }, + "existingAppServicePlan": { + "value": "" + }, + "newWebAppName": { + "value": "" + } + } +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..dcd6260a5 --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,191 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "F0", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "resourcesLocation": "[deployment().location]", + "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[variables('effectiveGroupLocation')]", + "properties": { + } + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new App Service Plan", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('effectivePlanLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('appServicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using the new App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2015-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('appServicePlanName')]", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..b790d2bdc --- /dev/null +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,158 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", + "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "name": "[variables('servicePlanName')]", + "reserved":true + } + }, + { + "comments": "Create a Web App using an App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2016-08-01", + "location": "[variables('resourcesLocation')]", + "kind": "app", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "serverFarmId": "[variables('servicePlanName')]", + "siteConfig": { + "linuxFxVersion": "JAVA|8-jre8", + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/pom.xml b/samples/44.prompt-users-for-input/pom.xml new file mode 100644 index 000000000..0174fbb77 --- /dev/null +++ b/samples/44.prompt-users-for-input/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-promptusersforinput + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Prompt Users for Input sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.promptusersforinput.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.promptusersforinput.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.7.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + jre8 + jre8 + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java new file mode 100644 index 000000000..bd12d9ab2 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see DialogBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java new file mode 100644 index 000000000..cf9f51702 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java @@ -0,0 +1,32 @@ +package com.microsoft.bot.sample.promptusersforinput; + +public class ConversationFlow { + + private Question lastQuestionAsked = Question.None; + + /** + * Identifies the last question asked. + */ + public enum Question { + Name, + Age, + Date, + None //Our last action did not involved a question. + } + + /** + * Gets the last question asked. + * @return The last question asked. + */ + public Question getLastQuestionAsked() { + return lastQuestionAsked; + } + + /** + * Sets the last question asked. + * @param withLastQuestionAsked the last question asked. + */ + public void setLastQuestionAsked(Question withLastQuestionAsked) { + this.lastQuestionAsked = withLastQuestionAsked; + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java new file mode 100644 index 000000000..08320dce6 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java @@ -0,0 +1,223 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.dialogs.prompts.PromptCultureModels; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; +import com.microsoft.recognizers.text.number.NumberRecognizer; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.Triple; +import org.springframework.stereotype.Component; + +/** + * This Bot implementation can run any type of Dialog. The use of type + * parameterization is to allows multiple different bots to be run at different + * endpoints within the same project. This can be achieved by defining distinct + * Controller types each with dependency on distinct Bot types. + */ +@Component +public class CustomPromptBot extends ActivityHandler { + + private final BotState userState; + private final BotState conversationState; + + public CustomPromptBot(ConversationState conversationState, UserState userState) { + this.conversationState = conversationState; + this.userState = userState; + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + + StatePropertyAccessor conversationStateAccessors = + conversationState.createProperty("ConversationFlow"); + + StatePropertyAccessor userStateAccessors = userState.createProperty("UserProfile"); + return userStateAccessors.get(turnContext, () -> new UserProfile()).thenCompose(profile -> { + return conversationStateAccessors.get(turnContext, () -> new ConversationFlow()).thenCompose(flow -> { + return fillOutUserProfile(flow, profile, turnContext); + }); + }) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + private static CompletableFuture fillOutUserProfile(ConversationFlow flow, + UserProfile profile, + TurnContext turnContext) { + String input = ""; + if (StringUtils.isNotBlank(turnContext.getActivity().getText())) { + input = turnContext.getActivity().getText().trim(); + } + + switch (flow.getLastQuestionAsked()) { + case None: + return turnContext.sendActivity("Let's get started. What is your name?", null, null) + .thenRun(() -> {flow.setLastQuestionAsked(ConversationFlow.Question.Name);}); + case Name: + Triple nameValidationResult = validateName(input); + if (nameValidationResult.getLeft()) { + profile.name = nameValidationResult.getMiddle(); + return turnContext.sendActivity(String.format("Hi %s.", profile.name), null, null) + .thenCompose(result -> turnContext.sendActivity("How old are you?", null, null)) + .thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Age); }); + } else { + if (StringUtils.isNotBlank(nameValidationResult.getRight())) { + return turnContext.sendActivity(nameValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); + } + } + case Age: + Triple ageValidationResult = ValidateAge(input); + if (ageValidationResult.getLeft()) { + profile.age = ageValidationResult.getMiddle(); + return turnContext.sendActivity(String.format("I have your age as %d.", profile.age), null, null) + .thenCompose(result -> turnContext.sendActivity("When is your flight?", null, null)) + .thenRun(() -> { flow.setLastQuestionAsked(ConversationFlow.Question.Date); }); + } else { + if (StringUtils.isNotBlank(ageValidationResult.getRight())) { + return turnContext.sendActivity(ageValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); + } + } + + case Date: + Triple dateValidationResult = ValidateDate(input); + AtomicReference profileReference = new AtomicReference(profile); + if (dateValidationResult.getLeft()) { + profile.date = dateValidationResult.getMiddle(); + return turnContext.sendActivity( + String.format("Your cab ride to the airport is scheduled for %s.", + profileReference.get().date)) + .thenCompose(result -> turnContext.sendActivity( + String.format("Thanks for completing the booking %s.", profileReference.get().name))) + .thenCompose(result -> turnContext.sendActivity("Type anything to run the bot again.")) + .thenRun(() -> { + flow.setLastQuestionAsked(ConversationFlow.Question.None); + profileReference.set(new UserProfile()); + }); + } else { + if (StringUtils.isNotBlank(dateValidationResult.getRight())) { + return turnContext.sendActivity(dateValidationResult.getRight(), null, null) + .thenApply(result -> null); + } else { + return turnContext.sendActivity("I'm sorry, I didn't understand that.", null, null) + .thenApply(result -> null); } + } + default: + return CompletableFuture.completedFuture(null); + } + } + + private static Triple validateName(String input) { + String name = null; + String message = null; + + if (StringUtils.isEmpty(input)) { + message = "Please enter a name that contains at least one character."; + } else { + name = input.trim(); + } + + return Triple.of(StringUtils.isBlank(message), name, message); + } + + private static Triple ValidateAge(String input) { + int age = 0; + String message = null; + + // Try to recognize the input as a number. This works for responses such as "twelve" as well as "12". + try { + // Attempt to convert the Recognizer result to an integer. This works for "a dozen", "twelve", "12", and so on. + // The recognizer returns a list of potential recognition results, if any. + + List results = NumberRecognizer.recognizeNumber(input, PromptCultureModels.ENGLISH_CULTURE); + for (ModelResult result : results) { + // The result resolution is a dictionary, where the "value" entry contains the processed String. + Object value = result.resolution.get("value"); + if (value != null) { + age = Integer.parseInt((String) value); + if (age >= 18 && age <= 120) { + return Triple.of(true, age, ""); + } + } + } + + message = "Please enter an age between 18 and 120."; + } + catch (Throwable th) { + message = "I'm sorry, I could not interpret that as an age. Please enter an age between 18 and 120."; + } + + return Triple.of(StringUtils.isBlank(message), age, message); + } + + private static Triple ValidateDate(String input) { + String date = null; + String message = null; + + // Try to recognize the input as a date-time. This works for responses such as "11/14/2018", "9pm", "tomorrow", "Sunday at 5pm", and so on. + // The recognizer returns a list of potential recognition results, if any. + try { + List results = DateTimeRecognizer.recognizeDateTime(input, PromptCultureModels.ENGLISH_CULTURE); + + // Check whether any of the recognized date-times are appropriate, + // and if so, return the first appropriate date-time. We're checking for a value at least an hour in the future. + LocalDateTime earliest = LocalDateTime.now().plus(1, ChronoUnit.HOURS); + + for (ModelResult result : results) { + // The result resolution is a dictionary, where the "values" entry contains the processed input. + List> resolutions = (List>) result.resolution.get("values"); + + for (Map resolution : resolutions) { + // The processed input contains a "value" entry if it is a date-time value, or "start" and + // "end" entries if it is a date-time range. + String dateString = (String) resolution.get("value"); + if (StringUtils.isBlank(dateString)) { + dateString = (String) resolution.get("start"); + } + if (StringUtils.isNotBlank(dateString)){ + DateTimeFormatter f = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); + LocalDateTime candidate = LocalDateTime.from(f.parse(dateString)); + if (earliest.isBefore(candidate)) { + DateTimeFormatter dateformat = DateTimeFormatter.ofPattern("MM-dd-yyyy"); + date = candidate.format(dateformat); + return Triple.of(true, date, message); + } + } + + } + } + + message = "I'm sorry, please enter a date at least an hour out."; + } + catch (Throwable th) { + message = "I'm sorry, I could not interpret that as an appropriate date. Please enter a date at least an hour out."; + } + + return Triple.of(false, date, message); + } +} diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java new file mode 100644 index 000000000..2f5df5b91 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java @@ -0,0 +1,10 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +public class UserProfile { + public String name; + public Integer age; + public String date; +} diff --git a/samples/44.prompt-users-for-input/src/main/resources/application.properties b/samples/44.prompt-users-for-input/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/44.prompt-users-for-input/src/main/resources/log4j2.json b/samples/44.prompt-users-for-input/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF b/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml b/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/src/main/webapp/index.html b/samples/44.prompt-users-for-input/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/44.prompt-users-for-input/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java b/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java new file mode 100644 index 000000000..df4f92a57 --- /dev/null +++ b/samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.promptusersforinput; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 59b5c50ca8650ca5c5afc8e5a2acce442af69cf7 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 3 Feb 2021 07:51:29 -0600 Subject: [PATCH 055/221] Add 'ExpectReplies' as the list of options for Delivery Mode (#937) --- .../bot/builder/BotFrameworkAdapter.java | 32 +++++- .../bot/builder/TurnContextImpl.java | 44 ++++++++- .../bot/builder/BotFrameworkAdapterTests.java | 97 +++++++++++++++++++ .../microsoft/bot/schema/DeliveryModes.java | 11 ++- .../microsoft/bot/schema/ExpectedReplies.java | 60 ++++++++++++ 5 files changed, 233 insertions(+), 11 deletions(-) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ExpectedReplies.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 9cae8c24a..a587516c5 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -37,6 +37,8 @@ import com.microsoft.bot.schema.ConversationParameters; import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.ConversationsResult; +import com.microsoft.bot.schema.DeliveryModes; +import com.microsoft.bot.schema.ExpectedReplies; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.Serialization; import com.microsoft.bot.schema.TokenExchangeState; @@ -483,10 +485,20 @@ public CompletableFuture processActivity( context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); return runPipeline(context, callback); }) - - // Handle Invoke scenarios, which deviate from the request/response model in - // that the Bot will return a specific body and return code. .thenCompose(result -> { + // Handle ExpectedReplies scenarios where the all the activities have been + // buffered and sent back at once in an invoke response. + if (DeliveryModes.fromString( + context.getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES + ) { + return CompletableFuture.completedFuture(new InvokeResponse( + HttpURLConnection.HTTP_OK, + new ExpectedReplies(context.getBufferedReplyActivities()) + )); + } + + // Handle Invoke scenarios, which deviate from the request/response model in + // that the Bot will return a specific body and return code. if (activity.isType(ActivityTypes.INVOKE)) { Activity invokeResponse = context.getTurnState().get(INVOKE_RESPONSE_KEY); if (invokeResponse == null) { @@ -1581,11 +1593,23 @@ protected Map getCredentialsCache() { } /** - * Get the ConnectorClient cache. For unit testing. + * Get the ConnectorClient cache. FOR UNIT TESTING. * * @return The ConnectorClient cache. */ protected Map getConnectorClientCache() { return Collections.unmodifiableMap(connectorClients); } + + /** + * Inserts a ConnectorClient into the cache. FOR UNIT TESTING ONLY. + * @param serviceUrl The service url + * @param appId The app did + * @param scope The scope + * @param client The ConnectorClient to insert. + */ + protected void addConnectorClientToCache(String serviceUrl, String appId, String scope, ConnectorClient client) { + String key = BotFrameworkAdapter.keyForConnectorClient(serviceUrl, appId, scope); + connectorClients.put(key, client); + } } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 2d53e94c3..e64022c1c 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -7,6 +7,7 @@ import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.DeliveryModes; import com.microsoft.bot.schema.InputHints; import com.microsoft.bot.schema.ResourceResponse; import java.util.Locale; @@ -39,6 +40,8 @@ public class TurnContextImpl implements TurnContext, AutoCloseable { */ private final Activity activity; + private List bufferedReplyActivities = new ArrayList<>(); + /** * Response handlers for send activity operations. */ @@ -217,6 +220,14 @@ public void setLocale(String withLocale) { } } + /** + * Gets a list of activities to send when `context.Activity.DeliveryMode == 'expectReplies'. + * @return A list of activities. + */ + public List getBufferedReplyActivities() { + return bufferedReplyActivities; + } + /** * Sends a message activity to the sender of the incoming activity. * @@ -385,12 +396,21 @@ public CompletableFuture sendActivities(List activ private CompletableFuture sendActivitiesThroughAdapter( List activities ) { - return adapter.sendActivities(this, activities).thenApply(responses -> { + if (DeliveryModes.fromString(getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES) { + ResourceResponse[] responses = new ResourceResponse[activities.size()]; boolean sentNonTraceActivity = false; for (int index = 0; index < responses.length; index++) { Activity sendActivity = activities.get(index); - sendActivity.setId(responses[index].getId()); + bufferedReplyActivities.add(sendActivity); + + // Ensure the TurnState has the InvokeResponseKey, since this activity + // is not being sent through the adapter, where it would be added to TurnState. + if (activity.isType(ActivityTypes.INVOKE_RESPONSE)) { + getTurnState().add(BotFrameworkAdapter.INVOKE_RESPONSE_KEY, activity); + } + + responses[index] = new ResourceResponse(); sentNonTraceActivity |= !sendActivity.isType(ActivityTypes.TRACE); } @@ -398,8 +418,24 @@ private CompletableFuture sendActivitiesThroughAdapter( responded = true; } - return responses; - }); + return CompletableFuture.completedFuture(responses); + } else { + return adapter.sendActivities(this, activities).thenApply(responses -> { + boolean sentNonTraceActivity = false; + + for (int index = 0; index < responses.length; index++) { + Activity sendActivity = activities.get(index); + sendActivity.setId(responses[index].getId()); + sentNonTraceActivity |= !sendActivity.isType(ActivityTypes.TRACE); + } + + if (sentNonTraceActivity) { + responded = true; + } + + return responses; + }); + } } private CompletableFuture sendActivitiesThroughCallbackPipeline( diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java index 4c68ed00f..58a72a3e7 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java @@ -19,12 +19,18 @@ import com.microsoft.bot.connector.authentication.SimpleChannelProvider; import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.CallerIdConstants; import com.microsoft.bot.schema.ConversationAccount; import com.microsoft.bot.schema.ConversationParameters; import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.ConversationResourceResponse; +import com.microsoft.bot.schema.DeliveryModes; +import com.microsoft.bot.schema.ExpectedReplies; +import com.microsoft.bot.schema.ResourceResponse; +import java.net.HttpURLConnection; import java.util.HashMap; +import java.util.List; import java.util.Map; import org.junit.Assert; import org.junit.Test; @@ -364,4 +370,95 @@ private static void getConnectorClientAndAssertValues( ); Assert.assertEquals("Unexpected base url", expectedUrl, client.baseUrl()); } + + @Test + public void DeliveryModeExpectReplies() { + BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider()); + + MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome")); + adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector); + + BotCallbackHandler callback = turnContext -> { + turnContext.sendActivity(MessageFactory.text("activity 1")).join(); + turnContext.sendActivity(MessageFactory.text("activity 2")).join(); + turnContext.sendActivity(MessageFactory.text("activity 3")).join(); + return CompletableFuture.completedFuture(null); + }; + + Activity inboundActivity = new Activity() {{ + setType(ActivityTypes.MESSAGE); + setChannelId(Channels.EMULATOR); + setServiceUrl("http://tempuri.org/whatever"); + setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); + setText("hello world"); + }}; + + InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); + + Assert.assertEquals((int) HttpURLConnection.HTTP_OK, invokeResponse.getStatus()); + List activities = ((ExpectedReplies)invokeResponse.getBody()).getActivities(); + Assert.assertEquals(3, activities.size()); + Assert.assertEquals("activity 1", activities.get(0).getText()); + Assert.assertEquals("activity 2", activities.get(1).getText()); + Assert.assertEquals("activity 3", activities.get(2).getText()); + Assert.assertEquals(0, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size()); + } + + @Test + public void DeliveryModeNormal() { + BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider()); + + MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome")); + adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector); + + BotCallbackHandler callback = turnContext -> { + turnContext.sendActivity(MessageFactory.text("activity 1")).join(); + turnContext.sendActivity(MessageFactory.text("activity 2")).join(); + turnContext.sendActivity(MessageFactory.text("activity 3")).join(); + return CompletableFuture.completedFuture(null); + }; + + Activity inboundActivity = new Activity() {{ + setType(ActivityTypes.MESSAGE); + setChannelId(Channels.EMULATOR); + setServiceUrl("http://tempuri.org/whatever"); + setDeliveryMode(DeliveryModes.NORMAL.toString()); + setText("hello world"); + setConversation(new ConversationAccount("conversationId")); + }}; + + InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); + + Assert.assertNull(invokeResponse); + Assert.assertEquals(3, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size()); + } + + // should be same as DeliverModes.NORMAL + @Test + public void DeliveryModeNull() { + BotFrameworkAdapter adapter = new BotFrameworkAdapter(new SimpleCredentialProvider()); + + MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome")); + adapter.addConnectorClientToCache("http://tempuri.org/whatever", null, null, mockConnector); + + BotCallbackHandler callback = turnContext -> { + turnContext.sendActivity(MessageFactory.text("activity 1")).join(); + turnContext.sendActivity(MessageFactory.text("activity 2")).join(); + turnContext.sendActivity(MessageFactory.text("activity 3")).join(); + return CompletableFuture.completedFuture(null); + }; + + Activity inboundActivity = new Activity() {{ + setType(ActivityTypes.MESSAGE); + setChannelId(Channels.EMULATOR); + setServiceUrl("http://tempuri.org/whatever"); + setText("hello world"); + setConversation(new ConversationAccount("conversationId")); + }}; + + InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); + + Assert.assertNull(invokeResponse); + Assert.assertEquals(3, ((MemoryConversations) mockConnector.getConversations()).getSentActivities().size()); + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java index 52c78f507..b69d9768d 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java @@ -11,14 +11,19 @@ */ public enum DeliveryModes { /** - * Enum value normal. + * The mode value for normal delivery modes. */ NORMAL("normal"), /** - * Enum value notification. + * The mode value for notification delivery modes. */ - NOTIFICATION("notification"); + NOTIFICATION("notification"), + + /** + * The value for expected replies delivery modes. + */ + EXPECT_REPLIES("expectReplies"); /** * The actual serialized value for a DeliveryModes instance. diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ExpectedReplies.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ExpectedReplies.java new file mode 100644 index 000000000..dd7e2a0fe --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ExpectedReplies.java @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.Arrays; +import java.util.List; + +/** + * Replies in response to DeliveryModes.EXPECT_REPLIES. + */ +public class ExpectedReplies { + @JsonProperty(value = "activities") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private List activities; + + /** + * Create an instance of ExpectReplies. + */ + public ExpectedReplies() { + + } + + /** + * Create an instance of ExpectReplies. + * @param withActivities The collection of activities that conforms to the + * ExpectedREplies schema. + */ + public ExpectedReplies(List withActivities) { + activities = withActivities; + } + + /** + * Create an instance of ExpectReplies. + * @param withActivities The array of activities that conforms to the + * ExpectedREplies schema. + */ + public ExpectedReplies(Activity... withActivities) { + this(Arrays.asList(withActivities)); + } + + /** + * Gets collection of Activities that conforms to the ExpectedReplies schema. + * @return The collection of activities that conforms to the ExpectedREplies schema. + */ + public List getActivities() { + return activities; + } + + /** + * Sets collection of Activities that conforms to the ExpectedReplies schema. + * @param withActivities The collection of activities that conforms to the + * ExpectedREplies schema. + */ + public void setActivities(List withActivities) { + activities = withActivities; + } +} From f6b4e1996ed45bc4e9eff7ae6c2ee2921c0e8dab Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 3 Feb 2021 09:26:42 -0600 Subject: [PATCH 056/221] Support for new installation action types (#941) --- .../bot/builder/ActivityHandler.java | 38 ++++++++ .../bot/builder/ActivityHandlerTests.java | 89 +++++++++++++++++++ 2 files changed, 127 insertions(+) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index f44d4aee0..60987f678 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -524,6 +524,44 @@ protected CompletableFuture onEvent(TurnContext turnContext) { * @return A task that represents the work queued to execute. */ protected CompletableFuture onInstallationUpdate(TurnContext turnContext) { + String action = turnContext.getActivity().getAction(); + if (StringUtils.isEmpty(action)) { + return CompletableFuture.completedFuture(null); + } + + switch (action) { + case "add": + case "add-upgrade": + return onInstallationUpdateAdd(turnContext); + + case "remove": + case "remove-upgrade": + return onInstallationUpdateRemove(turnContext); + + default: + return CompletableFuture.completedFuture(null); + } + } + + /** + * Override this in a derived class to provide logic specific to ActivityTypes.InstallationUpdate + * activities with 'action' set to 'add'. + * + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onInstallationUpdateAdd(TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + + /** + * Override this in a derived class to provide logic specific to ActivityTypes.InstallationUpdate + * activities with 'action' set to 'remove'. + * + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onInstallationUpdateRemove(TurnContext turnContext) { return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 4b358b2db..4ca4fcfc4 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -12,6 +12,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.atomic.AtomicReference; +import org.mockito.internal.matchers.Not; public class ActivityHandlerTests { @Test @@ -38,6 +39,82 @@ public void TestOnInstallationUpdate() { Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); } + @Test + public void TestInstallationUpdateAdd() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INSTALLATION_UPDATE); + setAction("add"); + } + }; + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); + Assert.assertEquals("onInstallationUpdateAdd", bot.getRecord().get(1)); + } + + @Test + public void TestInstallationUpdateAddUpgrade() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INSTALLATION_UPDATE); + setAction("add-upgrade"); + } + }; + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); + Assert.assertEquals("onInstallationUpdateAdd", bot.getRecord().get(1)); + } + + @Test + public void TestInstallationUpdateRemove() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INSTALLATION_UPDATE); + setAction("remove"); + } + }; + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); + Assert.assertEquals("onInstallationUpdateRemove", bot.getRecord().get(1)); + } + + @Test + public void TestInstallationUpdateRemoveUpgrade() { + Activity activity = new Activity() { + { + setType(ActivityTypes.INSTALLATION_UPDATE); + setAction("remove-upgrade"); + } + }; + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInstallationUpdate", bot.getRecord().get(0)); + Assert.assertEquals("onInstallationUpdateRemove", bot.getRecord().get(1)); + } + @Test public void TestOnTypingActivity() { Activity activity = new Activity(ActivityTypes.TYPING); @@ -570,6 +647,18 @@ protected CompletableFuture onInstallationUpdate(TurnContext turnContext) { return super.onInstallationUpdate(turnContext); } + @Override + protected CompletableFuture onInstallationUpdateAdd(TurnContext turnContext) { + record.add("onInstallationUpdateAdd"); + return CompletableFuture.completedFuture(null); + } + + @Override + protected CompletableFuture onInstallationUpdateRemove(TurnContext turnContext) { + record.add("onInstallationUpdateRemove"); + return CompletableFuture.completedFuture(null); + } + @Override protected CompletableFuture onTypingActivity(TurnContext turnContext) { record.add("onTypingActivity"); From f624b5649ca87dc6bc6c908051692d680914440e Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 3 Feb 2021 11:16:25 -0600 Subject: [PATCH 057/221] Updated ARM templates for "deployment sub create" support (#944) --- samples/02.echo-bot/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/03.welcome-user/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/05.multi-turn-prompt/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/06.using-cards/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/08.suggested-actions/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/16.proactive-messages/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/17.multilingual-bot/README.md | 43 ++++++++++++++++++- .../template-with-new-rg.json | 31 ++++++++----- samples/44.prompt-users-for-input/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/45.state-management/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- samples/47.inspection/README.md | 4 +- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- .../template-with-new-rg.json | 13 +++--- samples/servlet-echo/README.md | 4 +- .../template-with-new-rg-gov.json | 17 ++++---- .../template-with-new-rg.json | 20 +++++---- 32 files changed, 228 insertions(+), 157 deletions(-) diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index a89774b04..246459b59 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json b/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index e65396c03..a1f6435a3 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json b/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json +++ b/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/05.multi-turn-prompt/README.md b/samples/05.multi-turn-prompt/README.md index e4375c425..5a9e2a472 100644 --- a/samples/05.multi-turn-prompt/README.md +++ b/samples/05.multi-turn-prompt/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json +++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/06.using-cards/README.md b/samples/06.using-cards/README.md index ddfdce522..38d2a17bc 100644 --- a/samples/06.using-cards/README.md +++ b/samples/06.using-cards/README.md @@ -67,10 +67,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json +++ b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md index 40dcd231e..b6a6ea967 100644 --- a/samples/08.suggested-actions/README.md +++ b/samples/08.suggested-actions/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json b/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json +++ b/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/16.proactive-messages/README.md b/samples/16.proactive-messages/README.md index dc35a0cee..eefe97d13 100644 --- a/samples/16.proactive-messages/README.md +++ b/samples/16.proactive-messages/README.md @@ -63,10 +63,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json b/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json +++ b/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/17.multilingual-bot/README.md b/samples/17.multilingual-bot/README.md index 16ddd7477..417f3d110 100644 --- a/samples/17.multilingual-bot/README.md +++ b/samples/17.multilingual-bot/README.md @@ -58,7 +58,48 @@ The API uses the most modern neural machine translation technology, as well as o ## Deploy this bot to Azure -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` + +#### To an existing Resource Group +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` + +### 5. Update app id and password +In src/main/resources/application.properties update +- `MicrosoftAppPassword` with the botsecret value +- `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. ### Add `TranslatorKey` to Application Settings diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json index 3a0e81219..c951bc908 100644 --- a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json @@ -3,6 +3,7 @@ "contentVersion": "1.0.0.0", "parameters": { "groupLocation": { + "defaultValue": "", "type": "string", "metadata": { "description": "Specifies the location of the Resource Group." @@ -33,12 +34,14 @@ } }, "botSku": { + "defaultValue": "F0", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." } }, "newAppServicePlanName": { + "defaultValue": "", "type": "string", "metadata": { "description": "The name of the App Service Plan." @@ -47,10 +50,10 @@ "newAppServicePlanSku": { "type": "object", "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", "capacity": 1 }, "metadata": { @@ -58,6 +61,7 @@ } }, "newAppServicePlanLocation": { + "defaultValue": "", "type": "string", "metadata": { "description": "The location of the App Service Plan. Defaults to \"westus\"." @@ -72,8 +76,10 @@ } }, "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", + "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", + "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", @@ -84,8 +90,9 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} + "location": "[variables('effectiveGroupLocation')]", + "properties": { + } }, { "type": "Microsoft.Resources/deployments", @@ -110,8 +117,10 @@ "apiVersion": "2018-02-01", "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]" + "name": "[variables('appServicePlanName')]", + "reserved":true } }, { @@ -130,8 +139,8 @@ "siteConfig": { "appSettings": [ { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" }, { "name": "MicrosoftAppId", diff --git a/samples/44.prompt-users-for-input/README.md b/samples/44.prompt-users-for-input/README.md index 1a7f0a8e7..0ea71b0e0 100644 --- a/samples/44.prompt-users-for-input/README.md +++ b/samples/44.prompt-users-for-input/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/45.state-management/README.md b/samples/45.state-management/README.md index d7fda09ec..875ef97b1 100644 --- a/samples/45.state-management/README.md +++ b/samples/45.state-management/README.md @@ -57,10 +57,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/45.state-management/deploymentTemplates/template-with-new-rg.json b/samples/45.state-management/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/45.state-management/deploymentTemplates/template-with-new-rg.json +++ b/samples/45.state-management/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/47.inspection/README.md b/samples/47.inspection/README.md index c789de873..641081aae 100644 --- a/samples/47.inspection/README.md +++ b/samples/47.inspection/README.md @@ -69,10 +69,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/47.inspection/deploymentTemplates/template-with-new-rg.json b/samples/47.inspection/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/47.inspection/deploymentTemplates/template-with-new-rg.json +++ b/samples/47.inspection/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json +++ b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json +++ b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json +++ b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json +++ b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json b/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json +++ b/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json +++ b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json b/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json +++ b/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json index dcd6260a5..c951bc908 100644 --- a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json +++ b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { @@ -129,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -179,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], diff --git a/samples/servlet-echo/README.md b/samples/servlet-echo/README.md index 0b08a5371..53f981d88 100644 --- a/samples/servlet-echo/README.md +++ b/samples/servlet-echo/README.md @@ -36,10 +36,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` #### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/servlet-echo/deploymentTemplates/template-with-new-rg-gov.json b/samples/servlet-echo/deploymentTemplates/template-with-new-rg-gov.json index d5264be99..097476a7f 100644 --- a/samples/servlet-echo/deploymentTemplates/template-with-new-rg-gov.json +++ b/samples/servlet-echo/deploymentTemplates/template-with-new-rg-gov.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.us')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,7 +115,7 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", "kind": "app", "properties": { @@ -128,7 +129,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -180,7 +181,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], @@ -189,4 +190,4 @@ } } ] -} +} \ No newline at end of file diff --git a/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json b/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json index b12b7056f..c951bc908 100644 --- a/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json +++ b/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json @@ -76,13 +76,14 @@ } }, "variables": { - "resourcesLocation": "[deployment().location]", + "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", + "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ { @@ -114,11 +115,12 @@ "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", + "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", - "kind": "app", + "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]" + "name": "[variables('appServicePlanName')]", + "reserved":true } }, { @@ -128,7 +130,7 @@ "location": "[variables('resourcesLocation')]", "kind": "app", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { @@ -178,7 +180,7 @@ "storageResourceId": null }, "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" ] } ], @@ -187,4 +189,4 @@ } } ] -} +} \ No newline at end of file From 5a7ba24a0f6a0e6bde518503ca829c685e99992a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 4 Feb 2021 12:24:35 -0600 Subject: [PATCH 058/221] Corrected ARM templates and POM settings for proper Azure deployment. (#952) * Corrected ARM templates and POM settings for proper Azure deployment. * Corrected sample READMEs to match new ARM templates --- samples/02.echo-bot/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/02.echo-bot/pom.xml | 6 +- samples/03.welcome-user/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/03.welcome-user/pom.xml | 8 +- samples/05.multi-turn-prompt/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/05.multi-turn-prompt/pom.xml | 8 +- samples/06.using-cards/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/06.using-cards/pom.xml | 6 +- samples/08.suggested-actions/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/08.suggested-actions/pom.xml | 6 +- samples/16.proactive-messages/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/16.proactive-messages/pom.xml | 6 +- samples/17.multilingual-bot/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 131 ++++++++++++++++-- samples/17.multilingual-bot/pom.xml | 6 +- samples/44.prompt-users-for-input/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/44.prompt-users-for-input/pom.xml | 6 +- samples/45.state-management/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/45.state-management/pom.xml | 6 +- samples/47.inspection/README.md | 4 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/47.inspection/pom.xml | 6 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- .../pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- .../pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- .../pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- .../pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/54.teams-task-module/pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/55.teams-link-unfurling/pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/56.teams-file-upload/pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- samples/57.teams-conversation-bot/pom.xml | 10 +- .../new-rg-parameters.json | 42 ------ .../preexisting-rg-parameters.json | 39 ------ .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 125 +++++++++++++++-- .../pom.xml | 10 +- samples/servlet-echo/README.md | 4 +- .../template-with-new-rg.json | 129 +++++++++++++++-- .../template-with-preexisting-rg.json | 123 ++++++++++++++-- samples/servlet-echo/pom.xml | 4 +- 109 files changed, 4646 insertions(+), 2179 deletions(-) delete mode 100644 samples/02.echo-bot/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/02.echo-bot/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/03.welcome-user/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/03.welcome-user/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/06.using-cards/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/08.suggested-actions/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/08.suggested-actions/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/16.proactive-messages/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/16.proactive-messages/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/45.state-management/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/45.state-management/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/47.inspection/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/47.inspection/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/50.teams-messaging-extensions-search/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/50.teams-messaging-extensions-search/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/51.teams-messaging-extensions-action/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/51.teams-messaging-extensions-action/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/54.teams-task-module/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/54.teams-task-module/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/55.teams-link-unfurling/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/55.teams-link-unfurling/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/56.teams-file-upload/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/56.teams-file-upload/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/57.teams-conversation-bot/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/57.teams-conversation-bot/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/deploymentTemplates/preexisting-rg-parameters.json diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md index 246459b59..b34532527 100644 --- a/samples/02.echo-bot/README.md +++ b/samples/02.echo-bot/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/02.echo-bot/deploymentTemplates/new-rg-parameters.json b/samples/02.echo-bot/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/02.echo-bot/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/02.echo-bot/deploymentTemplates/preexisting-rg-parameters.json b/samples/02.echo-bot/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/02.echo-bot/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json b/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index 58da4dc5b..da10c1451 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md index a1f6435a3..fe55c6e58 100644 --- a/samples/03.welcome-user/README.md +++ b/samples/03.welcome-user/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "welcomBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="welcomBotPlan" newWebAppName="welcomBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="welcomBot" newAppServicePlanName="welcomBotPlan" appServicePlanLocation="westus" --name "welcomBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/03.welcome-user/deploymentTemplates/new-rg-parameters.json b/samples/03.welcome-user/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/03.welcome-user/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/03.welcome-user/deploymentTemplates/preexisting-rg-parameters.json b/samples/03.welcome-user/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/03.welcome-user/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json b/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json +++ b/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json b/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 72edaa5ef..9f9fed900 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,9 +141,9 @@ linux - jre8 - jre8 - + Java 8 + Java SE + diff --git a/samples/05.multi-turn-prompt/README.md b/samples/05.multi-turn-prompt/README.md index 5a9e2a472..c45ee940a 100644 --- a/samples/05.multi-turn-prompt/README.md +++ b/samples/05.multi-turn-prompt/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/05.multi-turn-prompt/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json b/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/05.multi-turn-prompt/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json +++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/05.multi-turn-prompt/pom.xml b/samples/05.multi-turn-prompt/pom.xml index 7dfbebe83..8ba0b34b5 100644 --- a/samples/05.multi-turn-prompt/pom.xml +++ b/samples/05.multi-turn-prompt/pom.xml @@ -134,7 +134,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -147,9 +147,9 @@ linux - jre8 - jre8 - + Java 8 + Java SE + diff --git a/samples/06.using-cards/README.md b/samples/06.using-cards/README.md index 38d2a17bc..dae398417 100644 --- a/samples/06.using-cards/README.md +++ b/samples/06.using-cards/README.md @@ -67,10 +67,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "usingCardsBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="usingCardsBotPlan" newWebAppName="usingCardsBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="usingCardsBot" newAppServicePlanName="usingCardsBotPlan" appServicePlanLocation="westus" --name "usingCardsBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json b/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/06.using-cards/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json b/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/06.using-cards/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json +++ b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/06.using-cards/pom.xml b/samples/06.using-cards/pom.xml index 85ab6bc90..f584799fc 100644 --- a/samples/06.using-cards/pom.xml +++ b/samples/06.using-cards/pom.xml @@ -134,7 +134,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -147,8 +147,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md index b6a6ea967..4c19c3efd 100644 --- a/samples/08.suggested-actions/README.md +++ b/samples/08.suggested-actions/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "suggestedActionsBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="suggestedActionsBotPlan" newWebAppName="suggestedActionsBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="suggestedActionsBot" newAppServicePlanName="suggestedActionsBotPlan" appServicePlanLocation="westus" --name "suggestedActionsBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/08.suggested-actions/deploymentTemplates/new-rg-parameters.json b/samples/08.suggested-actions/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/08.suggested-actions/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/08.suggested-actions/deploymentTemplates/preexisting-rg-parameters.json b/samples/08.suggested-actions/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/08.suggested-actions/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json b/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json +++ b/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json b/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 1070cd534..24390d9ab 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/16.proactive-messages/README.md b/samples/16.proactive-messages/README.md index eefe97d13..06a53ef39 100644 --- a/samples/16.proactive-messages/README.md +++ b/samples/16.proactive-messages/README.md @@ -63,10 +63,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "proactiveBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="proactiveBotPlan" newWebAppName="proactiveBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="proactiveBot" newAppServicePlanName="proactiveBotPlan" appServicePlanLocation="westus" --name "proactiveBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/16.proactive-messages/deploymentTemplates/new-rg-parameters.json b/samples/16.proactive-messages/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/16.proactive-messages/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/16.proactive-messages/deploymentTemplates/preexisting-rg-parameters.json b/samples/16.proactive-messages/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/16.proactive-messages/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json b/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json +++ b/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json b/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index d57074b94..ed234c19b 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/17.multilingual-bot/README.md b/samples/17.multilingual-bot/README.md index 417f3d110..7cd05af61 100644 --- a/samples/17.multilingual-bot/README.md +++ b/samples/17.multilingual-bot/README.md @@ -83,10 +83,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "multilingualBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multilingualBotPlan" newWebAppName="multilingualBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multilingualBot" newAppServicePlanName="multilingualBotPlan" appServicePlanLocation="westus" --name "multilingualBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json b/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/17.multilingual-bot/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json b/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/17.multilingual-bot/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json index 34a026819..024dcf08d 100644 --- a/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -21,7 +21,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -37,10 +37,10 @@ "newAppServicePlanSku": { "type": "object", "defaultValue": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", "capacity": 1 }, "metadata": { @@ -49,6 +49,7 @@ }, "appServicePlanLocation": { "type": "string", + "defaultValue": "", "metadata": { "description": "The location of the App Service Plan." } @@ -72,6 +73,7 @@ "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", @@ -79,35 +81,66 @@ }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", "apiVersion": "2018-02-01", "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]" + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "10.14.1" + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" }, { "name": "MicrosoftAppId", @@ -127,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -151,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/17.multilingual-bot/pom.xml b/samples/17.multilingual-bot/pom.xml index 137e34808..b9c2888b4 100644 --- a/samples/17.multilingual-bot/pom.xml +++ b/samples/17.multilingual-bot/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/44.prompt-users-for-input/README.md b/samples/44.prompt-users-for-input/README.md index 0ea71b0e0..27ee05cdb 100644 --- a/samples/44.prompt-users-for-input/README.md +++ b/samples/44.prompt-users-for-input/README.md @@ -54,10 +54,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "promptForInputBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="promptForInputBotPlan" newWebAppName="promptForInputBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="promptForInputBot" newAppServicePlanName="promptForInputBotPlan" appServicePlanLocation="westus" --name "promptForInputBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/44.prompt-users-for-input/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json b/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/44.prompt-users-for-input/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/44.prompt-users-for-input/pom.xml b/samples/44.prompt-users-for-input/pom.xml index 0174fbb77..968896797 100644 --- a/samples/44.prompt-users-for-input/pom.xml +++ b/samples/44.prompt-users-for-input/pom.xml @@ -134,7 +134,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -147,8 +147,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/45.state-management/README.md b/samples/45.state-management/README.md index 875ef97b1..26439b984 100644 --- a/samples/45.state-management/README.md +++ b/samples/45.state-management/README.md @@ -57,10 +57,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "stateBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="stateBotPlan" newWebAppName="stateBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "stateBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="stateBot" newAppServicePlanName="stateBotPlan" appServicePlanLocation="westus" --name "stateBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/45.state-management/deploymentTemplates/new-rg-parameters.json b/samples/45.state-management/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/45.state-management/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/45.state-management/deploymentTemplates/preexisting-rg-parameters.json b/samples/45.state-management/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/45.state-management/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/45.state-management/deploymentTemplates/template-with-new-rg.json b/samples/45.state-management/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/45.state-management/deploymentTemplates/template-with-new-rg.json +++ b/samples/45.state-management/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/45.state-management/deploymentTemplates/template-with-preexisting-rg.json b/samples/45.state-management/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/45.state-management/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/45.state-management/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 810b2ff63..2b4b38f4a 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -127,7 +127,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -140,8 +140,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/47.inspection/README.md b/samples/47.inspection/README.md index 641081aae..2c7061009 100644 --- a/samples/47.inspection/README.md +++ b/samples/47.inspection/README.md @@ -69,10 +69,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "inspectionBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="inspectionBotPlan" newWebAppName="inspectionBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="inspectionBot" newAppServicePlanName="inspectionBotPlan" appServicePlanLocation="westus" --name "inspectionBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/47.inspection/deploymentTemplates/new-rg-parameters.json b/samples/47.inspection/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/47.inspection/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/47.inspection/deploymentTemplates/preexisting-rg-parameters.json b/samples/47.inspection/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/47.inspection/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/47.inspection/deploymentTemplates/template-with-new-rg.json b/samples/47.inspection/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/47.inspection/deploymentTemplates/template-with-new-rg.json +++ b/samples/47.inspection/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/47.inspection/deploymentTemplates/template-with-preexisting-rg.json b/samples/47.inspection/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/47.inspection/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/47.inspection/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index c9234069f..ceb1e6f9a 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -128,7 +128,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/50.teams-messaging-extensions-search/deploymentTemplates/new-rg-parameters.json b/samples/50.teams-messaging-extensions-search/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/50.teams-messaging-extensions-search/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/50.teams-messaging-extensions-search/deploymentTemplates/preexisting-rg-parameters.json b/samples/50.teams-messaging-extensions-search/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/50.teams-messaging-extensions-search/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json +++ b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-preexisting-rg.json b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index a0f0cee95..a414d66b6 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -143,11 +143,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -156,8 +156,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/51.teams-messaging-extensions-action/deploymentTemplates/new-rg-parameters.json b/samples/51.teams-messaging-extensions-action/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/51.teams-messaging-extensions-action/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/51.teams-messaging-extensions-action/deploymentTemplates/preexisting-rg-parameters.json b/samples/51.teams-messaging-extensions-action/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/51.teams-messaging-extensions-action/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json +++ b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-preexisting-rg.json b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index 7c1bcf328..ca1d13c8c 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -128,11 +128,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/new-rg-parameters.json b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/preexisting-rg-parameters.json b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json +++ b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-preexisting-rg.json b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index 75875b517..6ed6f2e37 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -139,11 +139,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -152,8 +152,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/new-rg-parameters.json b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/preexisting-rg-parameters.json b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json +++ b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-preexisting-rg.json b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index 520a821ea..5bcd5697f 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -128,11 +128,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/54.teams-task-module/deploymentTemplates/new-rg-parameters.json b/samples/54.teams-task-module/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/54.teams-task-module/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/54.teams-task-module/deploymentTemplates/preexisting-rg-parameters.json b/samples/54.teams-task-module/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/54.teams-task-module/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json b/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json +++ b/samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/54.teams-task-module/deploymentTemplates/template-with-preexisting-rg.json b/samples/54.teams-task-module/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/54.teams-task-module/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/54.teams-task-module/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index df0eb0454..707d49e82 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -137,11 +137,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -150,8 +150,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/55.teams-link-unfurling/deploymentTemplates/new-rg-parameters.json b/samples/55.teams-link-unfurling/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/55.teams-link-unfurling/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/55.teams-link-unfurling/deploymentTemplates/preexisting-rg-parameters.json b/samples/55.teams-link-unfurling/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/55.teams-link-unfurling/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json +++ b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-preexisting-rg.json b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/55.teams-link-unfurling/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/55.teams-link-unfurling/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index 31149dbf3..f4434ba87 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -128,11 +128,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/56.teams-file-upload/deploymentTemplates/new-rg-parameters.json b/samples/56.teams-file-upload/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/56.teams-file-upload/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/56.teams-file-upload/deploymentTemplates/preexisting-rg-parameters.json b/samples/56.teams-file-upload/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/56.teams-file-upload/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json b/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json +++ b/samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/56.teams-file-upload/deploymentTemplates/template-with-preexisting-rg.json b/samples/56.teams-file-upload/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/56.teams-file-upload/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/56.teams-file-upload/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index 8561fb30c..9471be319 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -128,11 +128,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/57.teams-conversation-bot/deploymentTemplates/new-rg-parameters.json b/samples/57.teams-conversation-bot/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/57.teams-conversation-bot/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/57.teams-conversation-bot/deploymentTemplates/preexisting-rg-parameters.json b/samples/57.teams-conversation-bot/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/57.teams-conversation-bot/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json +++ b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/57.teams-conversation-bot/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/57.teams-conversation-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index b89168a1f..bef2bf5b9 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -128,11 +128,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -141,8 +141,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/new-rg-parameters.json b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/preexisting-rg-parameters.json b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json +++ b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-preexisting-rg.json b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-preexisting-rg.json index b790d2bdc..024dcf08d 100644 --- a/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-preexisting-rg.json @@ -72,15 +72,16 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", @@ -89,25 +90,53 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", "appSettings": [ { "name": "JAVA_OPTS", @@ -131,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -155,4 +256,4 @@ ] } ] -} \ No newline at end of file +} diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index 3967d715e..6ea0ef908 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -132,11 +132,11 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 - {groupname} - {botname} + ${groupname} + ${botname} JAVA_OPTS @@ -145,8 +145,8 @@ linux - jre8 - jre8 + Java 8 + Java SE diff --git a/samples/servlet-echo/README.md b/samples/servlet-echo/README.md index 53f981d88..814abf980 100644 --- a/samples/servlet-echo/README.md +++ b/samples/servlet-echo/README.md @@ -36,10 +36,10 @@ Record the `appid` from the returned JSON Replace the values for ``, ``, ``, and `` in the following commands: #### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` #### To an existing Resource Group -`az group deployment sub create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` ### 5. Update app id and password In src/main/resources/application.properties update diff --git a/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json b/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json index c951bc908..ec2460d3a 100644 --- a/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json +++ b/samples/servlet-echo/deploymentTemplates/template-with-new-rg.json @@ -34,7 +34,7 @@ } }, "botSku": { - "defaultValue": "F0", + "defaultValue": "S1", "type": "string", "metadata": { "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." @@ -76,13 +76,12 @@ } }, "variables": { - "resourcesLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), deployment().location, parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" }, "resources": [ @@ -90,9 +89,8 @@ "name": "[parameters('groupName')]", "type": "Microsoft.Resources/resourceGroups", "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } + "location": "[parameters('groupLocation')]", + "properties": {} }, { "type": "Microsoft.Resources/deployments", @@ -111,7 +109,7 @@ "variables": {}, "resources": [ { - "comments": "Create a new App Service Plan", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "name": "[variables('appServicePlanName')]", "apiVersion": "2018-02-01", @@ -119,23 +117,52 @@ "sku": "[parameters('newAppServicePlanSku')]", "kind": "linux", "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using the new App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -160,6 +187,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", @@ -189,4 +288,4 @@ } } ] -} \ No newline at end of file +} diff --git a/samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg.json b/samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg.json index 5f5b6fba9..024dcf08d 100644 --- a/samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg.json +++ b/samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg.json @@ -72,39 +72,70 @@ "variables": { "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" }, "resources": [ { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", "type": "Microsoft.Web/serverfarms", "condition": "[not(variables('useExistingAppServicePlan'))]", "name": "[variables('servicePlanName')]", "apiVersion": "2018-02-01", "location": "[variables('resourcesLocation')]", "sku": "[parameters('newAppServicePlanSku')]", - "kind": "app", + "kind": "linux", "properties": { - "name": "[variables('servicePlanName')]" + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 } }, { - "comments": "Create a Web App using an App Service Plan", + "comments": "Create a Web App using a Linux App Service Plan", "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", + "apiVersion": "2018-11-01", "location": "[variables('resourcesLocation')]", - "kind": "app", + "kind": "app,linux", "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" ], "name": "[variables('webAppName')]", "properties": { "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", "siteConfig": { "appSettings": [ { @@ -129,6 +160,78 @@ } } }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, { "apiVersion": "2017-12-01", "type": "Microsoft.BotService/botServices", diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index ae1d05dbc..fecf0693c 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -122,7 +122,7 @@ com.microsoft.azure azure-webapp-maven-plugin - 1.7.0 + 1.12.0 V2 ${groupname} @@ -135,7 +135,7 @@ windows - 1.8 + Java 8 tomcat 9.0 From 2e7499e382df7dd63b1f1116bd4452cbed3d55c4 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 8 Feb 2021 20:12:56 -0600 Subject: [PATCH 059/221] Some Waterfall tests were missing some joins, causing some random failures (#961) * Some Waterfall tests were missing some joins, causing some random failures * More missing joins in dialog tests --- .../AliasPathResolver.java | 0 .../AtAtPathResolver.java | 0 .../AtPathResolver.java | 0 .../DollarPathResolver.java | 0 .../HashPathResolver.java | 0 .../PercentPathResolver.java | 0 .../package-info.java | 0 .../bot/dialogs/prompts/ConfirmPrompt.java | 2 +- .../bot/dialogs/ComponentDialogTests.java | 49 ++++++++------- .../bot/dialogs/MemoryScopeTests.java | 8 +-- .../dialogs/PromptValidatorContextTests.java | 15 +++-- .../bot/dialogs/ReplaceDialogTests.java | 3 +- .../microsoft/bot/dialogs/WaterfallTests.java | 17 ++--- .../dialogs/prompts/ActivityPromptTests.java | 31 ++++------ .../prompts/AttachmentPromptTests.java | 12 ++-- .../ChoicePromptLocaleVariationTests.java | 4 +- .../dialogs/prompts/ChoicePromptTests.java | 62 ++++++------------- .../prompts/ConfirmPromptLocTests.java | 22 +++---- .../dialogs/prompts/ConfirmPromptTests.java | 5 +- .../microsoft/bot/schema/ActivityTest.java | 1 - 20 files changed, 97 insertions(+), 134 deletions(-) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/AliasPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/AtAtPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/AtPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/DollarPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/HashPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/PercentPathResolver.java (100%) rename libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/{PathResolvers => pathresolvers}/package-info.java (100%) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AliasPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AliasPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AliasPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AtAtPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtAtPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AtAtPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AtPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/AtPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/AtPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/DollarPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/DollarPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/DollarPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/HashPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/HashPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/HashPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/PercentPathResolver.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/PercentPathResolver.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/PercentPathResolver.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/package-info.java similarity index 100% rename from libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/PathResolvers/package-info.java rename to libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/pathresolvers/package-info.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java index bb7afe798..d9f9f003f 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -345,7 +345,7 @@ private String determineCulture(Activity activity) { } String culture = PromptCultureModels.mapToNearestLanguage(locale); - if (StringUtils.isBlank(culture) || !choiceDefaults.containsKey(culture)) { + if (StringUtils.isEmpty(culture) || !choiceDefaults.containsKey(culture)) { culture = PromptCultureModels.ENGLISH_CULTURE; } return culture; diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java index 2a0bb2a98..9f822ef58 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java @@ -51,16 +51,17 @@ public void CallDialogInParentComponent() { class Step1 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity("Child started.").join(); - return stepContext.beginDialog("parentDialog", "test"); + return stepContext.getContext() + .sendActivity("Child started.") + .thenCompose(resourceResponse -> stepContext.beginDialog("parentDialog", "test")); } } class Step2 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext() - .sendActivity(String.format("Child finished. Value: %s", stepContext.getResult())); - return stepContext.endDialog(); + return stepContext.getContext() + .sendActivity(String.format("Child finished. Value: %s", stepContext.getResult())) + .thenCompose(resourceResponse -> stepContext.endDialog()); } } @@ -73,8 +74,9 @@ public CompletableFuture waterfallStep(WaterfallStepContext st class ParentStep implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(String.format("Parent called.", stepContext.getResult())); - return stepContext.endDialog(stepContext.getOptions()); + return stepContext.getContext() + .sendActivity(String.format("Parent called.", stepContext.getResult())) + .thenCompose(resourceResponse -> stepContext.endDialog(stepContext.getOptions())); } } WaterfallStep[] parentStep = new WaterfallStep[] {new ParentStep() }; @@ -90,7 +92,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st dc.beginDialog("parentComponent", null).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int value = (int) results.getResult(); - turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))).join(); } return CompletableFuture.completedFuture(null); @@ -122,10 +124,10 @@ public void BasicWaterfallTest() throws UnsupportedDataTypeException { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { - dc.beginDialog("test-waterfall", null); + dc.beginDialog("test-waterfall", null).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int value = (int) results.getResult(); - turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))).join(); } return CompletableFuture.completedFuture(null); }) @@ -238,10 +240,10 @@ public void BasicComponentDialogTest() throws UnsupportedDataTypeException { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { - dc.beginDialog("TestComponentDialog", null); + dc.beginDialog("TestComponentDialog", null).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int value = (int) results.getResult(); - turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))).join(); } return CompletableFuture.completedFuture(null); }) @@ -280,10 +282,10 @@ public void NestedComponentDialogTest() { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { - dc.beginDialog("TestNestedComponentDialog", null); + dc.beginDialog("TestNestedComponentDialog", null).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int value = (int) results.getResult(); - turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))); + turnContext.sendActivity(MessageFactory.text(String.format("Bot received the number '%d'.", value))).join(); } return CompletableFuture.completedFuture(null); }) @@ -328,16 +330,16 @@ public void CallDialogDefinedInParentComponent() { class Step1 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity("Child started."); - return stepContext.beginDialog("parentDialog", options); + return stepContext.getContext().sendActivity("Child started.") + .thenCompose(resourceResponse -> stepContext.beginDialog("parentDialog", options)); } } class Step2 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { Assert.assertEquals("test", (String) stepContext.getResult()); - stepContext.getContext().sendActivity("Child finished."); - return stepContext.endDialog(); + return stepContext.getContext().sendActivity("Child finished.") + .thenCompose(resourceResponse -> stepContext.endDialog()); } } @@ -353,9 +355,9 @@ public CompletableFuture waterfallStep(WaterfallStepContext st Map stepOptions = (Map) stepContext.getOptions(); Assert.assertNotNull(stepOptions); Assert.assertTrue(stepOptions.containsKey("value")); - stepContext.getContext().sendActivity( - String.format("Parent called with: {%s}", stepOptions.get("value"))); - return stepContext.endDialog(stepOptions.get("value")); + return stepContext.getContext() + .sendActivity(String.format("Parent called with: %s", stepOptions.get("value"))) + .thenCompose(resourceResponse -> stepContext.endDialog(stepOptions.get("value"))); } } @@ -374,7 +376,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st if (results.getStatus() == DialogTurnStatus.EMPTY) { dc.beginDialog("parentComponent", null).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { - turnContext.sendActivity(MessageFactory.text("Done")); + turnContext.sendActivity(MessageFactory.text("Done")).join(); } return CompletableFuture.completedFuture(null); }) @@ -382,7 +384,8 @@ public CompletableFuture waterfallStep(WaterfallStepContext st .assertReply("Child started.") .assertReply("Parent called with: test") .assertReply("Child finished.") - .startTest(); + .startTest() + .join(); } // private static TestFlow CreateTestFlow(WaterfallDialog waterfallDialog) { diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java index fbf000797..dd04e1af6 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/MemoryScopeTests.java @@ -45,7 +45,7 @@ public TestFlow CreateDialogContext(DialogTestFunction handler) { .useBotState(new ConversationState(new MemoryStorage())); DialogManager dm = new DialogManager(new LamdbaDialog(testName.getMethodName(), handler), null); return new TestFlow(adapter, (turnContext) -> { - return dm.onTurn(turnContext).thenAccept(null); + return dm.onTurn(turnContext).thenApply(dialogManagerResult -> null); }).sendConverationUpdate(); } @@ -77,7 +77,7 @@ public void SimpleMemoryScopesTest() { } return CompletableFuture.completedFuture(null); }; - CreateDialogContext(testFunction).startTest(); + CreateDialogContext(testFunction).startTest().join(); } @Test @@ -100,7 +100,7 @@ public void BotStateMemoryScopeTest() { for (Pair stateScope : stateScopes) { final String name = "test-name"; final String value = "test-value"; - stateScope.getValue0().createProperty(name).set(dc.getContext(), value); + stateScope.getValue0().createProperty(name).set(dc.getContext(), value).join(); Object memory = stateScope.getValue1().getMemory(dc); @@ -108,7 +108,7 @@ public void BotStateMemoryScopeTest() { } return CompletableFuture.completedFuture(null); }; - CreateDialogContext(testFunction).startTest(); + CreateDialogContext(testFunction).startTest().join(); } public class CustomState extends BotState { diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java index ef71e46aa..539255999 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java @@ -156,16 +156,14 @@ public void PromptValidatorNumberOfAttempts() { PromptValidator validator = (promptContext) -> { String result = promptContext.getRecognized().getValue(); if (result.length() > 3) { - Activity succeededMessage = MessageFactory.text(String.format("You got it at the %nth try!", + Activity succeededMessage = MessageFactory.text(String.format("You got it at the %dth try!", promptContext.getAttemptCount())); - promptContext.getContext().sendActivity(succeededMessage); - return CompletableFuture.completedFuture(true); + return promptContext.getContext().sendActivity(succeededMessage).thenApply(resourceResponse -> true); } else { - promptContext.getContext().sendActivity( - MessageFactory.text("Please send a name that is longer than 3 characters.")); + return promptContext.getContext() + .sendActivity(MessageFactory.text(String.format("Please send a name that is longer than 3 characters. %d", promptContext.getAttemptCount()))) + .thenApply(resourceResponse -> false); } - - return CompletableFuture.completedFuture(false); }; dialogs.add(new TextPrompt("namePrompt", validator)); @@ -197,7 +195,8 @@ public void PromptValidatorNumberOfAttempts() { .send("John") .assertReply("You got it at the 4th try!") .assertReply("John is a great name!") - .startTest(); + .startTest() + .join(); } } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java index 80553d51e..f4b02fb00 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java @@ -94,7 +94,8 @@ public void ReplaceDialogTelemetryClientNotNull() { return CompletableFuture.completedFuture(null); }) .send("hello") - .startTest(); + .startTest() + .join(); } private class FirstDialog extends ComponentDialog { diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java index c9944e5fc..ed5bd91cf 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java @@ -189,9 +189,9 @@ public void WaterfallWithClass() { new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); - dc.continueDialog(); + dc.continueDialog().join(); if (!turnContext.getResponded()) { - dc.beginDialog("test", null); + dc.beginDialog("test", null).join(); } return CompletableFuture.completedFuture(null); }) @@ -287,7 +287,8 @@ public void WaterfallNested() { .assertReply("step2.1") .send("hello") .assertReply("step2.2") - .startTest(); + .startTest() + .join(); } @Test @@ -302,8 +303,7 @@ public void WaterfallDateTimePromptFirstInvalidThenValidInput() { WaterfallStep[] steps = new WaterfallStep[] { (stepContext) -> { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("Provide a date"); + options.setPrompt(MessageFactory.text("Provide a date")); return stepContext.prompt("dateTimePrompt", options); }, (stepContext) -> { @@ -325,7 +325,7 @@ public void WaterfallDateTimePromptFirstInvalidThenValidInput() { dc.continueDialog().join(); if (!turnContext.getResponded()) { - dc.beginDialog("test-dateTimePrompt", null); + dc.beginDialog("test-dateTimePrompt", null).join(); } return CompletableFuture.completedFuture(null); }) @@ -334,7 +334,8 @@ public void WaterfallDateTimePromptFirstInvalidThenValidInput() { .send("hello again") .assertReply("Provide a date") .send("Wednesday 4 oclock") - .startTest(); + .startTest() + .join(); } @Test @@ -356,7 +357,7 @@ public void WaterfallCancel() { dialog.endDialog( new TurnContextImpl(new TestAdapter(), new Activity(ActivityTypes.MESSAGE)), dialogInstance, - DialogReason.CANCEL_CALLED); + DialogReason.CANCEL_CALLED).join(); Assert.assertTrue(telemetryClient.trackEventCallCount > 0); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java index 160d798c9..e0038d656 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java @@ -113,18 +113,14 @@ public void ActivityPromptShouldSendRetryPromptIfValidationFailed() { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("please send an event."); - Activity retryActivity = new Activity(ActivityTypes.MESSAGE); - retryActivity.setText("Retrying - please send an event."); - options.setPrompt(activity); - options.setRetryPrompt(retryActivity); + options.setPrompt(MessageFactory.text("please send an event.")); + options.setRetryPrompt(MessageFactory.text("Retrying - please send an event.")); dc.prompt("EventActivityPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Activity content = (Activity) results.getResult(); turnContext.sendActivity(content).join(); } else if (results.getStatus() == DialogTurnStatus.WAITING) { - turnContext.sendActivity("Test complete."); + turnContext.sendActivity("Test complete.").join(); } return CompletableFuture.completedFuture(null); }); @@ -134,7 +130,8 @@ public void ActivityPromptShouldSendRetryPromptIfValidationFailed() { .assertReply("please send an event.") .send("test") .assertReply("Retrying - please send an event.") - .startTest(); + .startTest() + .join(); } @Test @@ -156,12 +153,8 @@ public void ActivityPromptResumeDialogShouldPromptNotRetry() switch (turnContext.getActivity().getText()) { case "begin": PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("please send an event."); - Activity retryActivity = new Activity(ActivityTypes.MESSAGE); - retryActivity.setText("Retrying - please send an event."); - options.setPrompt(activity); - options.setRetryPrompt(retryActivity); + options.setPrompt(MessageFactory.text("please send an event.")); + options.setRetryPrompt(MessageFactory.text("Retrying - please send an event.")); dc.prompt("EventActivityPrompt", options).join(); break; case "continue": @@ -183,7 +176,8 @@ public void ActivityPromptResumeDialogShouldPromptNotRetry() .send("resume") // 'ResumeDialogAsync' of ActivityPrompt does NOT cause a Retry .assertReply("please send an event.") - .startTest(); + .startTest() + .join(); } @Test @@ -208,9 +202,7 @@ public void OnPromptOverloadWithoutIsRetryParamReturnsBasicActivityPrompt() { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("please send an event."); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("please send an event.")); dc.prompt("EventActivityWithoutRetryPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Activity content = (Activity) results.getResult(); @@ -223,7 +215,8 @@ public void OnPromptOverloadWithoutIsRetryParamReturnsBasicActivityPrompt() { .assertReply("please send an event.") .send(eventActivity) .assertReply("2") - .startTest(); + .startTest() + .join(); } @Test diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java index 508ca84ad..075b1dc98 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/AttachmentPromptTests.java @@ -76,8 +76,7 @@ public void BasicAttachmentPrompt() { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("please add an attachment."); + options.setPrompt(MessageFactory.text("please add an attachment.")); dc.prompt("AttachmentPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { ArrayList attachments = (ArrayList) results.getResult(); @@ -94,7 +93,8 @@ public void BasicAttachmentPrompt() { .assertReply("please add an attachment.") .send(activityWithAttachment) .assertReply("some content") - .startTest(); + .startTest() + .join(); } @Test @@ -130,8 +130,7 @@ public void RetryAttachmentPrompt() { DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("please add an attachment."); + options.setPrompt(MessageFactory.text("please add an attachment.")); dc.prompt("AttachmentPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { ArrayList attachments = (ArrayList) results.getResult(); @@ -148,6 +147,7 @@ public void RetryAttachmentPrompt() { .assertReply("please add an attachment.") .send(activityWithAttachment) .assertReply("some content") - .startTest(); + .startTest() + .join(); } } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java index 6d5bd1f7a..df2259bba 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptLocaleVariationTests.java @@ -120,9 +120,7 @@ public void ShouldRecognizeLocaleVariationsOfCorrectLocales(String testCulture, if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java index 4d99f3d9d..c6df50191 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -118,9 +118,7 @@ public void ShouldSendPrompt() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -151,9 +149,7 @@ public void ShouldSendPromptAsAnInlineList() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -186,9 +182,7 @@ public void ShouldSendPromptAsANumberedList() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -196,7 +190,8 @@ public void ShouldSendPromptAsANumberedList() { }) .send("hello") .assertReply("favorite color?\n\n 1. red\n 2. green\n 3. blue") - .startTest(); + .startTest() + .join(); } @Test @@ -220,9 +215,7 @@ public void ShouldSendPromptUsingSuggestedActions() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -279,9 +272,7 @@ public void ShouldSendPromptUsingHeroCard() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -347,9 +338,8 @@ public void ShouldSendPromptUsingAppendedHeroCard() { attachment.setContentType("text/plain"); PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - activity.setAttachments(new ArrayList() { { add(attachment); } }); + Activity activity = MessageFactory.text("favorite color?"); + activity.setAttachments(Arrays.asList(attachment)); options.setPrompt(activity); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); @@ -412,9 +402,7 @@ public void ShouldSendPromptWithoutAddingAList() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -447,8 +435,7 @@ public void ShouldSendPromptWithoutAddingAListButAddingSsml() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); + Activity activity = MessageFactory.text("favorite color?"); activity.setSpeak("spoken prompt"); options.setPrompt(activity); options.setChoices(colorChoices); @@ -483,9 +470,7 @@ public void ShouldRecognizeAChoice() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { @@ -524,13 +509,9 @@ public void ShouldNotRecognizeOtherText() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); + options.setRetryPrompt(MessageFactory.text("your favorite color, please?")); options.setChoices(colorChoices); - Activity retryActivity = new Activity(ActivityTypes.MESSAGE); - retryActivity.setText("your favorite color, please?"); - options.setRetryPrompt(retryActivity); dc.prompt("ChoicePrompt", options).join(); } return CompletableFuture.completedFuture(null); @@ -571,9 +552,7 @@ public void ShouldCallCustomValidator() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -608,9 +587,7 @@ public void ShouldUseChoiceStyleIfPresent() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); options.setStyle(ListStyle.SUGGESTED_ACTION); dc.prompt("ChoicePrompt", options).join(); @@ -690,9 +667,7 @@ public void PerformShouldDefaultToEnglishLocale(String locale) { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("favorite color?")); options.setChoices(colorChoices); dc.prompt("ChoicePrompt", options).join(); } @@ -749,8 +724,7 @@ public void ShouldAcceptAndRecognizeCustomLocaleDict() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("favorite color?"); + Activity activity = MessageFactory.text("favorite color?"); activity.setLocale(culture.getLocale()); options.setPrompt(activity); options.setChoices(colorChoices); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java index 51631bd8f..c4ca44438 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java @@ -146,15 +146,13 @@ public void ConfirmPrompt_Locale_Override_ChoiceDefaults(String defaultLocale, S if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("Prompt."); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("Prompt.")); dc.prompt("ConfirmPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { if ((Boolean) results.getResult()) { - turnContext.sendActivity(MessageFactory.text("1")); + turnContext.sendActivity(MessageFactory.text("1")).join(); } else { - turnContext.sendActivity(MessageFactory.text("0")); + turnContext.sendActivity(MessageFactory.text("0")).join(); } } return CompletableFuture.completedFuture(null); @@ -175,26 +173,26 @@ private void ConfirmPrompt_Locale_Impl(String activityLocale, String defaultLoca DialogSet dialogs = new DialogSet(dialogState); // Prompt should default to English if locale is a non-supported value - dialogs.add(new ConfirmPrompt("ConfirmPrompt", null, null, defaultLocale)); + dialogs.add(new ConfirmPrompt("ConfirmPrompt", null, defaultLocale)); new TestFlow(adapter, (turnContext) -> { + turnContext.getActivity().setLocale(activityLocale); + DialogContext dc = dialogs.createContext(turnContext).join(); DialogTurnResult results = dc.continueDialog().join(); if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("Prompt."); - options.setPrompt(activity); + options.setPrompt(MessageFactory.text("Prompt.")); dc.prompt("ConfirmPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { if ((Boolean) results.getResult()) { - turnContext.sendActivity(MessageFactory.text("1")); + turnContext.sendActivity(MessageFactory.text("1")).join(); } else { - turnContext.sendActivity(MessageFactory.text("0")); + turnContext.sendActivity(MessageFactory.text("0")).join(); } } return CompletableFuture.completedFuture(null); - }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest(); + }).send("hello").assertReply("Prompt. " + prompt).send(utterance).assertReply(expectedResponse).startTest().join(); } } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java index d017e8996..76ad2edee 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptTests.java @@ -60,10 +60,7 @@ public void ConfirmPrompt() { if (results.getStatus() == DialogTurnStatus.EMPTY) { PromptOptions options = new PromptOptions(); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setText("Please confirm."); - options.setPrompt(activity); - + options.setPrompt(MessageFactory.text("Please confirm.")); dc.prompt("ConfirmPrompt", options).join(); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { if ((Boolean) results.getResult()) { diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java index 61f15ff75..949226bcc 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java @@ -459,7 +459,6 @@ public void HasContentIsTrueWhenActivityChannelDataHasContent() { activity.setText(null); activity.setSummary(null); - activity.setAttachments(null); activity.setChannelData("test-channelData"); boolean result = activity.hasContent(); From 1537eceaec31f7dd886bc315601bc92afcce1b5a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Tue, 9 Feb 2021 08:14:23 -0600 Subject: [PATCH 060/221] OpenIdMetadata signing keys should refresh every 24 hours (#930) --- .../authentication/CachingOpenIdMetadata.java | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java index 336c5fa71..29c6f7dee 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CachingOpenIdMetadata.java @@ -27,7 +27,8 @@ */ class CachingOpenIdMetadata implements OpenIdMetadata { private static final Logger LOGGER = LoggerFactory.getLogger(CachingOpenIdMetadata.class); - private static final int CACHE_DAYS = 5; + private static final int CACHE_DAYS = 1; + private static final int CACHE_HOURS = 1; private String url; private long lastUpdated; @@ -58,13 +59,19 @@ class CachingOpenIdMetadata implements OpenIdMetadata { @Override public OpenIdMetadataKey getKey(String keyId) { synchronized (sync) { - // If keys are more than 5 days old, refresh them + // If keys are more than CACHE_DAYS days old, refresh them if (lastUpdated < System.currentTimeMillis() - Duration.ofDays(CACHE_DAYS).toMillis()) { refreshCache(); } // Search the cache even if we failed to refresh - return findKey(keyId); + OpenIdMetadataKey key = findKey(keyId); + if (key == null && lastUpdated < System.currentTimeMillis() - Duration.ofHours(CACHE_HOURS).toMillis()) { + // Refresh the cache if a key is not found (max once per CACHE_HOURS) + refreshCache(); + key = findKey(keyId); + } + return key; } } From 28fe834716f71b7c340e250bcd6e36dd53286ca9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Feb 2021 08:42:04 -0600 Subject: [PATCH 061/221] Sample 43.complex-dialog (#959) * Sample 43.complex-dialog * Refactored to use @Bean instead of @Component to be consistent with other samples. --- .../microsoft/bot/dialogs/DialogState.java | 2 +- .../bot/dialogs/WaterfallDialog.java | 34 +- .../microsoft/bot/dialogs/prompts/Prompt.java | 42 +- samples/43.complex-dialog/LICENSE | 21 + samples/43.complex-dialog/README.md | 90 ++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/43.complex-dialog/pom.xml | 244 ++++++++++ .../bot/sample/complexdialog/Application.java | 86 ++++ .../complexdialog/DialogAndWelcome.java | 45 ++ .../bot/sample/complexdialog/DialogBot.java | 53 +++ .../bot/sample/complexdialog/MainDialog.java | 55 +++ .../complexdialog/ReviewSelectionDialog.java | 99 +++++ .../sample/complexdialog/TopLevelDialog.java | 95 ++++ .../bot/sample/complexdialog/UserProfile.java | 18 + .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/complexdialog/ApplicationTest.java | 19 + 21 files changed, 1869 insertions(+), 38 deletions(-) create mode 100644 samples/43.complex-dialog/LICENSE create mode 100644 samples/43.complex-dialog/README.md create mode 100644 samples/43.complex-dialog/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/43.complex-dialog/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/43.complex-dialog/pom.xml create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/Application.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogAndWelcome.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogBot.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/MainDialog.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/ReviewSelectionDialog.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/TopLevelDialog.java create mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/UserProfile.java create mode 100644 samples/43.complex-dialog/src/main/resources/application.properties create mode 100644 samples/43.complex-dialog/src/main/resources/log4j2.json create mode 100644 samples/43.complex-dialog/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/43.complex-dialog/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/43.complex-dialog/src/main/webapp/index.html create mode 100644 samples/43.complex-dialog/src/test/java/com/microsoft/bot/sample/complexdialog/ApplicationTest.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java index 29d1fdb01..257889980 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogState.java @@ -13,7 +13,7 @@ */ public class DialogState { @JsonProperty(value = "dialogStack") - private List dialogStack = new ArrayList(); + private List dialogStack; /** * Initializes a new instance of the class with an empty stack. diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java index 1ac77aef5..597f71ad9 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java @@ -24,15 +24,15 @@ */ public class WaterfallDialog extends Dialog { - private final String persistedOptions = "options"; - private final String stepIndex = "stepIndex"; - private final String persistedValues = "values"; - private final String persistedInstanceId = "instanceId"; + private static final String PERSISTED_OPTIONS = "options"; + private static final String PERSISTED_VALUES = "values"; + private static final String PERSISTED_INSTANCEID = "instanceId"; + private static final String STEP_INDEX = "stepIndex"; private final List steps; /** - * Initializes a new instance of the {@link waterfallDialog} class. + * Initializes a new instance of the {@link WaterfallDialog} class. * * @param dialogId The dialog ID. * @param actions Optional actions to be defined by the caller. @@ -90,9 +90,9 @@ public CompletableFuture beginDialog(DialogContext dc, Object // Initialize waterfall state Map state = dc.getActiveDialog().getState(); String instanceId = UUID.randomUUID().toString(); - state.put(persistedOptions, options); - state.put(persistedValues, new HashMap()); - state.put(persistedInstanceId, instanceId); + state.put(PERSISTED_OPTIONS, options); + state.put(PERSISTED_VALUES, new HashMap()); + state.put(PERSISTED_INSTANCEID, instanceId); Map properties = new HashMap(); properties.put("DialogId", getId()); @@ -156,8 +156,8 @@ public CompletableFuture resumeDialog(DialogContext dc, Dialog // Increment step index and run step Map state = dc.getActiveDialog().getState(); int index = 0; - if (state.containsKey(stepIndex)) { - index = (int) state.get(stepIndex); + if (state.containsKey(STEP_INDEX)) { + index = (int) state.get(STEP_INDEX); } return runStep(dc, index + 1, reason, result); @@ -178,9 +178,9 @@ public CompletableFuture endDialog(TurnContext turnContext, DialogInstance HashMap state = new HashMap((Map) instance.getState()); // Create step context - int index = (int) state.get(stepIndex); + int index = (int) state.get(STEP_INDEX); String stepName = waterfallStepName(index); - String instanceId = (String) state.get(persistedInstanceId); + String instanceId = (String) state.get(PERSISTED_INSTANCEID); HashMap properties = new HashMap(); properties.put("DialogId", getId()); @@ -190,7 +190,7 @@ public CompletableFuture endDialog(TurnContext turnContext, DialogInstance getTelemetryClient().trackEvent("WaterfallCancel", properties); } else if (reason == DialogReason.END_CALLED) { HashMap state = new HashMap((Map) instance.getState()); - String instanceId = (String) state.get(persistedInstanceId); + String instanceId = (String) state.get(PERSISTED_INSTANCEID); HashMap properties = new HashMap(); properties.put("DialogId", getId()); @@ -210,7 +210,7 @@ public CompletableFuture endDialog(TurnContext turnContext, DialogInstance */ protected CompletableFuture onStep(WaterfallStepContext stepContext) { String stepName = waterfallStepName(stepContext.getIndex()); - String instanceId = (String) stepContext.getActiveDialog().getState().get(persistedInstanceId); + String instanceId = (String) stepContext.getActiveDialog().getState().get(PERSISTED_INSTANCEID); HashMap properties = new HashMap(); properties.put("DialogId", getId()); @@ -245,11 +245,11 @@ protected CompletableFuture runStep(DialogContext dc, int inde // Update persisted step index Map state = (Map) dc.getActiveDialog().getState(); - state.put(stepIndex, index); + state.put(STEP_INDEX, index); // Create step context - Object options = state.get(persistedOptions); - Map values = (Map) state.get(persistedValues); + Object options = state.get(PERSISTED_OPTIONS); + Map values = (Map) state.get(PERSISTED_VALUES); WaterfallStepContext stepContext = new WaterfallStepContext(this, dc, options, values, index, reason, result); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java index aced3a4ab..8188870f8 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -32,13 +32,14 @@ * Defines the core behavior of prompt dialogs. * * When the prompt ends, it should return a Object that represents the value - * that was prompted for. Use {@link DialogSet#add(Dialog)} or - * {@link ComponentDialog#addDialog(Dialog)} to add a prompt to a dialog set or - * component dialog, respectively. Use + * that was prompted for. Use {@link com.microsoft.bot.dialogs.DialogSet#add(Dialog)} or + * {@link com.microsoft.bot.dialogs.ComponentDialog#addDialog(Dialog)} to add a prompt to + * a dialog set or component dialog, respectively. Use * {@link DialogContext#prompt(String, PromptOptions)} or * {@link DialogContext#beginDialog(String, Object)} to start the prompt. If you - * start a prompt from a {@link WaterfallStep} in a {@link WaterfallDialog} , - * then the prompt result will be available in the next step of the waterfall. + * start a prompt from a {@link com.microsoft.bot.dialogs.WaterfallStep} in a + * {@link com.microsoft.bot.dialogs.WaterfallDialog}, then the prompt result will be + * available in the next step of the waterfall. * * @param Type the prompt is created for. */ @@ -46,8 +47,8 @@ public abstract class Prompt extends Dialog { public static final String ATTEMPTCOUNTKEY = "AttemptCount"; - private final String persistedOptions = "options"; - private final String persistedState = "state"; + private static final String PERSISTED_OPTIONS = "options"; + private static final String PERSISTED_STATE = "state"; private final PromptValidator validator; /** @@ -58,8 +59,9 @@ public abstract class Prompt extends Dialog { * @param validator Optional, a {@link PromptValidator{T}} that contains * additional, custom validation for this prompt. * - * The value of {@link dialogId} must be unique within the - * {@link DialogSet} or {@link ComponentDialog} to which the + * The value of dialogId must be unique within the + * {@link com.microsoft.bot.dialogs.DialogSet} or + * {@link com.microsoft.bot.dialogs.ComponentDialog} to which the * prompt is added. */ public Prompt(String dialogId, PromptValidator validator) { @@ -114,16 +116,16 @@ public CompletableFuture beginDialog(DialogContext dc, Object // Initialize prompt state Map state = dc.getActiveDialog().getState(); - state.put(persistedOptions, opt); + state.put(PERSISTED_OPTIONS, opt); HashMap pState = new HashMap(); pState.put(ATTEMPTCOUNTKEY, 0); - state.put(persistedState, pState); + state.put(PERSISTED_STATE, pState); // Send initial prompt onPrompt(dc.getContext(), - (Map) state.get(persistedState), - (PromptOptions) state.get(persistedOptions), + (Map) state.get(PERSISTED_STATE), + (PromptOptions) state.get(PERSISTED_OPTIONS), false); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } @@ -157,8 +159,8 @@ public CompletableFuture continueDialog(DialogContext dc) { // Perform base recognition DialogInstance instance = dc.getActiveDialog(); - Map state = (Map) instance.getState().get(persistedState); - PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + Map state = (Map) instance.getState().get(PERSISTED_STATE); + PromptOptions options = (PromptOptions) instance.getState().get(PERSISTED_OPTIONS); PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); state.put(ATTEMPTCOUNTKEY, (int) state.get(ATTEMPTCOUNTKEY) + 1); @@ -226,8 +228,8 @@ public CompletableFuture resumeDialog(DialogContext dc, Dialog */ @Override public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { - Map state = (Map) instance.getState().get(persistedState); - PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); + Map state = (Map) instance.getState().get(PERSISTED_STATE); + PromptOptions options = (PromptOptions) instance.getState().get(PERSISTED_OPTIONS); onPrompt(turnContext, state, options, false).join(); return CompletableFuture.completedFuture(null); } @@ -252,7 +254,7 @@ protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEv // Perform base recognition Map state = dc.getActiveDialog().getState(); PromptRecognizerResult recognized = onRecognize(dc.getContext(), - (Map) state.get(persistedState), (PromptOptions) state.get(persistedOptions)).join(); + (Map) state.get(PERSISTED_STATE), (PromptOptions) state.get(PERSISTED_OPTIONS)).join(); return CompletableFuture.completedFuture(recognized.getSucceeded()); } @@ -272,8 +274,8 @@ protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEv * @param isRetry true if this is the first time this prompt dialog instance * is on the stack is prompting the user for input; * otherwise, false. Determines whether - * {@link PromptOptions#prompt} or - * {@link PromptOptions#retryPrompt} should be used. + * {@link PromptOptions#getPrompt()} or + * {@link PromptOptions#getRetryPrompt()} should be used. * * @return A {@link CompletableFuture} representing the asynchronous operation. */ diff --git a/samples/43.complex-dialog/LICENSE b/samples/43.complex-dialog/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/43.complex-dialog/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/43.complex-dialog/README.md b/samples/43.complex-dialog/README.md new file mode 100644 index 000000000..39197e467 --- /dev/null +++ b/samples/43.complex-dialog/README.md @@ -0,0 +1,90 @@ +# Complex Dialog Sample + +This sample creates a complex conversation with dialogs. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use the prompts classes included in `botbuilder-dialogs`. This bot will ask for the user's name and age, then store the responses. It demonstrates a multi-turn dialog flow using a text prompt, a number prompt, and state accessors to store and retrieve values. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-complexdialog-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/43.complex-dialog/deploymentTemplates/template-with-new-rg.json b/samples/43.complex-dialog/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/43.complex-dialog/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/43.complex-dialog/deploymentTemplates/template-with-preexisting-rg.json b/samples/43.complex-dialog/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/43.complex-dialog/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/43.complex-dialog/pom.xml b/samples/43.complex-dialog/pom.xml new file mode 100644 index 000000000..7304e65aa --- /dev/null +++ b/samples/43.complex-dialog/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-complexdialog + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Multi-turn Prompt sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.complexdialog.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.complexdialog.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/Application.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/Application.java new file mode 100644 index 000000000..6b500f091 --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/Application.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog dialog + ) { + return new DialogAndWelcome(conversationState, userState, dialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog(UserState userState) { + return new MainDialog(userState); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogAndWelcome.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogAndWelcome.java new file mode 100644 index 000000000..39d5badad --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogAndWelcome.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ChannelAccount; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; + +public class DialogAndWelcome extends DialogBot { + + public DialogAndWelcome( + ConversationState withConversationState, + UserState withUserState, + Dialog withDialog + ) { + super(withConversationState, withUserState, withDialog); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + Activity reply = MessageFactory.text("Welcome to Complex Dialog. " + + "This bot provides a complex conversation, with multiple dialogs. " + + "Type anything to get started."); + + return turnContext.sendActivity(reply); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogBot.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogBot.java new file mode 100644 index 000000000..fc7ed3512 --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This IBot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + Dialog withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/MainDialog.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/MainDialog.java new file mode 100644 index 000000000..db1a5ff8f --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/MainDialog.java @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +public class MainDialog extends ComponentDialog { + private UserState userState; + + public MainDialog(UserState withUserState) { + super("MainDialog"); + + userState = withUserState; + + addDialog(new TopLevelDialog("TopLevelDialog")); + + WaterfallStep[] waterfallSteps = { + this::initialStep, + this::finalStep + }; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture initialStep(WaterfallStepContext stepContext) { + return stepContext.beginDialog("TopLevelDialog"); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + UserProfile userInfo = (UserProfile) stepContext.getResult(); + + String status = String.format("You are signed up to review %s.", + userInfo.companiesToReview.size() == 0 + ? "no companies" + : String.join(",", userInfo.companiesToReview)); + + return stepContext.getContext().sendActivity(status) + .thenCompose(resourceResponse -> { + StatePropertyAccessor userProfileAccessor = userState.createProperty("UserProfile"); + return userProfileAccessor.set(stepContext.getContext(), userInfo); + }) + .thenCompose(setResult -> stepContext.endDialog()); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/ReviewSelectionDialog.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/ReviewSelectionDialog.java new file mode 100644 index 000000000..749b403da --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/ReviewSelectionDialog.java @@ -0,0 +1,99 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.prompts.ChoicePrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import org.apache.commons.lang3.StringUtils; + +public class ReviewSelectionDialog extends ComponentDialog { + // Define a "done" response for the company selection prompt. + private static final String DONE_OPTION = "done"; + + // Define value names for values tracked inside the dialogs. + private static final String COMPANIES_SELECTED = "value-companiesSelected"; + + private static List companiesOptions = Arrays.asList( + "Adatum Corporation", "Contoso Suites", "Graphic Design Institute", "Wide World Importers" + ); + + public ReviewSelectionDialog(String withId) { + super(withId); + + addDialog(new ChoicePrompt("ChoicePrompt")); + + WaterfallStep[] waterfallSteps = { + this::selectionStep, + this::loopStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture selectionStep(WaterfallStepContext stepContext) { + // Continue using the same selection list, if any, from the previous iteration of this dialog. + List list = stepContext.getOptions() instanceof List + ? (List) stepContext.getOptions() + : new ArrayList<>(); + stepContext.getValues().put(COMPANIES_SELECTED, list); + + String message; + if (list.size() == 0) { + message = String.format("Please choose a company to review, or `%s` to finish.", DONE_OPTION); + } else { + message = String.format("You have selected **%s**. You can review an additional company, or choose `%s` to finish.", + list.get(0), + DONE_OPTION); + } + + // Create the list of options to choose from. + List options = new ArrayList<>(companiesOptions); + options.add(DONE_OPTION); + if (list.size() > 0) { + options.remove(list.get(0)); + } + + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text(message)); + promptOptions.setRetryPrompt(MessageFactory.text("Please choose an option from the list.")); + promptOptions.setChoices(ChoiceFactory.toChoices(options)); + + // Prompt the user for a choice. + return stepContext.prompt("ChoicePrompt", promptOptions); + } + + private CompletableFuture loopStep(WaterfallStepContext stepContext) { + // Retrieve their selection list, the choice they made, and whether they chose to finish. + List list = (List) stepContext.getValues().get(COMPANIES_SELECTED); + FoundChoice choice = (FoundChoice) stepContext.getResult(); + boolean done = StringUtils.equals(choice.getValue(), DONE_OPTION); + + // If they chose a company, add it to the list. + if (!done) { + list.add(choice.getValue()); + } + + // If they're done, exit and return their list. + if (done || list.size() >= 2) { + return stepContext.endDialog(list); + } + + // Otherwise, repeat this dialog, passing in the list from this iteration. + return stepContext.replaceDialog(getId(), list); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/TopLevelDialog.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/TopLevelDialog.java new file mode 100644 index 000000000..0e8d03ca1 --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/TopLevelDialog.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class TopLevelDialog extends ComponentDialog { + // Define a "done" response for the company selection prompt. + private static final String DONE_OPTION = "done"; + + // Define value names for values tracked inside the dialogs. + private static final String USER_INFO = "value-userInfo"; + + public TopLevelDialog(String withId) { + super(withId); + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new NumberPrompt("NumberPrompt", Integer.class)); + + addDialog(new ReviewSelectionDialog("ReviewSelectionDialog")); + + WaterfallStep[] waterfallSteps = { + this::nameStep, + this::ageStep, + this::startSelectionStep, + this::acknowledgementStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture nameStep(WaterfallStepContext stepContext) { + // Create an object in which to collect the user's information within the dialog. + stepContext.getValues().put(USER_INFO, new UserProfile()); + + // Ask the user to enter their name. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please enter your name.")); + return stepContext.prompt("TextPrompt", promptOptions); + } + + private CompletableFuture ageStep(WaterfallStepContext stepContext) { + // Set the user's name to what they entered in response to the name prompt. + UserProfile userProfile = (UserProfile) stepContext.getValues().get(USER_INFO); + userProfile.name = (String) stepContext.getResult(); + + // Ask the user to enter their age. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(MessageFactory.text("Please enter your age.")); + return stepContext.prompt("NumberPrompt", promptOptions); + } + + private CompletableFuture startSelectionStep(WaterfallStepContext stepContext) { + // Set the user's age to what they entered in response to the age prompt. + UserProfile userProfile = (UserProfile) stepContext.getValues().get(USER_INFO); + userProfile.age = (Integer) stepContext.getResult(); + + // If they are too young, skip the review selection dialog, and pass an empty list to the next step. + if (userProfile.age < 25) { + return stepContext.getContext().sendActivity(MessageFactory.text("You must be 25 or older to participate.")) + .thenCompose(resourceResponse -> stepContext.next(new ArrayList())); + } + + // Otherwise, start the review selection dialog. + return stepContext.beginDialog("ReviewSelectionDialog"); + } + + private CompletableFuture acknowledgementStep(WaterfallStepContext stepContext) { + // Set the user's company selection to what they entered in the review-selection dialog. + UserProfile userProfile = (UserProfile) stepContext.getValues().get(USER_INFO); + userProfile.companiesToReview = stepContext.getResult() instanceof List + ? (List) stepContext.getResult() + : new ArrayList<>(); + + // Thank them for participating. + return stepContext.getContext() + .sendActivity(MessageFactory.text(String.format("Thanks for participating, %s.", userProfile.name))) + .thenCompose(resourceResponse -> stepContext.endDialog(stepContext.getValues().get(USER_INFO))); + } +} diff --git a/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/UserProfile.java b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/UserProfile.java new file mode 100644 index 000000000..6dbb95e90 --- /dev/null +++ b/samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/UserProfile.java @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import java.util.ArrayList; +import java.util.List; + +/** + * Contains information about a user. + */ +public class UserProfile { + public String name; + public Integer age; + + // The list of companies the user wants to review. + public List companiesToReview = new ArrayList<>(); +} diff --git a/samples/43.complex-dialog/src/main/resources/application.properties b/samples/43.complex-dialog/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/43.complex-dialog/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/43.complex-dialog/src/main/resources/log4j2.json b/samples/43.complex-dialog/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/43.complex-dialog/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/43.complex-dialog/src/main/webapp/META-INF/MANIFEST.MF b/samples/43.complex-dialog/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/43.complex-dialog/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/43.complex-dialog/src/main/webapp/WEB-INF/web.xml b/samples/43.complex-dialog/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/43.complex-dialog/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/43.complex-dialog/src/main/webapp/index.html b/samples/43.complex-dialog/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/43.complex-dialog/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/43.complex-dialog/src/test/java/com/microsoft/bot/sample/complexdialog/ApplicationTest.java b/samples/43.complex-dialog/src/test/java/com/microsoft/bot/sample/complexdialog/ApplicationTest.java new file mode 100644 index 000000000..6df04dbd5 --- /dev/null +++ b/samples/43.complex-dialog/src/test/java/com/microsoft/bot/sample/complexdialog/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.complexdialog; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From d224f55ea444cdc83cd28eb0f558b356537c5622 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Feb 2021 08:51:40 -0600 Subject: [PATCH 062/221] Refactored samples to use @Bean method for Bot instead of @Component. (#964) --- .../bot/sample/echo/Application.java | 35 ++++++++--- .../microsoft/bot/sample/echo/EchoBot.java | 2 - .../bot/sample/welcomeuser/Application.java | 37 ++++++++---- .../sample/welcomeuser/WelcomeUserBot.java | 2 - .../sample/multiturnprompt/Application.java | 58 +++++++++++++++---- .../bot/sample/multiturnprompt/DialogBot.java | 2 - .../multiturnprompt/UserProfileDialog.java | 2 - .../bot/sample/usingcards/Application.java | 58 +++++++++++++++---- .../bot/sample/usingcards/MainDialog.java | 2 - .../bot/sample/usingcards/RichCardsBot.java | 7 +-- .../sample/suggestedactions/Application.java | 36 ++++++++---- .../suggestedactions/SuggestedActionsBot.java | 2 - .../bot/sample/proactive/Application.java | 49 +++++++++++----- .../bot/sample/proactive/ProactiveBot.java | 7 ++- .../bot/sample/multilingual/Application.java | 35 +++++++---- .../sample/multilingual/MultiLingualBot.java | 1 - .../promptusersforinput/Application.java | 41 +++++++++---- .../promptusersforinput/CustomPromptBot.java | 1 - .../sample/statemanagement/Application.java | 41 +++++++++---- .../statemanagement/StateManagementBot.java | 1 - .../bot/sample/inspection/Application.java | 49 ++++++++++------ .../bot/sample/inspection/EchoBot.java | 1 - .../pom.xml | 2 +- .../bot/sample/teamssearch/Application.java | 42 ++++++++++---- .../TeamsMessagingExtensionsSearchBot.java | 2 - .../bot/sample/teamsaction/Application.java | 42 ++++++++++---- .../TeamsMessagingExtensionsActionBot.java | 2 - .../pom.xml | 4 +- .../sample/teamssearchauth/Application.java | 47 +++++++++++---- ...essagingExtensionsSearchAuthConfigBot.java | 2 - .../teamsactionpreview/Application.java | 42 ++++++++++---- ...msMessagingExtensionsActionPreviewBot.java | 2 - .../sample/teamstaskmodule/Application.java | 42 ++++++++++---- .../teamstaskmodule/TeamsTaskModuleBot.java | 6 +- .../bot/sample/teamsunfurl/Application.java | 42 ++++++++++---- .../sample/teamsunfurl/LinkUnfurlingBot.java | 2 - .../sample/teamsfileupload/Application.java | 42 ++++++++++---- .../teamsfileupload/TeamsFileUploadBot.java | 3 - .../sample/teamsconversation/Application.java | 36 ++++++++---- .../TeamsConversationBot.java | 2 - .../teamsstartnewthread/Application.java | 42 ++++++++++---- .../TeamsStartNewThreadBot.java | 2 - 42 files changed, 611 insertions(+), 264 deletions(-) diff --git a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java index dfdcb0731..cc7014c76 100644 --- a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java +++ b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.echo; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see EchoBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new EchoBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java index d6c5c2e0f..e34b01416 100644 --- a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java +++ b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java @@ -9,7 +9,6 @@ import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.schema.ChannelAccount; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -23,7 +22,6 @@ * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. *

*/ -@Component public class EchoBot extends ActivityHandler { @Override diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java index 6931438e5..78f74fe9d 100644 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java +++ b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java @@ -3,6 +3,8 @@ package com.microsoft.bot.sample.welcomeuser; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.UserState; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +12,46 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see WelcomeUserBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(UserState userState) { + return new WelcomeUserBot(userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java index ab9a14b4d..c0a1e94a6 100644 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java +++ b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java @@ -18,7 +18,6 @@ import com.microsoft.bot.schema.ResourceResponse; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.Collections; @@ -36,7 +35,6 @@ * * @see WelcomeUserState */ -@Component public class WelcomeUserBot extends ActivityHandler { // Messages sent to the user. private static final String WELCOMEMESSAGE = diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java index 1324a2348..32ac57561 100644 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java @@ -3,6 +3,10 @@ package com.microsoft.bot.sample.multiturnprompt; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +14,65 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see DialogBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog dialog + ) { + return new DialogBot(conversationState, userState, dialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog(UserState userState) { + return new UserProfileDialog(userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java index d71d53ed6..ddcafee17 100644 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java @@ -10,7 +10,6 @@ import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.UserState; import java.util.concurrent.CompletableFuture; -import org.springframework.stereotype.Component; /** * This IBot implementation can run any type of Dialog. The use of type parameterization is to @@ -21,7 +20,6 @@ * been used in a Dialog implementation, and the requirement is that all BotState objects are * saved at the end of a turn. */ -@Component public class DialogBot extends ActivityHandler { protected Dialog dialog; protected BotState conversationState; diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java index 302fe5d3a..ade706bd8 100644 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java +++ b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java @@ -27,9 +27,7 @@ import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; -@Component public class UserProfileDialog extends ComponentDialog { private StatePropertyAccessor userProfileAccessor; diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java index 5c08c5a98..9db23c06f 100644 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java @@ -3,6 +3,10 @@ package com.microsoft.bot.sample.usingcards; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +14,65 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see RichCardsBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog rootDialog + ) { + return new RichCardsBot(conversationState, userState, rootDialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog() { + return new MainDialog(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java index 7507bc568..c6510175f 100644 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java @@ -20,9 +20,7 @@ import java.util.Arrays; import java.util.List; import java.util.concurrent.CompletableFuture; -import org.springframework.stereotype.Component; -@Component public class MainDialog extends ComponentDialog { public MainDialog() { super("MainDialog"); diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java index aede87551..70235e3d1 100644 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java @@ -8,22 +8,21 @@ import com.microsoft.bot.builder.MessageFactory; import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ChannelAccount; import java.util.List; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; // RichCardsBot prompts a user to select a Rich Card and then returns the card // that matches the user's selection. -@Component -public class RichCardsBot extends DialogBot { +public class RichCardsBot extends DialogBot { public RichCardsBot( ConversationState withConversationState, UserState withUserState, - MainDialog withDialog + Dialog withDialog ) { super(withConversationState, withUserState, withDialog); } diff --git a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java index 36190e2bb..08514f8b3 100644 --- a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java +++ b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.suggestedactions; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +11,46 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see SuggestedActionsBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new SuggestedActionsBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java index 17f6556c1..5fb06fcd8 100644 --- a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java +++ b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java @@ -13,7 +13,6 @@ import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.SuggestedActions; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import java.util.Arrays; import java.util.List; @@ -30,7 +29,6 @@ * conversation participants. *

*/ -@Component public class SuggestedActionsBot extends ActivityHandler { public static final String WELCOMETEXT = "This bot will introduce you to suggestedActions." + " Please answer the question:"; diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java index 5a115736f..fead67fbd 100644 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java +++ b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.proactive; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -13,38 +14,43 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see ProactiveBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. +// +// See NotifyController in this project for an example on adding a controller. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } /** - * Returns a custom Adapter that provides error handling. + * Returns the Bot for this application. * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); + @Bean + public Bot getBot(ConversationReferences conversationReferences) { + return new ProactiveBot(conversationReferences); } /** @@ -57,4 +63,15 @@ public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configur public ConversationReferences getConversationReferences() { return new ConversationReferences(); } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } } diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java index 033f79d35..732bee213 100644 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java +++ b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java @@ -10,8 +10,8 @@ import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.ConversationReference; +import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Component; import java.util.List; import java.util.concurrent.CompletableFuture; @@ -28,7 +28,6 @@ * conversation participants with instructions for sending a proactive message. *

*/ -@Component public class ProactiveBot extends ActivityHandler { @Value("${server.port:3978}") private int port; @@ -57,6 +56,10 @@ protected CompletableFuture onMembersAdded( TurnContext turnContext ) { return membersAdded.stream() + .filter( + member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) + ) .map( channel -> turnContext .sendActivity(MessageFactory.text(String.format(WELCOMEMESSAGE, port))) diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java index c1052cd4b..dafe6ca93 100644 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.multilingual; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.Storage; import com.microsoft.bot.builder.UserState; @@ -19,29 +20,43 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see MultiLingualBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(UserState userState) { + return new MultiLingualBot(userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java index d549c3f91..39ac8e1b2 100644 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java @@ -36,7 +36,6 @@ * More information can be found * here https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-info-overview. */ -@Component public class MultiLingualBot extends ActivityHandler { private static final String WELCOME_TEXT = new StringBuilder("This bot will introduce you to translation middleware. ") diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java index bd12d9ab2..bccd07d93 100644 --- a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java @@ -3,6 +3,9 @@ package com.microsoft.bot.sample.promptusersforinput; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +13,49 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see DialogBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState + ) { + return new CustomPromptBot(conversationState, userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java index 08320dce6..d761a8e4c 100644 --- a/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java +++ b/samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java @@ -32,7 +32,6 @@ * endpoints within the same project. This can be achieved by defining distinct * Controller types each with dependency on distinct Bot types. */ -@Component public class CustomPromptBot extends ActivityHandler { private final BotState userState; diff --git a/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/Application.java b/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/Application.java index 73059f165..dfcb90cd9 100644 --- a/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/Application.java +++ b/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/Application.java @@ -3,6 +3,9 @@ package com.microsoft.bot.sample.statemanagement; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +13,49 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see StateManagementBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState + ) { + return new StateManagementBot(conversationState, userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/StateManagementBot.java b/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/StateManagementBot.java index d4a8b1e7b..2c0737fa2 100644 --- a/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/StateManagementBot.java +++ b/samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/StateManagementBot.java @@ -34,7 +34,6 @@ * @see ConversationData * @see UserProfile */ -@Component public class StateManagementBot extends ActivityHandler { private ConversationState conversationState; private UserState userState; diff --git a/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/Application.java b/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/Application.java index d29cbbcf5..5165c0086 100644 --- a/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/Application.java +++ b/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.inspection; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.Storage; import com.microsoft.bot.builder.UserState; @@ -18,36 +19,50 @@ import org.springframework.context.annotation.Import; import org.springframework.context.annotation.Primary; -/** - * This is the starting point of the Sprint Boot Bot application. - * - *

- * This class could provide overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - *

- * - *

- * See README.md for details on using the InspectionMiddleware. - *

- * - * @see BotDependencyConfiguration - * @see EchoBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + * + *

+ * See README.md for details on using the InspectionMiddleware. + *

+ */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState + ) { + return new EchoBot(conversationState, userState); + } + /** * Create an adapter with InspectionMiddleware. * diff --git a/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/EchoBot.java b/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/EchoBot.java index e56969338..e6a9f5cfc 100644 --- a/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/EchoBot.java +++ b/samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/EchoBot.java @@ -26,7 +26,6 @@ * See README.md for details on using the InspectionMiddleware. *

*/ -@Component public class EchoBot extends ActivityHandler { private ConversationState conversationState; private UserState userState; diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index a414d66b6..7a36d7f24 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -75,7 +75,7 @@ org.json json - 20190722 + 20201115 org.apache.logging.log4j diff --git a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/Application.java b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/Application.java index c10fc2f92..e71c04735 100644 --- a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/Application.java +++ b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamssearch; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,29 +11,46 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the - * {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsMessagingExtensionsSearchBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new TeamsMessagingExtensionsSearchBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java index f5efe9ad3..3d489f3a8 100644 --- a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java +++ b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java @@ -15,7 +15,6 @@ import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import java.io.IOException; import java.util.*; @@ -28,7 +27,6 @@ * added. This sample illustrates how to build a Search-based Messaging Extension.

*/ -@Component public class TeamsMessagingExtensionsSearchBot extends TeamsActivityHandler { @Override diff --git a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java index c49310477..c8e160336 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java +++ b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsaction; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,30 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsMessagingExtensionsActionBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new TeamsMessagingExtensionsActionBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java index 867fb466e..b9ed343a5 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java +++ b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java @@ -8,7 +8,6 @@ import com.microsoft.bot.schema.CardImage; import com.microsoft.bot.schema.HeroCard; import com.microsoft.bot.schema.teams.*; -import org.springframework.stereotype.Component; import java.util.*; import java.util.concurrent.CompletableFuture; @@ -20,7 +19,6 @@ * added. There are two basic types of Messaging Extension in Teams: Search-based and Action-based. * This sample illustrates how to build an Action-based Messaging Extension.

*/ -@Component public class TeamsMessagingExtensionsActionBot extends TeamsActivityHandler { @Override protected CompletableFuture onTeamsMessagingExtensionSubmitAction( diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index 6ed6f2e37..16b8d7f3c 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -48,7 +48,7 @@ com.microsoft.graph microsoft-graph - 1.7.1 + 2.6.0 junit @@ -92,7 +92,7 @@ org.json json - 20190722 + 20201115 compile diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/Application.java b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/Application.java index d3bd10099..b8435de1e 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/Application.java +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/Application.java @@ -3,6 +3,9 @@ package com.microsoft.bot.sample.teamssearchauth; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,29 +13,49 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that extends the - * {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsConversationBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + UserState userState + ) { + return new TeamsMessagingExtensionsSearchAuthConfigBot(configuration, userState); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java index 13db8ab3e..956c4f9ad 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java @@ -21,7 +21,6 @@ import org.json.JSONArray; import org.json.JSONObject; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; @@ -42,7 +41,6 @@ * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. *

*/ -@Component public class TeamsMessagingExtensionsSearchAuthConfigBot extends TeamsActivityHandler { private String siteUrl; diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/Application.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/Application.java index e36a7a320..bdf7287e5 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/Application.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsactionpreview; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,29 +11,46 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that extends the - * {@link com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsMessagingExtensionsActionPreviewBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new TeamsMessagingExtensionsActionPreviewBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java index 9330b04d3..f8d28359c 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java @@ -16,7 +16,6 @@ import com.microsoft.bot.schema.teams.*; import org.apache.commons.io.IOUtils; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import java.io.IOException; import java.io.InputStream; @@ -34,7 +33,6 @@ * parameters entered by the user from a Task Module. *

*/ -@Component public class TeamsMessagingExtensionsActionPreviewBot extends TeamsActivityHandler { @Override diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/Application.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/Application.java index 3994a52ce..905caedd8 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/Application.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamstaskmodule; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,30 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsTaskModuleBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Configuration configuration) { + return new TeamsTaskModuleBot(configuration); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java index 9cb519aba..6d3065b36 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java @@ -28,7 +28,6 @@ import java.util.concurrent.CompletionException; import java.util.stream.Collectors; import org.apache.commons.io.IOUtils; -import org.springframework.stereotype.Component; import java.io.InputStream; import java.nio.charset.StandardCharsets; @@ -44,7 +43,6 @@ * user. The {@link #onMembersAdded(List, TurnContext)} will send a greeting to new conversation * participants.

*/ -@Component public class TeamsTaskModuleBot extends TeamsActivityHandler { private final String baseUrl; private final List actions = Arrays.asList( @@ -79,7 +77,7 @@ protected CompletableFuture onTeamsTaskModuleFetch( ) { // Called when the user selects an options from the displayed HeroCard or // AdaptiveCard. The result is the action to perform. - return Async.tryCompletion(() -> { + return Async.wrapBlock(() -> { CardTaskFetchValue value = Serialization .safeGetAs(taskModuleRequest.getData(), CardTaskFetchValue.class); @@ -126,7 +124,7 @@ protected CompletableFuture onTeamsTaskModuleSubmit( TaskModuleRequest taskModuleRequest ) { // Called when data is being returned from the selected option (see `onTeamsTaskModuleFetch'). - return Async.tryCompletion(() -> + return Async.wrapBlock(() -> // Echo the users input back. In a production bot, this is where you'd add behavior in // response to the input. MessageFactory.text( diff --git a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/Application.java b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/Application.java index 889cf95cb..1d2166b03 100644 --- a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/Application.java +++ b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsunfurl; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,30 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see LinkUnfurlingBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new LinkUnfurlingBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java index fcd9ea09a..969008cb0 100644 --- a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java +++ b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java @@ -14,7 +14,6 @@ import com.microsoft.bot.schema.teams.MessagingExtensionResponse; import com.microsoft.bot.schema.teams.MessagingExtensionResult; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import java.util.Collections; import java.util.concurrent.CompletableFuture; @@ -25,7 +24,6 @@ *

This is where application specific logic for interacting with the users would be * added.

*/ -@Component public class LinkUnfurlingBot extends TeamsActivityHandler { @Override diff --git a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/Application.java b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/Application.java index 0ab16e191..a39f0326a 100644 --- a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/Application.java +++ b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsfileupload; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,30 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsFileUploadBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new TeamsFileUploadBot(); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java index fb5a2f444..0d602b8c6 100644 --- a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java +++ b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java @@ -16,7 +16,6 @@ import com.microsoft.bot.schema.teams.FileDownloadInfo; import com.microsoft.bot.schema.teams.FileInfoCard; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import java.io.File; import java.io.FileInputStream; @@ -30,7 +29,6 @@ import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicReference; /** @@ -41,7 +39,6 @@ * user. The {@link #onMembersAdded(List, TurnContext)} will send a greeting to new conversation * participants.

*/ -@Component public class TeamsFileUploadBot extends TeamsActivityHandler { @Override diff --git a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/Application.java b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/Application.java index 79eb28415..bace23165 100644 --- a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/Application.java +++ b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsconversation; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +11,46 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see TeamsConversationBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Configuration configuration) { + return new TeamsConversationBot(configuration); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java index fa507b73a..b4f89b657 100644 --- a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java +++ b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java @@ -21,7 +21,6 @@ import com.microsoft.bot.schema.teams.TeamInfo; import com.microsoft.bot.schema.teams.TeamsChannelAccount; import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; import java.net.URLEncoder; import java.util.ArrayList; @@ -41,7 +40,6 @@ * will send a greeting to new conversation participants. *

*/ -@Component public class TeamsConversationBot extends TeamsActivityHandler { private String appId; private String appPassword; diff --git a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/Application.java b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/Application.java index 6415a327d..cafc946a3 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/Application.java +++ b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/Application.java @@ -3,6 +3,7 @@ package com.microsoft.bot.sample.teamsstartnewthread; +import com.microsoft.bot.builder.Bot; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,30 +11,47 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - *

- * This class also provides overrides for dependency injections. A class that extends the {@link - * com.microsoft.bot.builder.Bot} interface should be annotated with @Component. - * - * @see TeamsStartNewThreadBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication -// Use the default BotController to receive incoming Channel messages. A custom controller -// could be used by eliminating this import and creating a new RestController. The default -// controller is created by the Spring Boot container using dependency injection. The -// default route is /api/messages. +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Configuration configuration) { + return new TeamsStartNewThreadBot(configuration); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java index ef2de3748..ec2e0d8ae 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java +++ b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java @@ -14,7 +14,6 @@ import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ConversationParameters; import com.microsoft.bot.schema.ConversationReference; -import org.springframework.stereotype.Component; import java.util.concurrent.CompletableFuture; @@ -25,7 +24,6 @@ * added. For this sample, the {@link #onMessageActivity(TurnContext)} creates a thread in a Teams channel. *

*/ -@Component public class TeamsStartNewThreadBot extends TeamsActivityHandler { private String appId; From f24b0b914c25a82a2ad72a6aca7dd114fe058998 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Feb 2021 14:24:47 -0600 Subject: [PATCH 063/221] Teams Adaptive Card Tabs (#963) * Teams Adaptive Cards model classes * Teams Adaptive Cars, TeamsActivityHandler Co-authored-by: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> --- .../builder/teams/TeamsActivityHandler.java | 52 +++++++++++- .../bot/schema/teams/TabContext.java | 45 ++++++++++ .../bot/schema/teams/TabEntityContext.java | 45 ++++++++++ .../bot/schema/teams/TabRequest.java | 75 +++++++++++++++++ .../bot/schema/teams/TabResponse.java | 37 +++++++++ .../bot/schema/teams/TabResponseCard.java | 37 +++++++++ .../bot/schema/teams/TabResponseCards.java | 38 +++++++++ .../bot/schema/teams/TabResponsePayload.java | 83 +++++++++++++++++++ .../microsoft/bot/schema/teams/TabSubmit.java | 75 +++++++++++++++++ .../bot/schema/teams/TabSubmitData.java | 73 ++++++++++++++++ .../bot/schema/teams/TabSuggestedActions.java | 48 +++++++++++ .../bot/schema/teams/TaskModuleRequest.java | 19 +++++ 12 files changed, 623 insertions(+), 4 deletions(-) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabContext.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabEntityContext.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabRequest.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponse.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCard.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCards.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponsePayload.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmit.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmitData.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSuggestedActions.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java index 4d113b82d..4831fffe6 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/teams/TeamsActivityHandler.java @@ -23,6 +23,9 @@ import com.microsoft.bot.schema.teams.MessagingExtensionQuery; import com.microsoft.bot.schema.teams.MessagingExtensionResponse; import com.microsoft.bot.schema.teams.O365ConnectorCardActionQuery; +import com.microsoft.bot.schema.teams.TabRequest; +import com.microsoft.bot.schema.teams.TabResponse; +import com.microsoft.bot.schema.teams.TabSubmit; import com.microsoft.bot.schema.teams.TaskModuleRequest; import com.microsoft.bot.schema.teams.TaskModuleResponse; import com.microsoft.bot.schema.teams.TeamInfo; @@ -42,7 +45,7 @@ * Pre and post processing of Activities can be plugged in by deriving and * calling the base class implementation. */ -@SuppressWarnings({ "checkstyle:JavadocMethod", "checkstyle:DesignForExtension" }) +@SuppressWarnings({ "checkstyle:JavadocMethod", "checkstyle:DesignForExtension", "checkstyle:MethodLength" }) public class TeamsActivityHandler extends ActivityHandler { /** * Invoked when an invoke activity is received from the connector when the base @@ -174,6 +177,26 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon ).thenApply(ActivityHandler::createInvokeResponse); break; + case "tab/fetch": + result = onTeamsTabFetch( + turnContext, + Serialization.safeGetAs( + turnContext.getActivity().getValue(), + TabRequest.class + ) + ).thenApply(ActivityHandler::createInvokeResponse); + break; + + case "tab/submit": + result = onTeamsTabSubmit( + turnContext, + Serialization.safeGetAs( + turnContext.getActivity().getValue(), + TabSubmit.class + ) + ).thenApply(ActivityHandler::createInvokeResponse); + break; + default: result = super.onInvokeActivity(turnContext); break; @@ -484,7 +507,8 @@ protected CompletableFuture onTeamsTaskModuleFetch( } /** - * Override this in a derived class to provide logic for when a card button is clicked in a messaging extension. + * Override this in a derived class to provide logic for when a card button + * is clicked in a messaging extension. * * @param turnContext The current TurnContext. * @param cardData Object representing the card data. @@ -676,8 +700,8 @@ protected CompletableFuture onTeamsMembersAddedDispatch( } if (teamsChannelAccount == null) { - // unable to find the member added in ConversationUpdate Activity in the response from the - // getMember call + // unable to find the member added in ConversationUpdate Activity in + // the response from the getMember call teamsChannelAccount = new TeamsChannelAccount(); teamsChannelAccount.setId(memberAdded.getId()); teamsChannelAccount.setName(memberAdded.getName()); @@ -919,6 +943,26 @@ protected CompletableFuture onTeamsTeamUnarchived( return CompletableFuture.completedFuture(null); } + /** + * Override this in a derived class to provide logic for when a tab is fetched. + * @param turnContext The context object for this turn. + * @param tabRequest The tab invoke request value payload. + * @return A Tab Response for the request. + */ + protected CompletableFuture onTeamsTabFetch(TurnContext turnContext, TabRequest tabRequest) { + return withException(new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED)); + } + + /** + * Override this in a derived class to provide logic for when a tab is submitted. + * @param turnContext The context object for this turn. + * @param tabSubmit The tab submit invoke request value payload. + * @return A Tab Response for the request. + */ + protected CompletableFuture onTeamsTabSubmit(TurnContext turnContext, TabSubmit tabSubmit) { + return withException(new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED)); + } + /** * Invoke a new InvokeResponseException with a HTTP 501 code status. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabContext.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabContext.java new file mode 100644 index 000000000..bcafe7d21 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabContext.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Current tab request context, i.e., the current theme. + */ +public class TabContext { + @JsonProperty(value = "theme") + private String theme; + + /** + * Initializes a new instance of the class. + */ + public TabContext() { + + } + + /** + * Initializes a new instance of the class. + * @param withTheme The current user's theme. + */ + public TabContext(String withTheme) { + theme = withTheme; + } + + /** + * Gets the current user's theme. + * @return The current user's theme. + */ + public String getTheme() { + return theme; + } + + /** + * Sets the current user's theme. + * @param withTheme The current user's theme. + */ + public void setTheme(String withTheme) { + theme = withTheme; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabEntityContext.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabEntityContext.java new file mode 100644 index 000000000..04f6dc5ec --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabEntityContext.java @@ -0,0 +1,45 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Current TabRequest entity context, or 'tabEntityId'. + */ +public class TabEntityContext { + @JsonProperty(value = "tabEntityId") + private String tabEntityId; + + /** + * Initializes a new instance of the class. + */ + public TabEntityContext() { + + } + + /** + * Initializes a new instance of the class. + * @param withTabEntityId The entity id of the tab. + */ + public TabEntityContext(String withTabEntityId) { + tabEntityId = withTabEntityId; + } + + /** + * Gets the entity id of the tab. + * @return The entity id of the tab. + */ + public String getTabEntityId() { + return tabEntityId; + } + + /** + * Sets the entity id of the tab. + * @param withTabEntityId The entity id of the tab. + */ + public void setTabEntityId(String withTabEntityId) { + tabEntityId = withTabEntityId; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabRequest.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabRequest.java new file mode 100644 index 000000000..25edda7e5 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabRequest.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Invoke ('tab/fetch') request value payload. + */ +public class TabRequest { + @JsonProperty(value = "tabContext") + private TabEntityContext tabContext; + + @JsonProperty(value = "context") + private TabContext context; + + @JsonProperty(value = "state") + private String state; + + /** + * Initializes a new instance of the class. + */ + public TabRequest() { + + } + + /** + * Gets current tab entity request context. + * @return Tab context + */ + public TabEntityContext getTabContext() { + return tabContext; + } + + /** + * Sets current tab entity request context. + * @param withTabContext Tab context + */ + public void setTabContext(TabEntityContext withTabContext) { + tabContext = withTabContext; + } + + /** + * Gets current user context, i.e., the current theme. + * @return Current user context, i.e., the current theme. + */ + public TabContext getContext() { + return context; + } + + /** + * Sets current user context, i.e., the current theme. + * @param withContext Current user context, i.e., the current theme. + */ + public void setContext(TabContext withContext) { + context = withContext; + } + + /** + * Gets state, which is the magic code for OAuth Flow. + * @return State, which is the magic code for OAuth Flow. + */ + public String getState() { + return state; + } + + /** + * Sets state, which is the magic code for OAuth Flow. + * @param withState State, which is the magic code for OAuth Flow. + */ + public void setState(String withState) { + state = withState; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponse.java new file mode 100644 index 000000000..a8f97d1f4 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponse.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Envelope for Card Tab Response Payload. + */ +public class TabResponse { + @JsonProperty(value = "tab") + private TabResponsePayload tab; + + /** + * Initializes a new instance of the class. + */ + public TabResponse() { + + } + + /** + * Gets the response to the tab/fetch message. + * @return Cards in response to a TabRequest. + */ + public TabResponsePayload getTab() { + return tab; + } + + /** + * Sets the response to the tab/fetch message. + * @param withTab Cards in response to a TabRequest. + */ + public void setTab(TabResponsePayload withTab) { + tab = withTab; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCard.java new file mode 100644 index 000000000..277ae99a6 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCard.java @@ -0,0 +1,37 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Envelope for cards for a Tab request. + */ +public class TabResponseCard { + @JsonProperty(value = "card") + private Object card; + + /** + * Initializes a new instance of the class. + */ + public TabResponseCard() { + + } + + /** + * Gets adaptive card for this card tab response. + * @return Cards for this TabResponse. + */ + public Object getCard() { + return card; + } + + /** + * Sets adaptive card for this card tab response. + * @param withCard Cards for this TabResponse. + */ + public void setCard(Object withCard) { + card = withCard; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCards.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCards.java new file mode 100644 index 000000000..c36a63c9f --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponseCards.java @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Envelope for cards for a TabResponse. + */ +public class TabResponseCards { + @JsonProperty(value = "cards") + private List cards; + + /** + * Initializes a new instance of the class. + */ + public TabResponseCards() { + + } + + /** + * Gets adaptive cards for this card tab response. + * @return Cards for the TabResponse. + */ + public List getCards() { + return cards; + } + + /** + * Sets adaptive cards for this card tab response. + * @param withCards Cards for the TabResponse. + */ + public void setCards(List withCards) { + cards = withCards; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponsePayload.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponsePayload.java new file mode 100644 index 000000000..3817f9555 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabResponsePayload.java @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Payload for Tab Response. + */ +public class TabResponsePayload { + @JsonProperty(value = "type") + private String type; + + @JsonProperty(value = "value") + private TabResponseCards value; + + @JsonProperty(value = "suggestedActions") + private TabSuggestedActions suggestedActions; + + /** + * Initializes a new instance of the class. + */ + public TabResponsePayload() { + + } + + /** + * Gets choice of action options when responding to the tab/fetch message. + * Possible values include: 'continue', 'auth' or 'silentAuth'. + * + * @return One of either: 'continue', 'auth' or 'silentAuth'. + */ + public String getType() { + return type; + } + + /** + * Sets choice of action options when responding to the tab/fetch message. + * Possible values include: 'continue', 'auth' or 'silentAuth'. + + * @param withType One of either: 'continue', 'auth' or 'silentAuth'. + */ + public void setType(String withType) { + type = withType; + } + + /** + * Gets or sets the TabResponseCards when responding to + * tab/fetch activity with type of 'continue'. + * + * @return Cards in response to a TabResponseCards. + */ + public TabResponseCards getValue() { + return value; + } + + /** + * Sets or sets the TabResponseCards when responding to + * tab/fetch activity with type of 'continue'. + * + * @param withValue Cards in response to a TabResponseCards. + */ + public void setValue(TabResponseCards withValue) { + value = withValue; + } + + /** + * Gets the Suggested Actions for this card tab. + * @return The Suggested Actions for this card tab. + */ + public TabSuggestedActions getSuggestedActions() { + return suggestedActions; + } + + /** + * Sets the Suggested Actions for this card tab. + * @param withSuggestedActions The Suggested Actions for this card tab. + */ + public void setSuggestedActions(TabSuggestedActions withSuggestedActions) { + suggestedActions = withSuggestedActions; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmit.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmit.java new file mode 100644 index 000000000..5f69a5757 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmit.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Invoke ('tab/submit') request value payload. + */ +public class TabSubmit { + @JsonProperty(value = "tabContext") + private TabEntityContext tabEntityContext; + + @JsonProperty(value = "context") + private TabContext context; + + @JsonProperty(value = "data") + private TabSubmitData data; + + /** + * Initializes a new instance of the class. + */ + public TabSubmit() { + + } + + /** + * Gets current tab entity request context. + * @return Tab context for the TabSubmit. + */ + public TabEntityContext getTabEntityContext() { + return tabEntityContext; + } + + /** + * Sets current tab entity request context. + * @param withTabEntityContext Tab context for the TabSubmit. + */ + public void setTabEntityContext(TabEntityContext withTabEntityContext) { + tabEntityContext = withTabEntityContext; + } + + /** + * Gets current user context, i.e., the current theme. + * @return Current user context, i.e., the current theme. + */ + public TabContext getContext() { + return context; + } + + /** + * Sets current user context, i.e., the current theme. + * @param withContext Current user context, i.e., the current theme. + */ + public void setContext(TabContext withContext) { + context = withContext; + } + + /** + * Gets user input data. Free payload containing properties of key-value pairs. + * @return User input data. Free payload containing properties of key-value pairs. + */ + public TabSubmitData getData() { + return data; + } + + /** + * Sets user input data. Free payload containing properties of key-value pairs. + * @param withData User input data. Free payload containing properties of key-value pairs. + */ + public void setData(TabSubmitData withData) { + data = withData; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmitData.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmitData.java new file mode 100644 index 000000000..3f17c6376 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSubmitData.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import java.util.HashMap; +import java.util.Map; + +/** + * Invoke ('tab/submit') request value payload data. + */ +public class TabSubmitData { + @JsonProperty(value = "type") + private String type; + + /** + * Holds the overflow properties that aren't first class properties in the + * object. This allows extensibility while maintaining the object. + */ + private HashMap properties = new HashMap<>(); + + /** + * Initializes a new instance of the class. + */ + public TabSubmitData() { + + } + + /** + * Gets the type for this TabSubmitData. + * + * @return Currently, 'tab/submit'. + */ + public String getType() { + return type; + } + + /** + * Sets the type for this TabSubmitData. + + * @param withType Currently, 'tab/submit'. + */ + public void setType(String withType) { + type = withType; + } + + /** + * Holds the overflow properties that aren't first class properties in the + * object. This allows extensibility while maintaining the object. + * + * @return Map of additional properties. + */ + @JsonAnyGetter + public Map getProperties() { + return this.properties; + } + + /** + * Holds the overflow properties that aren't first class properties in the + * object. This allows extensibility while maintaining the object. + * + * @param key The key of the property to set. + * @param withValue The value for the property. + */ + @JsonAnySetter + public void setProperties(String key, JsonNode withValue) { + this.properties.put(key, withValue); + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSuggestedActions.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSuggestedActions.java new file mode 100644 index 000000000..de14ac5f7 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TabSuggestedActions.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema.teams; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.schema.CardAction; +import java.util.Arrays; +import java.util.List; + +/** + * Tab SuggestedActions (Only when type is 'auth' or 'silentAuth'). + */ +public class TabSuggestedActions { + @JsonProperty(value = "actions") + private List actions; + + /** + * Initializes a new instance of the class. + */ + public TabSuggestedActions() { + + } + + /** + * Gets actions for a card tab response. + * @return Actions for this TabSuggestedActions. + */ + public List getActions() { + return actions; + } + + /** + * Sets actions for a card tab response. + * @param withActions Actions for this TabSuggestedActions. + */ + public void setActions(List withActions) { + actions = withActions; + } + + /** + * Sets actions for a card tab response. + * @param withActions Actions for this TabSuggestedActions. + */ + public void setActions(CardAction... withActions) { + actions = Arrays.asList(withActions); + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleRequest.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleRequest.java index d2dd5d779..c4daaf9e3 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleRequest.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/teams/TaskModuleRequest.java @@ -15,6 +15,9 @@ public class TaskModuleRequest { @JsonProperty(value = "context") private TaskModuleRequestContext context; + @JsonProperty(value = "tabContext") + private TabContext tabContext; + /** * Gets user input data. Free payload with key-value pairs. * @@ -50,4 +53,20 @@ public TaskModuleRequestContext getContext() { public void setContext(TaskModuleRequestContext withContext) { context = withContext; } + + /** + * Gets current tab request context. + * @return Tab request context. + */ + public TabContext getTabContext() { + return tabContext; + } + + /** + * Sets current tab request context. + * @param withTabContext Tab request context. + */ + public void setTabContext(TabContext withTabContext) { + tabContext = withTabContext; + } } From d133a1e27051f327e03278b8af44b3feed3a2566 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 10 Feb 2021 14:36:25 -0600 Subject: [PATCH 064/221] Lparrish/o auth prompt (#967) * Initial Port of OAuthPrompt * Unit tests started * Unit Tests * Completed Unit Tests * Fix merge issue. * Complete Auth Sample and fixes to AuthDialog Co-authored-by: tracyboehrer --- .../bot/builder/BotFrameworkAdapter.java | 1135 ++++++++++------- .../bot/builder/ConnectorClientBuilder.java | 26 + .../bot/builder/TurnStateConstants.java | 34 + .../bot/builder/UserTokenProvider.java | 240 +++- .../bot/builder/TestAdapterTests.java | 9 +- .../bot/builder/adapters/TestAdapter.java | 475 ++++--- .../microsoft/bot/connector/BotSignIn.java | 25 + .../microsoft/bot/connector/UserToken.java | 17 + .../bot/connector/rest/RestBotSignIn.java | 100 +- .../bot/connector/rest/RestUserToken.java | 77 ++ .../bot/dialogs/prompts/OAuthPrompt.java | 761 +++++++++++ .../dialogs/prompts/OAuthPromptSettings.java | 145 +++ .../microsoft/bot/dialogs/prompts/Prompt.java | 10 +- .../bot/dialogs/prompts/OAuthPromptTests.java | 751 +++++++++++ .../com/microsoft/bot/schema/OAuthCard.java | 23 +- .../microsoft/bot/schema/SignInResource.java | 77 ++ .../schema/TokenExchangeInvokeRequest.java | 70 + .../schema/TokenExchangeInvokeResponse.java | 74 ++ .../bot/schema/TokenExchangeRequest.java | 85 ++ .../bot/schema/TokenExchangeResource.java | 103 ++ .../bot/schema/TokenExchangeState.java | 6 +- .../microsoft/bot/schema/TokenResponse.java | 24 +- samples/18.bot-authentication/LICENSE | 21 + samples/18.bot-authentication/README.md | 94 ++ .../template-with-new-rg.json | 291 +++++ .../template-with-preexisting-rg.json | 259 ++++ samples/18.bot-authentication/pom.xml | 244 ++++ .../sample/authentication/Application.java | 48 + .../bot/sample/authentication/AuthBot.java | 55 + .../bot/sample/authentication/DialogBot.java | 53 + .../sample/authentication/LogoutDialog.java | 69 + .../bot/sample/authentication/MainDialog.java | 102 ++ .../src/main/resources/application.properties | 4 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++ .../authentication/ApplicationTest.java | 19 + 38 files changed, 5304 insertions(+), 673 deletions(-) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConnectorClientBuilder.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnStateConstants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPromptSettings.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInResource.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeRequest.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeResponse.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeRequest.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeResource.java create mode 100644 samples/18.bot-authentication/LICENSE create mode 100644 samples/18.bot-authentication/README.md create mode 100644 samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/18.bot-authentication/pom.xml create mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java create mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java create mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java create mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java create mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java create mode 100644 samples/18.bot-authentication/src/main/resources/application.properties create mode 100644 samples/18.bot-authentication/src/main/resources/log4j2.json create mode 100644 samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/18.bot-authentication/src/main/webapp/index.html create mode 100644 samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index a587516c5..54ba59213 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -41,6 +41,8 @@ import com.microsoft.bot.schema.ExpectedReplies; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.Serialization; +import com.microsoft.bot.schema.SignInResource; +import com.microsoft.bot.schema.TokenExchangeRequest; import com.microsoft.bot.schema.TokenExchangeState; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; @@ -78,8 +80,10 @@ *

* {@link TurnContext} {@link Activity} {@link Bot} {@link Middleware} */ -public class BotFrameworkAdapter extends BotAdapter - implements AdapterIntegration, UserTokenProvider { +public class BotFrameworkAdapter extends BotAdapter implements + AdapterIntegration, + UserTokenProvider, + ConnectorClientBuilder { /** * Key to store InvokeResponse. */ @@ -151,19 +155,10 @@ public BotFrameworkAdapter(CredentialProvider withCredentialProvider) { * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter( - CredentialProvider withCredentialProvider, - ChannelProvider withChannelProvider, - RetryStrategy withRetryStrategy, - Middleware withMiddleware - ) { - this( - withCredentialProvider, - new AuthenticationConfiguration(), - withChannelProvider, - withRetryStrategy, - withMiddleware - ); + public BotFrameworkAdapter(CredentialProvider withCredentialProvider, ChannelProvider withChannelProvider, + RetryStrategy withRetryStrategy, Middleware withMiddleware) { + this(withCredentialProvider, new AuthenticationConfiguration(), withChannelProvider, withRetryStrategy, + withMiddleware); } /** @@ -176,13 +171,8 @@ public BotFrameworkAdapter( * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter( - CredentialProvider withCredentialProvider, - AuthenticationConfiguration withAuthConfig, - ChannelProvider withChannelProvider, - RetryStrategy withRetryStrategy, - Middleware withMiddleware - ) { + public BotFrameworkAdapter(CredentialProvider withCredentialProvider, AuthenticationConfiguration withAuthConfig, + ChannelProvider withChannelProvider, RetryStrategy withRetryStrategy, Middleware withMiddleware) { if (withCredentialProvider == null) { throw new IllegalArgumentException("CredentialProvider cannot be null"); } @@ -220,13 +210,8 @@ public BotFrameworkAdapter( * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter( - AppCredentials withCredentials, - AuthenticationConfiguration withAuthConfig, - ChannelProvider withChannelProvider, - RetryStrategy withRetryStrategy, - Middleware withMiddleware - ) { + public BotFrameworkAdapter(AppCredentials withCredentials, AuthenticationConfiguration withAuthConfig, + ChannelProvider withChannelProvider, RetryStrategy withRetryStrategy, Middleware withMiddleware) { if (withCredentials == null) { throw new IllegalArgumentException("credentials"); } @@ -281,11 +266,8 @@ public BotFrameworkAdapter( * @throws IllegalArgumentException botAppId, reference, or callback is null. */ @Override - public CompletableFuture continueConversation( - String botAppId, - ConversationReference reference, - BotCallbackHandler callback - ) { + public CompletableFuture continueConversation(String botAppId, ConversationReference reference, + BotCallbackHandler callback) { if (reference == null) { return Async.completeExceptionally(new IllegalArgumentException("reference")); } @@ -322,14 +304,9 @@ public CompletableFuture continueConversation( * @param callback The method to call for the result bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture continueConversation( - ClaimsIdentity claimsIdentity, - ConversationReference reference, - BotCallbackHandler callback - ) { - return continueConversation( - claimsIdentity, reference, getBotFrameworkOAuthScope(), callback - ); + public CompletableFuture continueConversation(ClaimsIdentity claimsIdentity, ConversationReference reference, + BotCallbackHandler callback) { + return continueConversation(claimsIdentity, reference, getBotFrameworkOAuthScope(), callback); } /** @@ -348,12 +325,8 @@ claimsIdentity, reference, getBotFrameworkOAuthScope(), callback * @param callback The method to call for the result bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture continueConversation( - ClaimsIdentity claimsIdentity, - ConversationReference reference, - String audience, - BotCallbackHandler callback - ) { + public CompletableFuture continueConversation(ClaimsIdentity claimsIdentity, ConversationReference reference, + String audience, BotCallbackHandler callback) { if (claimsIdentity == null) { return Async.completeExceptionally(new IllegalArgumentException("claimsIdentity")); } @@ -367,34 +340,30 @@ public CompletableFuture continueConversation( } if (StringUtils.isEmpty(audience)) { - return Async.completeExceptionally(new IllegalArgumentException( - "audience cannot be null or empty" - )); + return Async.completeExceptionally(new IllegalArgumentException("audience cannot be null or empty")); } CompletableFuture pipelineResult = new CompletableFuture<>(); - try (TurnContextImpl context = - new TurnContextImpl(this, reference.getContinuationActivity())) { + try (TurnContextImpl context = new TurnContextImpl(this, reference.getContinuationActivity())) { context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); context.getTurnState().add(OAUTH_SCOPE_KEY, audience); String appIdFromClaims = JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims()); - return credentialProvider.isValidAppId(appIdFromClaims) - .thenCompose(isValidAppId -> { - // If we receive a valid app id in the incoming token claims, add the - // channel service URL to the trusted services list so we can send messages back. - if (!StringUtils.isEmpty(appIdFromClaims) && isValidAppId) { - AppCredentials.trustServiceUrl(reference.getServiceUrl()); - } + return credentialProvider.isValidAppId(appIdFromClaims).thenCompose(isValidAppId -> { + // If we receive a valid app id in the incoming token claims, add the + // channel service URL to the trusted services list so we can send messages + // back. + if (!StringUtils.isEmpty(appIdFromClaims) && isValidAppId) { + AppCredentials.trustServiceUrl(reference.getServiceUrl()); + } - return createConnectorClient( - reference.getServiceUrl(), claimsIdentity, audience - ).thenCompose(connectorClient -> { - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - return runPipeline(context, callback); - }); - }); + return createConnectorClient(reference.getServiceUrl(), claimsIdentity, audience) + .thenCompose(connectorClient -> { + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + return runPipeline(context, callback); + }); + }); } catch (Exception e) { pipelineResult.completeExceptionally(e); } @@ -430,18 +399,15 @@ public BotFrameworkAdapter use(Middleware middleware) { * returned. * @throws IllegalArgumentException Activity is null. */ - public CompletableFuture processActivity( - String authHeader, - Activity activity, - BotCallbackHandler callback - ) { + public CompletableFuture processActivity(String authHeader, Activity activity, + BotCallbackHandler callback) { if (activity == null) { return Async.completeExceptionally(new IllegalArgumentException("Activity")); } - return JwtTokenValidation.authenticateRequest( - activity, authHeader, credentialProvider, channelProvider, authConfiguration - ).thenCompose(claimsIdentity -> processActivity(claimsIdentity, activity, callback)); + return JwtTokenValidation + .authenticateRequest(activity, authHeader, credentialProvider, channelProvider, authConfiguration) + .thenCompose(claimsIdentity -> processActivity(claimsIdentity, activity, callback)); } /** @@ -458,11 +424,8 @@ public CompletableFuture processActivity( * returned. * @throws IllegalArgumentException Activity is null. */ - public CompletableFuture processActivity( - ClaimsIdentity identity, - Activity activity, - BotCallbackHandler callback - ) { + public CompletableFuture processActivity(ClaimsIdentity identity, Activity activity, + BotCallbackHandler callback) { if (activity == null) { return Async.completeExceptionally(new IllegalArgumentException("Activity")); } @@ -522,30 +485,27 @@ public CompletableFuture processActivity( return pipelineResult; } - @SuppressWarnings({"PMD"}) + @SuppressWarnings("PMD") private CompletableFuture generateCallerId(ClaimsIdentity claimsIdentity) { - return credentialProvider.isAuthenticationDisabled() - .thenApply( - is_auth_disabled -> { - // Is the bot accepting all incoming messages? - if (is_auth_disabled) { - return null; - } + return credentialProvider.isAuthenticationDisabled().thenApply(is_auth_disabled -> { + // Is the bot accepting all incoming messages? + if (is_auth_disabled) { + return null; + } - // Is the activity from Public Azure? - if (channelProvider == null || channelProvider.isPublicAzure()) { - return CallerIdConstants.PUBLIC_AZURE_CHANNEL; - } + // Is the activity from Public Azure? + if (channelProvider == null || channelProvider.isPublicAzure()) { + return CallerIdConstants.PUBLIC_AZURE_CHANNEL; + } - // Is the activity from Azure Gov? - if (channelProvider != null && channelProvider.isGovernment()) { - return CallerIdConstants.US_GOV_CHANNEL; - } + // Is the activity from Azure Gov? + if (channelProvider != null && channelProvider.isGovernment()) { + return CallerIdConstants.US_GOV_CHANNEL; + } - // Return null so that the callerId is cleared. - return null; - } - ); + // Return null so that the callerId is cleared. + return null; + }); } /** @@ -562,10 +522,7 @@ private CompletableFuture generateCallerId(ClaimsIdentity claimsIdentity */ @SuppressWarnings("checkstyle:EmptyBlock, checkstyle:linelength") @Override - public CompletableFuture sendActivities( - TurnContext context, - List activities - ) { + public CompletableFuture sendActivities(TurnContext context, List activities) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("context")); } @@ -575,9 +532,8 @@ public CompletableFuture sendActivities( } if (activities.size() == 0) { - return Async.completeExceptionally(new IllegalArgumentException( - "Expecting one or more activities, but the array was empty." - )); + return Async.completeExceptionally( + new IllegalArgumentException("Expecting one or more activities, but the array was empty.")); } return CompletableFuture.supplyAsync(() -> { @@ -611,21 +567,16 @@ public CompletableFuture sendActivities( context.getTurnState().add(INVOKE_RESPONSE_KEY, activity); // No need to create a response. One will be created below. response = null; - } else if ( - activity.isType(ActivityTypes.TRACE) - && !StringUtils.equals(activity.getChannelId(), Channels.EMULATOR) - ) { + } else if (activity.isType(ActivityTypes.TRACE) + && !StringUtils.equals(activity.getChannelId(), Channels.EMULATOR)) { // if it is a Trace activity we only send to the channel if it's the emulator. response = null; } else if (!StringUtils.isEmpty(activity.getReplyToId())) { - ConnectorClient connectorClient = - context.getTurnState().get(CONNECTOR_CLIENT_KEY); + ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); response = connectorClient.getConversations().replyToActivity(activity).join(); } else { - ConnectorClient connectorClient = - context.getTurnState().get(CONNECTOR_CLIENT_KEY); - response = - connectorClient.getConversations().sendToConversation(activity).join(); + ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); + response = connectorClient.getConversations().sendToConversation(activity).join(); } // If No response is set, then default to a "simple" response. This can't really @@ -640,8 +591,7 @@ public CompletableFuture sendActivities( // https://github.com/Microsoft/botbuilder-dotnet/issues/460 // bug report : https://github.com/Microsoft/botbuilder-dotnet/issues/465 if (response == null) { - response = - new ResourceResponse((activity.getId() == null) ? "" : activity.getId()); + response = new ResourceResponse((activity.getId() == null) ? "" : activity.getId()); } responses[index] = response; @@ -667,10 +617,7 @@ public CompletableFuture sendActivities( * {@link TurnContext#onUpdateActivity(UpdateActivityHandler)} */ @Override - public CompletableFuture updateActivity( - TurnContext context, - Activity activity - ) { + public CompletableFuture updateActivity(TurnContext context, Activity activity) { ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); return connectorClient.getConversations().updateActivity(activity); } @@ -684,13 +631,10 @@ public CompletableFuture updateActivity( * {@link TurnContext#onDeleteActivity(DeleteActivityHandler)} */ @Override - public CompletableFuture deleteActivity( - TurnContext context, - ConversationReference reference - ) { + public CompletableFuture deleteActivity(TurnContext context, ConversationReference reference) { ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); - return connectorClient.getConversations() - .deleteActivity(reference.getConversation().getId(), reference.getActivityId()); + return connectorClient.getConversations().deleteActivity(reference.getConversation().getId(), + reference.getActivityId()); } /** @@ -700,26 +644,20 @@ public CompletableFuture deleteActivity( * @param memberId ID of the member to delete from the conversation * @return A task that represents the work queued to execute. */ - public CompletableFuture deleteConversationMember( - TurnContextImpl context, - String memberId - ) { + public CompletableFuture deleteConversationMember(TurnContextImpl context, String memberId) { if (context.getActivity().getConversation() == null) { return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.deleteConversationMember(): missing conversation" - )); + "BotFrameworkAdapter.deleteConversationMember(): missing conversation")); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.deleteConversationMember(): missing conversation.id" - )); + "BotFrameworkAdapter.deleteConversationMember(): missing conversation.id")); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); String conversationId = context.getActivity().getConversation().getId(); - return connectorClient.getConversations() - .deleteConversationMember(conversationId, memberId); + return connectorClient.getConversations().deleteConversationMember(conversationId, memberId); } /** @@ -740,25 +678,20 @@ public CompletableFuture> getActivityMembers(TurnContextImp * current activities ID will be used. * @return List of Members of the activity */ - public CompletableFuture> getActivityMembers( - TurnContextImpl context, - String activityId - ) { + public CompletableFuture> getActivityMembers(TurnContextImpl context, String activityId) { // If no activity was passed in, use the current activity. if (activityId == null) { activityId = context.getActivity().getId(); } if (context.getActivity().getConversation() == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.GetActivityMembers(): missing conversation" - )); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation")); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.GetActivityMembers(): missing conversation.id" - )); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id")); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -775,15 +708,13 @@ public CompletableFuture> getActivityMembers( */ public CompletableFuture> getConversationMembers(TurnContextImpl context) { if (context.getActivity().getConversation() == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.GetActivityMembers(): missing conversation" - )); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation")); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.GetActivityMembers(): missing conversation.id" - )); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id")); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -808,10 +739,8 @@ public CompletableFuture> getConversationMembers(TurnContex * conversation, as only the Bot's ServiceUrl and credentials are * required. */ - public CompletableFuture getConversations( - String serviceUrl, - MicrosoftAppCredentials credentials - ) { + public CompletableFuture getConversations(String serviceUrl, + MicrosoftAppCredentials credentials) { return getConversations(serviceUrl, credentials, null); } @@ -832,11 +761,8 @@ public CompletableFuture getConversations( * results. * @return List of Members of the current conversation */ - public CompletableFuture getConversations( - String serviceUrl, - MicrosoftAppCredentials credentials, - String continuationToken - ) { + public CompletableFuture getConversations(String serviceUrl, + MicrosoftAppCredentials credentials, String continuationToken) { if (StringUtils.isEmpty(serviceUrl)) { return Async.completeExceptionally(new IllegalArgumentException("serviceUrl")); } @@ -846,10 +772,7 @@ public CompletableFuture getConversations( } return getOrCreateConnectorClient(serviceUrl, credentials) - .thenCompose( - connectorClient -> connectorClient.getConversations() - .getConversations(continuationToken) - ); + .thenCompose(connectorClient -> connectorClient.getConversations().getConversations(continuationToken)); } /** @@ -885,10 +808,7 @@ public CompletableFuture getConversations(TurnContextImpl c * results. * @return List of Members of the current conversation */ - public CompletableFuture getConversations( - TurnContextImpl context, - String continuationToken - ) { + public CompletableFuture getConversations(TurnContextImpl context, String continuationToken) { ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); return connectorClient.getConversations().getConversations(continuationToken); } @@ -903,32 +823,22 @@ public CompletableFuture getConversations( * @return Token Response */ @Override - public CompletableFuture getUserToken( - TurnContext context, - String connectionName, - String magicCode - ) { + public CompletableFuture getUserToken(TurnContext context, String connectionName, String magicCode) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } - if ( - context.getActivity().getFrom() == null - || StringUtils.isEmpty(context.getActivity().getFrom().getId()) - ) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.getUserToken(): missing from or from.id" - )); + if (context.getActivity().getFrom() == null || StringUtils.isEmpty(context.getActivity().getFrom().getId())) { + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.getUserToken(): missing from or from.id")); } if (StringUtils.isEmpty(connectionName)) { return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } - return createOAuthClient(context, null).thenCompose(oAuthClient -> oAuthClient.getUserToken() - .getToken( - context.getActivity().getFrom().getId(), connectionName, - context.getActivity().getChannelId(), magicCode - )); + return createOAuthAPIClient(context, null) + .thenCompose(oAuthClient -> oAuthClient.getUserToken().getToken(context.getActivity().getFrom().getId(), + connectionName, context.getActivity().getChannelId(), magicCode)); } /** @@ -941,50 +851,8 @@ public CompletableFuture getUserToken( * @return A task that represents the work queued to execute. */ @Override - public CompletableFuture getOauthSignInLink( - TurnContext context, - String connectionName - ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - - if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException("connectionName")); - } - - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - try { - Activity activity = context.getActivity(); - String appId = getBotAppId(context); - - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setMsAppId(appId); - setRelatesTo(activity.getRelatesTo()); - } - }; - - String serializedState = Serialization.toString(tokenExchangeState); - String state = Base64.getEncoder() - .encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); - - return oAuthClient.getBotSignIn().getSignInUrl(state); - } catch (Throwable t) { - throw new CompletionException(t); - } - }); + public CompletableFuture getOAuthSignInLink(TurnContext context, String connectionName) { + return getOAuthSignInLink(context, null, connectionName); } /** @@ -999,54 +867,9 @@ public CompletableFuture getOauthSignInLink( * @return A task that represents the work queued to execute. */ @Override - public CompletableFuture getOauthSignInLink( - TurnContext context, - String connectionName, - String userId, - String finalRedirect - ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException("connectionName")); - } - if (StringUtils.isEmpty(userId)) { - return Async.completeExceptionally(new IllegalArgumentException("userId")); - } - - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - try { - Activity activity = context.getActivity(); - String appId = getBotAppId(context); - - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setRelatesTo(activity.getRelatesTo()); - setMsAppId(appId); - } - }; - - String serializedState = Serialization.toString(tokenExchangeState); - String state = Base64.getEncoder() - .encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); - - return oAuthClient.getBotSignIn().getSignInUrl(state); - } catch (Throwable t) { - throw new CompletionException(t); - } - }); + public CompletableFuture getOAuthSignInLink(TurnContext context, String connectionName, String userId, + String finalRedirect) { + return getOAuthSignInLink(context, null, connectionName, userId, finalRedirect); } /** @@ -1058,25 +881,8 @@ public CompletableFuture getOauthSignInLink( * @return A task that represents the work queued to execute. */ @Override - public CompletableFuture signOutUser( - TurnContext context, - String connectionName, - String userId - ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException("connectionName")); - } - - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken() - .signOut( - context.getActivity().getFrom().getId(), connectionName, - context.getActivity().getChannelId() - ); - }).thenApply(signOutResult -> null); + public CompletableFuture signOutUser(TurnContext context, String connectionName, String userId) { + return signOutUser(context, null, connectionName, userId); } /** @@ -1091,22 +897,9 @@ public CompletableFuture signOutUser( * @return Array of {@link TokenStatus}. */ @Override - public CompletableFuture> getTokenStatus( - TurnContext context, - String userId, - String includeFilter - ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - if (StringUtils.isEmpty(userId)) { - return Async.completeExceptionally(new IllegalArgumentException("userId")); - } - - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken() - .getTokenStatus(userId, context.getActivity().getChannelId(), includeFilter); - }); + public CompletableFuture> getTokenStatus(TurnContext context, String userId, + String includeFilter) { + return getTokenStatus(context, null, userId, includeFilter); } /** @@ -1124,34 +917,9 @@ public CompletableFuture> getTokenStatus( * @return Map of resourceUrl to the corresponding {@link TokenResponse}. */ @Override - public CompletableFuture> getAadTokens( - TurnContext context, - String connectionName, - String[] resourceUrls, - String userId - ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException("connectionName")); - } - if (resourceUrls == null) { - return Async.completeExceptionally(new IllegalArgumentException("resourceUrls")); - } - - return createOAuthClient(context, null).thenCompose(oAuthClient -> { - String effectiveUserId = userId; - if ( - StringUtils.isEmpty(effectiveUserId) && context.getActivity() != null - && context.getActivity().getFrom() != null - ) { - effectiveUserId = context.getActivity().getFrom().getId(); - } - - return oAuthClient.getUserToken() - .getAadTokens(effectiveUserId, connectionName, new AadResourceUrls(resourceUrls)); - }); + public CompletableFuture> getAadTokens(TurnContext context, String connectionName, + String[] resourceUrls, String userId) { + return getAadTokens(context, null, connectionName, resourceUrls, userId); } /** @@ -1179,58 +947,50 @@ public CompletableFuture> getAadTokens( * @param callback The method to call for the resulting bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture createConversation( - String channelId, - String serviceUrl, - MicrosoftAppCredentials credentials, - ConversationParameters conversationParameters, - BotCallbackHandler callback - ) { + public CompletableFuture createConversation(String channelId, String serviceUrl, + MicrosoftAppCredentials credentials, ConversationParameters conversationParameters, + BotCallbackHandler callback) { return getOrCreateConnectorClient(serviceUrl, credentials).thenCompose(connectorClient -> { Conversations conversations = connectorClient.getConversations(); return conversations.createConversation(conversationParameters) - .thenCompose(conversationResourceResponse -> { - // Create a event activity to represent the result. - Activity eventActivity = Activity.createEventActivity(); - eventActivity.setName("CreateConversation"); - eventActivity.setChannelId(channelId); - eventActivity.setServiceUrl(serviceUrl); - eventActivity.setId( - (conversationResourceResponse.getActivityId() != null) - ? conversationResourceResponse.getActivityId() - : UUID.randomUUID().toString() - ); - eventActivity.setConversation( - new ConversationAccount(conversationResourceResponse.getId()) { + .thenCompose(conversationResourceResponse -> { + // Create a event activity to represent the result. + Activity eventActivity = Activity.createEventActivity(); + eventActivity.setName("CreateConversation"); + eventActivity.setChannelId(channelId); + eventActivity.setServiceUrl(serviceUrl); + eventActivity.setId((conversationResourceResponse.getActivityId() != null) + ? conversationResourceResponse.getActivityId() + : UUID.randomUUID().toString()); + eventActivity.setConversation(new ConversationAccount(conversationResourceResponse.getId()) { { setTenantId(conversationParameters.getTenantId()); } + }); + eventActivity.setChannelData(conversationParameters.getChannelData()); + eventActivity.setRecipient(conversationParameters.getBot()); + + // run pipeline + CompletableFuture result = new CompletableFuture<>(); + try (TurnContextImpl context = new TurnContextImpl(this, eventActivity)) { + HashMap claims = new HashMap() { + { + put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); + put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); + put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); + } + }; + ClaimsIdentity claimsIdentity = new ClaimsIdentity("anonymous", claims); + + context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + + result = runPipeline(context, callback); + } catch (Exception e) { + result.completeExceptionally(e); } - ); - eventActivity.setChannelData(conversationParameters.getChannelData()); - eventActivity.setRecipient(conversationParameters.getBot()); - - // run pipeline - CompletableFuture result = new CompletableFuture<>(); - try (TurnContextImpl context = new TurnContextImpl(this, eventActivity)) { - HashMap claims = new HashMap() { - { - put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - } - }; - ClaimsIdentity claimsIdentity = new ClaimsIdentity("anonymous", claims); - - context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - - result = runPipeline(context, callback); - } catch (Exception e) { - result.completeExceptionally(e); - } - return result; - }); + return result; + }); }); } @@ -1262,14 +1022,9 @@ public CompletableFuture createConversation( * @return A task that represents the work queued to execute. */ @SuppressWarnings("checkstyle:InnerAssignment") - public CompletableFuture createConversation( - String channelId, - String serviceUrl, - MicrosoftAppCredentials credentials, - ConversationParameters conversationParameters, - BotCallbackHandler callback, - ConversationReference reference - ) { + public CompletableFuture createConversation(String channelId, String serviceUrl, + MicrosoftAppCredentials credentials, ConversationParameters conversationParameters, + BotCallbackHandler callback, ConversationReference reference) { if (reference.getConversation() == null) { return CompletableFuture.completedFuture(null); } @@ -1279,20 +1034,15 @@ public CompletableFuture createConversation( // Putting tenantId in channelData is a temporary solution while we wait for the // Teams API to be updated ObjectNode channelData = JsonNodeFactory.instance.objectNode(); - channelData.set( - "tenant", - JsonNodeFactory.instance.objectNode() - .set("tenantId", JsonNodeFactory.instance.textNode(tenantId)) - ); + channelData.set("tenant", + JsonNodeFactory.instance.objectNode().set("tenantId", JsonNodeFactory.instance.textNode(tenantId))); conversationParameters.setChannelData(channelData); conversationParameters.setTenantId(tenantId); } - return createConversation( - channelId, serviceUrl, credentials, conversationParameters, callback - ); + return createConversation(channelId, serviceUrl, credentials, conversationParameters, callback); } /** @@ -1308,42 +1058,33 @@ public CompletableFuture createConversation( * If null, the default credentials will be used. * @return An OAuth client for the bot. */ - protected CompletableFuture createOAuthClient( - TurnContext turnContext, - AppCredentials oAuthAppCredentials - ) { - if ( - !OAuthClientConfig.emulateOAuthCards - && StringUtils - .equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.EMULATOR) - && credentialProvider.isAuthenticationDisabled().join() - ) { + protected CompletableFuture createOAuthAPIClient(TurnContext turnContext, + AppCredentials oAuthAppCredentials) { + if (!OAuthClientConfig.emulateOAuthCards + && StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.EMULATOR) + && credentialProvider.isAuthenticationDisabled().join()) { OAuthClientConfig.emulateOAuthCards = true; } String appId = getBotAppId(turnContext); String cacheKey = appId + (oAuthAppCredentials != null ? oAuthAppCredentials.getAppId() : ""); String oAuthScope = getBotFrameworkOAuthScope(); - AppCredentials credentials = oAuthAppCredentials != null - ? oAuthAppCredentials - : getAppCredentials(appId, oAuthScope).join(); + AppCredentials credentials = oAuthAppCredentials != null ? oAuthAppCredentials + : getAppCredentials(appId, oAuthScope).join(); OAuthClient client = oAuthClients.computeIfAbsent(cacheKey, key -> { - OAuthClient oAuthClient = new RestOAuthClient( - OAuthClientConfig.emulateOAuthCards - ? turnContext.getActivity().getServiceUrl() - : OAuthClientConfig.OAUTHENDPOINT, - credentials - ); - - if (OAuthClientConfig.emulateOAuthCards) { - // do not join task - we want this to run in the background. - OAuthClientConfig - .sendEmulateOAuthCards(oAuthClient, OAuthClientConfig.emulateOAuthCards); - } + OAuthClient oAuthClient = new RestOAuthClient( + OAuthClientConfig.emulateOAuthCards ? turnContext.getActivity().getServiceUrl() + : OAuthClientConfig.OAUTHENDPOINT, + credentials); + + if (OAuthClientConfig.emulateOAuthCards) { + // do not join task - we want this to run in the background. + OAuthClientConfig.sendEmulateOAuthCards(oAuthClient, OAuthClientConfig.emulateOAuthCards); + } - return oAuthClient; - }); + return oAuthClient; + }); // adding the oAuthClient into the TurnState // TokenResolver.cs will use it get the correct credentials to poll for @@ -1360,21 +1101,19 @@ protected CompletableFuture createOAuthClient( * * @param serviceUrl The service URL. * @param claimsIdentity The claims identity. + * @param audience The target audience for the connector. * @return ConnectorClient instance. * @throws UnsupportedOperationException ClaimsIdentity cannot be null. Pass * Anonymous ClaimsIdentity if * authentication is turned off. */ @SuppressWarnings(value = "PMD") - private CompletableFuture createConnectorClient( - String serviceUrl, - ClaimsIdentity claimsIdentity, - String audience - ) { + public CompletableFuture createConnectorClient(String serviceUrl, + ClaimsIdentity claimsIdentity, + String audience) { if (claimsIdentity == null) { return Async.completeExceptionally(new UnsupportedOperationException( - "ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off." - )); + "ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.")); } // For requests from channel App Id is in Audience claim of JWT token. For @@ -1397,7 +1136,7 @@ private CompletableFuture createConnectorClient( String scope = getBotFrameworkOAuthScope(); return getAppCredentials(botAppIdClaim, scope) - .thenCompose(credentials -> getOrCreateConnectorClient(serviceUrl, credentials)); + .thenCompose(credentials -> getOrCreateConnectorClient(serviceUrl, credentials)); } return getOrCreateConnectorClient(serviceUrl); @@ -1419,34 +1158,25 @@ private CompletableFuture getOrCreateConnectorClient(String ser * @param usingAppCredentials (Optional) The AppCredentials to use. * @return A task that will return the ConnectorClient. */ - protected CompletableFuture getOrCreateConnectorClient( - String serviceUrl, - AppCredentials usingAppCredentials - ) { + protected CompletableFuture getOrCreateConnectorClient(String serviceUrl, + AppCredentials usingAppCredentials) { CompletableFuture result = new CompletableFuture<>(); - String clientKey = keyForConnectorClient( - serviceUrl, usingAppCredentials != null ? usingAppCredentials.getAppId() : null, - usingAppCredentials != null ? usingAppCredentials.oAuthScope() : null - ); + String clientKey = keyForConnectorClient(serviceUrl, + usingAppCredentials != null ? usingAppCredentials.getAppId() : null, + usingAppCredentials != null ? usingAppCredentials.oAuthScope() : null); result.complete(connectorClients.computeIfAbsent(clientKey, key -> { try { RestConnectorClient connectorClient; if (usingAppCredentials != null) { - connectorClient = new RestConnectorClient( - new URI(serviceUrl).toURL().toString(), - usingAppCredentials - ); + connectorClient = new RestConnectorClient(new URI(serviceUrl).toURL().toString(), + usingAppCredentials); } else { - AppCredentials emptyCredentials = - channelProvider != null && channelProvider.isGovernment() + AppCredentials emptyCredentials = channelProvider != null && channelProvider.isGovernment() ? MicrosoftGovernmentAppCredentials.empty() : MicrosoftAppCredentials.empty(); - connectorClient = new RestConnectorClient( - new URI(serviceUrl).toURL().toString(), - emptyCredentials - ); + connectorClient = new RestConnectorClient(new URI(serviceUrl).toURL().toString(), emptyCredentials); } if (connectorClientRetryStrategy != null) { @@ -1455,13 +1185,8 @@ protected CompletableFuture getOrCreateConnectorClient( return connectorClient; } catch (Throwable t) { - result - .completeExceptionally( - new IllegalArgumentException( - String.format("Invalid Service URL: %s", serviceUrl), - t - ) - ); + result.completeExceptionally( + new IllegalArgumentException(String.format("Invalid Service URL: %s", serviceUrl), t)); return null; } })); @@ -1504,9 +1229,7 @@ private CompletableFuture getAppCredentials(String appId, String private String getBotAppId(TurnContext turnContext) throws IllegalStateException { ClaimsIdentity botIdentity = turnContext.getTurnState().get(BOT_IDENTITY_KEY); if (botIdentity == null) { - throw new IllegalStateException( - "An IIdentity is required in TurnState for this operation." - ); + throw new IllegalStateException("An IIdentity is required in TurnState for this operation."); } String appId = botIdentity.claims().get(AuthenticationConstants.AUDIENCE_CLAIM); @@ -1519,8 +1242,8 @@ private String getBotAppId(TurnContext turnContext) throws IllegalStateException private String getBotFrameworkOAuthScope() { return channelProvider != null && channelProvider.isGovernment() - ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE - : AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + : AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; } /** @@ -1559,23 +1282,16 @@ protected static String keyForConnectorClient(String serviceUrl, String appId, S private static class TenantIdWorkaroundForTeamsMiddleware implements Middleware { @Override public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { - if ( - StringUtils - .equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.MSTEAMS) + if (StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.MSTEAMS) && turnContext.getActivity().getConversation() != null - && StringUtils.isEmpty(turnContext.getActivity().getConversation().getTenantId()) - ) { - - JsonNode teamsChannelData = - new ObjectMapper().valueToTree(turnContext.getActivity().getChannelData()); - if ( - teamsChannelData != null && teamsChannelData.has("tenant") - && teamsChannelData.get("tenant").has("id") - ) { - - turnContext.getActivity() - .getConversation() - .setTenantId(teamsChannelData.get("tenant").get("id").asText()); + && StringUtils.isEmpty(turnContext.getActivity().getConversation().getTenantId())) { + + JsonNode teamsChannelData = new ObjectMapper().valueToTree(turnContext.getActivity().getChannelData()); + if (teamsChannelData != null && teamsChannelData.has("tenant") + && teamsChannelData.get("tenant").has("id")) { + + turnContext.getActivity().getConversation() + .setTenantId(teamsChannelData.get("tenant").get("id").asText()); } } @@ -1601,6 +1317,453 @@ protected Map getConnectorClientCache() { return Collections.unmodifiableMap(connectorClients); } + /** + * Attempts to retrieve the token for a user that's in a login flow, using + * customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param magicCode (Optional) Optional user entered code + * to validate. + * + * @return Token Response. + */ + @Override + public CompletableFuture getUserToken(TurnContext context, AppCredentials oAuthAppCredentials, + String connectionName, String magicCode) { + + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + + if (context.getActivity().getFrom() == null + || StringUtils.isEmpty(context.getActivity().getFrom().getId())) { + return Async.completeExceptionally(new IllegalArgumentException( + "BotFrameworkAdapter.GetUserTokenAsync(): missing from or from.id" + )); + } + + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException( + "connectionName cannot be null." + )); + } + + OAuthClient client = createOAuthAPIClient(context, oAuthAppCredentials).join(); + return client.getUserToken().getToken( + context.getActivity().getFrom().getId(), + connectionName, + context.getActivity().getChannelId(), + magicCode); + } + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name, using customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw signin + * link. + */ + @Override + public CompletableFuture getOAuthSignInLink(TurnContext context, AppCredentials oAuthAppCredentials, + String connectionName) { + + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + + if (StringUtils.isEmpty(connectionName)) { + Async.completeExceptionally(new IllegalArgumentException( + "connectionName cannot be null." + )); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + try { + Activity activity = context.getActivity(); + String appId = getBotAppId(context); + + TokenExchangeState tokenExchangeState = new TokenExchangeState() { + { + setConnectionName(connectionName); + setConversation(new ConversationReference() { + { + setActivityId(activity.getId()); + setBot(activity.getRecipient()); + setChannelId(activity.getChannelId()); + setConversation(activity.getConversation()); + setServiceUrl(activity.getServiceUrl()); + setUser(activity.getFrom()); + } + }); + setRelatesTo(activity.getRelatesTo()); + setMsAppId(appId); + } + }; + + String serializedState = Serialization.toString(tokenExchangeState); + String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); + + return oAuthClient.getBotSignIn().getSignInUrl(state); + } catch (Throwable t) { + throw new CompletionException(t); + } + }); + } + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name, using the bot's AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with + * the token. + * @param finalRedirect The final URL that the OAuth flow will + * redirect to. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw signin + * link. + */ + @Override + public CompletableFuture getOAuthSignInLink(TurnContext context, AppCredentials oAuthAppCredentials, + String connectionName, String userId, String finalRedirect) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); + } + if (StringUtils.isEmpty(userId)) { + return Async.completeExceptionally(new IllegalArgumentException("userId")); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + try { + Activity activity = context.getActivity(); + String appId = getBotAppId(context); + + TokenExchangeState tokenExchangeState = new TokenExchangeState() { + { + setConnectionName(connectionName); + setConversation(new ConversationReference() { + { + setActivityId(activity.getId()); + setBot(activity.getRecipient()); + setChannelId(activity.getChannelId()); + setConversation(activity.getConversation()); + setServiceUrl(activity.getServiceUrl()); + setUser(activity.getFrom()); + } + }); + setRelatesTo(activity.getRelatesTo()); + setMsAppId(appId); + } + }; + + String serializedState = Serialization.toString(tokenExchangeState); + String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); + + return oAuthClient.getBotSignIn().getSignInUrl(state, null, null, finalRedirect); + } catch (Throwable t) { + throw new CompletionException(t); + } + }); + } + + /** + * Signs the user out with the token server, using customized + * AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId User id of user to sign out. + * + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture signOutUser(TurnContext context, AppCredentials oAuthAppCredentials, + String connectionName, String userId) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + return oAuthClient.getUserToken().signOut(context.getActivity().getFrom().getId(), connectionName, + context.getActivity().getChannelId()); + }).thenApply(signOutResult -> null); + } + + /** + * Retrieves the token status for each configured connection for the given + * user, using customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param userId The user Id for which token status is + * retrieved. + * @param includeFilter Optional comma separated list of + * connection's to include. Blank will return token status for all + * configured connections. + * + * @return List of TokenStatus. + */ + @Override + public CompletableFuture> getTokenStatus(TurnContext context, AppCredentials oAuthAppCredentials, + String userId, String includeFilter) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(userId)) { + return Async.completeExceptionally(new IllegalArgumentException("userId")); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + return oAuthClient.getUserToken().getTokenStatus(userId, context.getActivity().getChannelId(), + includeFilter); + }); + } + + /** + * Retrieves Azure Active Directory tokens for particular resources on a + * configured connection, using customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName The name of the Azure Active + * Directory connection configured with this bot. + * @param resourceUrls The list of resource URLs to retrieve + * tokens for. + * @param userId The user Id for which tokens are + * retrieved. If passing in null the userId is taken from the Activity in + * the TurnContext. + * + * @return Dictionary of resourceUrl to the corresponding + * TokenResponse. + */ + @Override + public CompletableFuture> getAadTokens(TurnContext context, + AppCredentials oAuthAppCredentials, String connectionName, String[] resourceUrls, String userId) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); + } + if (resourceUrls == null) { + return Async.completeExceptionally(new IllegalArgumentException("resourceUrls")); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + String effectiveUserId = userId; + if (StringUtils.isEmpty(effectiveUserId) && context.getActivity() != null + && context.getActivity().getFrom() != null) { + effectiveUserId = context.getActivity().getFrom().getId(); + } + + return oAuthClient.getUserToken().getAadTokens(effectiveUserId, connectionName, + new AadResourceUrls(resourceUrls)); + }); + + } + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw signin + * link. + */ + @Override + public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName) { + return getSignInResource(turnContext, connectionName, turnContext.getActivity().getFrom().getId(), null); + } + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with + * the token. + * @param finalRedirect The final URL that the OAuth flow will + * redirect to. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw signin + * link. + */ + @Override + public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName, + String userId, String finalRedirect) { + return getSignInResource(turnContext, null, connectionName, userId, finalRedirect); + } + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated + * with the token. + * @param finalRedirect The final URL that the OAuth flow + * will redirect to. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw signin + * link. + */ + @Override + public CompletableFuture getSignInResource(TurnContext context, + AppCredentials oAuthAppCredentials, String connectionName, String userId, String finalRedirect) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + + if (StringUtils.isEmpty(connectionName)) { + throw new IllegalArgumentException("connectionName cannot be null."); + } + + if (StringUtils.isEmpty(userId)) { + throw new IllegalArgumentException("userId cannot be null."); + } + + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + try { + Activity activity = context.getActivity(); + String appId = getBotAppId(context); + + TokenExchangeState tokenExchangeState = new TokenExchangeState() { + { + setConnectionName(connectionName); + setConversation(new ConversationReference() { + { + setActivityId(activity.getId()); + setBot(activity.getRecipient()); + setChannelId(activity.getChannelId()); + setConversation(activity.getConversation()); + setLocale(activity.getLocale()); + setServiceUrl(activity.getServiceUrl()); + setUser(activity.getFrom()); + } + }); + setRelatesTo(activity.getRelatesTo()); + setMsAppId(appId); + } + }; + + String serializedState = Serialization.toString(tokenExchangeState); + String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); + + return oAuthClient.getBotSignIn().getSignInResource(state, null, null, finalRedirect); + } catch (Throwable t) { + throw new CompletionException(t); + } + }); + + } + + /** + * Performs a token exchange operation such as for single sign-on. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the token.. + * @param exchangeRequest The exchange request details, either a + * token to exchange or a uri to exchange. + * + * @return If the task completes, the exchanged token is returned. + */ + @Override + public CompletableFuture exchangeToken(TurnContext turnContext, String connectionName, String userId, + TokenExchangeRequest exchangeRequest) { + return exchangeToken(turnContext, null, connectionName, userId, exchangeRequest); + } + + /** + * Performs a token exchange operation such as for single sign-on. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the + * token.. + * @param exchangeRequest The exchange request details, either + * a token to exchange or a uri to exchange. + * + * @return If the task completes, the exchanged token is returned. + */ + @Override + public CompletableFuture exchangeToken(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName, String userId, TokenExchangeRequest exchangeRequest) { + + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException( + "connectionName is null or empty" + )); + } + + if (StringUtils.isEmpty(userId)) { + return Async.completeExceptionally(new IllegalArgumentException( + "userId is null or empty" + )); + } + + if (exchangeRequest == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "exchangeRequest is null" + )); + } + + if (StringUtils.isEmpty(exchangeRequest.getToken()) && StringUtils.isEmpty(exchangeRequest.getUri())) { + return Async.completeExceptionally(new IllegalArgumentException( + "Either a Token or Uri property is required on the TokenExchangeRequest" + )); + } + + return createOAuthAPIClient(turnContext, oAuthAppCredentials).thenCompose(oAuthClient -> { + return oAuthClient.getUserToken().exchangeToken(userId, + connectionName, + turnContext.getActivity().getChannelId(), + exchangeRequest); + + }); + } + /** * Inserts a ConnectorClient into the cache. FOR UNIT TESTING ONLY. * @param serviceUrl The service url diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConnectorClientBuilder.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConnectorClientBuilder.java new file mode 100644 index 000000000..b63e19699 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ConnectorClientBuilder.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.ConnectorClient; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; + +/** + * Abstraction to build connector clients. + */ + public interface ConnectorClientBuilder { + + /** + * Creates the connector client asynchronous. + * @param serviceUrl The service URL. + * @param claimsIdentity The claims claimsIdentity. + * @param audience The target audience for the connector. + * @return ConnectorClient instance. + */ + CompletableFuture createConnectorClient(String serviceUrl, + ClaimsIdentity claimsIdentity, + String audience); +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnStateConstants.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnStateConstants.java new file mode 100644 index 000000000..fab1f8a14 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnStateConstants.java @@ -0,0 +1,34 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import java.time.Duration; + +/** + * Constants used in TurnState. + */ +public final class TurnStateConstants { + + private TurnStateConstants() { + + } + + /** + * TurnState key for the OAuth login timeout. + */ + public static final String OAUTH_LOGIN_TIMEOUT_KEY = "loginTimeout"; + + /** + * Name of the token polling settings key. + */ + public static final String TOKEN_POLLING_SETTINGS_KEY = "tokenPollingSettings"; + + /** + * Default amount of time an OAuthCard will remain active (clickable and + * actively waiting for a token). After this time: (1) the OAuthCard will not + * allow the user to click on it. (2) any polling triggered by the OAuthCard + * will stop. + */ + public static final Duration OAUTH_LOGIN_TIMEOUT_VALUE = Duration.ofMinutes(15); +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserTokenProvider.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserTokenProvider.java index 47b2b8370..995b97a8d 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserTokenProvider.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/UserTokenProvider.java @@ -3,6 +3,9 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.schema.SignInResource; +import com.microsoft.bot.schema.TokenExchangeRequest; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; @@ -39,7 +42,7 @@ CompletableFuture getUserToken( * @return A task that represents the work queued to execute. If the task * completes successfully, the result contains the raw signin link. */ - CompletableFuture getOauthSignInLink(TurnContext turnContext, String connectionName); + CompletableFuture getOAuthSignInLink(TurnContext turnContext, String connectionName); /** * Get the raw signin link to be sent to the user for signin for a connection @@ -53,7 +56,7 @@ CompletableFuture getUserToken( * @return A task that represents the work queued to execute. If the task * completes successfully, the result contains the raw signin link. */ - CompletableFuture getOauthSignInLink( + CompletableFuture getOAuthSignInLink( TurnContext turnContext, String connectionName, String userId, @@ -156,4 +159,237 @@ CompletableFuture> getAadTokens( String[] resourceUrls, String userId ); + + + /** + * Attempts to retrieve the token for a user that's in a login flow, using + * customized AppCredentials. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param magicCode (Optional) Optional user entered code + * to validate. + * + * @return Token Response. + */ + CompletableFuture getUserToken( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String magicCode); + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name, using customized AppCredentials. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * + * @return A CompletableFuture that represents the work queued to execute. + * + * If the CompletableFuture completes successfully, the result contains the raw signin + * link. + */ + CompletableFuture getOAuthSignInLink( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName); + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name, using customized AppCredentials. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated + * with the token. + * @param finalRedirect The final URL that the OAuth flow + * will redirect to. + * + * @return A CompletableFuture that represents the work queued to execute. + * + * If the CompletableFuture completes successfully, the result contains the raw signin + * link. + */ + CompletableFuture getOAuthSignInLink( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + String finalRedirect); + + /** + * Signs the user out with the token server, using customized + * AppCredentials. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId User id of user to sign out. + * + * @return A CompletableFuture that represents the work queued to execute. + */ + CompletableFuture signOutUser( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId); + + /** + * Retrieves the token status for each configured connection for the given + * user, using customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param userId The user Id for which token status is + * retrieved. + * @param includeFilter Optional comma separated list of + * connection's to include. Blank will return token status for all + * configured connections. + * + * @return Array of TokenStatus. + */ + CompletableFuture> getTokenStatus( + TurnContext context, + AppCredentials oAuthAppCredentials, + String userId, + String includeFilter); + + /** + * Retrieves Azure Active Directory tokens for particular resources on a + * configured connection, using customized AppCredentials. + * + * @param context Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName The name of the Azure Active + * Directory connection configured with this bot. + * @param resourceUrls The list of resource URLs to retrieve + * tokens for. + * @param userId The user Id for which tokens are + * retrieved. If passing in null the userId is taken from the Activity in + * the TurnContext. + * + * @return Dictionary of resourceUrl to the corresponding + * TokenResponse. + */ + CompletableFuture> getAadTokens( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String[] resourceUrls, + String userId); + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * + * @return A CompletableFuture that represents the work queued to execute. + * + * If the CompletableFuture completes successfully, the result contains the raw signin + * link. + */ + CompletableFuture getSignInResource( + TurnContext turnContext, + String connectionName); + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with + * the token. + * @param finalRedirect The final URL that the OAuth flow will + * redirect to. + * + * @return A CompletableFuture that represents the work queued to execute. + * + * If the CompletableFuture completes successfully, the result contains the raw signin + * link. + */ + CompletableFuture getSignInResource( + TurnContext turnContext, + String connectionName, + String userId, + String finalRedirect); + + /** + * Get the raw signin link to be sent to the user for signin for a + * connection name. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials Credentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated + * with the token. + * @param finalRedirect The final URL that the OAuth flow + * will redirect to. + * + * @return A CompletableFuture that represents the work queued to execute. + * + * If the CompletableFuture completes successfully, the result contains the raw signin + * link. + */ + CompletableFuture getSignInResource( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + String finalRedirect); + + /** + * Performs a token exchange operation such as for single sign-on. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the token.. + * @param exchangeRequest The exchange request details, either a + * token to exchange or a uri to exchange. + * + * @return If the CompletableFuture completes, the exchanged token is returned. + */ + CompletableFuture exchangeToken( + TurnContext turnContext, + String connectionName, + String userId, + TokenExchangeRequest exchangeRequest); + + /** + * Performs a token exchange operation such as for single sign-on. + * + * @param turnContext Context for the current turn of + * conversation with the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the + * token.. + * @param exchangeRequest The exchange request details, either + * a token to exchange or a uri to exchange. + * + * @return If the CompletableFuture completes, the exchanged token is returned. + */ + CompletableFuture exchangeToken( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + TokenExchangeRequest exchangeRequest); + } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java index b6be7ecc3..efd49f498 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java @@ -14,6 +14,7 @@ import org.junit.Assert; import org.junit.Test; +import java.util.List; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -451,9 +452,9 @@ public void TestAdapter_GetTokenStatus() { adapter.addUserToken("ABC", channelId, userId, token, null); adapter.addUserToken("DEF", channelId, userId, token, null); - TokenStatus[] status = adapter.getTokenStatus(turnContext, userId, null).join(); + List status = adapter.getTokenStatus(turnContext, userId, null).join(); Assert.assertNotNull(status); - Assert.assertEquals(2, status.length); + Assert.assertEquals(2, status.size()); } @Test @@ -478,8 +479,8 @@ public void TestAdapter_GetTokenStatusWithFilter() { adapter.addUserToken("ABC", channelId, userId, token, null); adapter.addUserToken("DEF", channelId, userId, token, null); - TokenStatus[] status = adapter.getTokenStatus(turnContext, userId, "DEF").join(); + List status = adapter.getTokenStatus(turnContext, userId, "DEF").join(); Assert.assertNotNull(status); - Assert.assertEquals(1, status.length); + Assert.assertEquals(1, status.size()); } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index d4425747b..84a46a6f4 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -4,35 +4,67 @@ package com.microsoft.bot.builder.adapters; import com.microsoft.bot.builder.*; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.Channels; -import com.microsoft.bot.connector.UserToken; +import com.microsoft.bot.connector.authentication.AppCredentials; import com.microsoft.bot.schema.*; import org.apache.commons.lang3.StringUtils; + import java.time.OffsetDateTime; import java.time.ZoneId; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -public class TestAdapter extends BotAdapter { +public class TestAdapter extends BotAdapter implements UserTokenProvider { + + private final String exceptionExpected = "ExceptionExpected"; private final Queue botReplies = new LinkedList<>(); private int nextId = 0; private ConversationReference conversationReference; private String locale; private boolean sendTraceActivity = false; + private Map exchangableToken = new HashMap(); + private static class UserTokenKey { - public String connectionName; - public String userId; - public String channelId; + private String connectionName; + private String userId; + private String channelId; + + public String getConnectionName() { + return connectionName; + } + + public void setConnectionName(String withConnectionName) { + connectionName = withConnectionName; + } + + public String getUserId() { + return userId; + } + + public void setUserId(String withUserId) { + userId = withUserId; + } + + public String getChannelId() { + return channelId; + } + + public void setChannelId(String withChannelId) { + channelId = withChannelId; + } + + @Override public boolean equals(Object rhs) { if (!(rhs instanceof UserTokenKey)) return false; return StringUtils.equals(connectionName, ((UserTokenKey) rhs).connectionName) - && StringUtils.equals(userId, ((UserTokenKey) rhs).userId) + && StringUtils.equals(userId, ((UserTokenKey) rhs).userId) && StringUtils.equals(channelId, ((UserTokenKey) rhs).channelId); } @@ -132,9 +164,11 @@ public TestAdapter use(Middleware middleware) { return this; } - /** - * Adds middleware to the adapter to register an Storage object on the turn context. - * The middleware registers the state objects on the turn context at the start of each turn. + /** + * Adds middleware to the adapter to register an Storage object on the turn + * context. The middleware registers the state objects on the turn context at + * the start of each turn. + * * @param storage The storage object to register. * @return The updated adapter. */ @@ -146,8 +180,10 @@ public TestAdapter useStorage(Storage storage) { } /** - * Adds middleware to the adapter to register one or more BotState objects on the turn context. - * The middleware registers the state objects on the turn context at the start of each turn. + * Adds middleware to the adapter to register one or more BotState objects on + * the turn context. The middleware registers the state objects on the turn + * context at the start of each turn. + * * @param botstates The state objects to register. * @return The updated adapter. */ @@ -169,10 +205,8 @@ public CompletableFuture processActivity(Activity activity, BotCallbackHan activity.setType(ActivityTypes.MESSAGE); activity.setChannelId(conversationReference().getChannelId()); - if (activity.getFrom() == null - || StringUtils.equalsIgnoreCase(activity.getFrom().getId(), "unknown") - || activity.getFrom().getRole() == RoleTypes.BOT - ) { + if (activity.getFrom() == null || StringUtils.equalsIgnoreCase(activity.getFrom().getId(), "unknown") + || activity.getFrom().getRole() == RoleTypes.BOT) { activity.setFrom(conversationReference().getUser()); } @@ -206,10 +240,7 @@ public void setConversationReference(ConversationReference conversationReference } @Override - public CompletableFuture sendActivities( - TurnContext context, - List activities - ) { + public CompletableFuture sendActivities(TurnContext context, List activities) { List responses = new LinkedList(); for (Activity activity : activities) { @@ -221,23 +252,12 @@ public CompletableFuture sendActivities( responses.add(new ResourceResponse(activity.getId())); - System.out.println( - String.format( - "TestAdapter:SendActivities, Count:%s (tid:%s)", - activities.size(), - Thread.currentThread().getId() - ) - ); + System.out.println(String.format("TestAdapter:SendActivities, Count:%s (tid:%s)", activities.size(), + Thread.currentThread().getId())); for (Activity act : activities) { System.out.printf(" :--------\n : To:%s\n", act.getRecipient().getName()); - System.out.printf( - " : From:%s\n", - (act.getFrom() == null) ? "No from set" : act.getFrom().getName() - ); - System.out.printf( - " : Text:%s\n :---------\n", - (act.getText() == null) ? "No text set" : act.getText() - ); + System.out.printf(" : From:%s\n", (act.getFrom() == null) ? "No from set" : act.getFrom().getName()); + System.out.printf(" : Text:%s\n :---------\n", (act.getText() == null) ? "No text set" : act.getText()); } // This is simulating DELAY @@ -263,16 +283,11 @@ public CompletableFuture sendActivities( } } } - return CompletableFuture.completedFuture( - responses.toArray(new ResourceResponse[responses.size()]) - ); + return CompletableFuture.completedFuture(responses.toArray(new ResourceResponse[responses.size()])); } @Override - public CompletableFuture updateActivity( - TurnContext context, - Activity activity - ) { + public CompletableFuture updateActivity(TurnContext context, Activity activity) { synchronized (botReplies) { List replies = new ArrayList<>(botReplies); for (int i = 0; i < botReplies.size(); i++) { @@ -283,9 +298,7 @@ public CompletableFuture updateActivity( for (Activity item : replies) { botReplies.add(item); } - return CompletableFuture.completedFuture( - new ResourceResponse(activity.getId()) - ); + return CompletableFuture.completedFuture(new ResourceResponse(activity.getId())); } } } @@ -293,10 +306,7 @@ public CompletableFuture updateActivity( } @Override - public CompletableFuture deleteActivity( - TurnContext context, - ConversationReference reference - ) { + public CompletableFuture deleteActivity(TurnContext context, ConversationReference reference) { synchronized (botReplies) { ArrayList replies = new ArrayList<>(botReplies); for (int i = 0; i < botReplies.size(); i++) { @@ -363,13 +373,8 @@ public CompletableFuture sendTextToBot(String userSays, BotCallbackHandler return processActivity(this.makeActivity(userSays), callback); } - public void addUserToken( - String connectionName, - String channelId, - String userId, - String token, - String withMagicCode - ) { + public void addUserToken(String connectionName, String channelId, String userId, String token, + String withMagicCode) { UserTokenKey userKey = new UserTokenKey(); userKey.connectionName = connectionName; userKey.channelId = channelId; @@ -388,116 +393,23 @@ public void addUserToken( } } - public CompletableFuture getUserToken( - TurnContext turnContext, - String connectionName, - String magicCode - ) { - UserTokenKey key = new UserTokenKey(); - key.connectionName = connectionName; - key.channelId = turnContext.getActivity().getChannelId(); - key.userId = turnContext.getActivity().getFrom().getId(); - - if (magicCode != null) { - TokenMagicCode magicCodeRecord = magicCodes.stream().filter( - tokenMagicCode -> key.equals(tokenMagicCode.key) - ).findFirst().orElse(null); - if ( - magicCodeRecord != null && StringUtils.equals(magicCodeRecord.magicCode, magicCode) - ) { - addUserToken( - connectionName, - key.channelId, - key.userId, - magicCodeRecord.userToken, - null - ); - } - } + public CompletableFuture getUserToken(TurnContext turnContext, String connectionName, + String magicCode) { + return getUserToken(turnContext, null, connectionName, magicCode); - if (userTokens.containsKey(key)) { - return CompletableFuture.completedFuture(new TokenResponse() { - { - setConnectionName(connectionName); - setToken(userTokens.get(key)); - } - }); - } - - return CompletableFuture.completedFuture(null); } - - public CompletableFuture getOAuthSignInLink( - TurnContext turnContext, - String connectionName - ) { - return getOAuthSignInLink( - turnContext, - connectionName, - turnContext.getActivity().getFrom().getId(), - null - ); - } - - public CompletableFuture getOAuthSignInLink( - TurnContext turnContext, - String connectionName, - String userId, - String finalRedirect - ) { - String link = String.format( - "https://fake.com/oauthsignin/%s/{turnContext.Activity.ChannelId}/%s", - connectionName, - userId == null ? "" : userId - ); - return CompletableFuture.completedFuture(link); - } - - public CompletableFuture signOutUser( - TurnContext turnContext, - String connectionName, - String userId - ) { - String channelId = turnContext.getActivity().getChannelId(); - final String effectiveUserId = userId == null - ? turnContext.getActivity().getFrom().getId() - : userId; - - userTokens.keySet().stream().filter( - t -> StringUtils.equals(t.channelId, channelId) && StringUtils.equals(t.userId, effectiveUserId) && connectionName == null || StringUtils.equals(t.connectionName, connectionName) - ).collect(Collectors.toList()).forEach(key -> userTokens.remove(key)); - - return CompletableFuture.completedFuture(null); + public CompletableFuture signOutUser(TurnContext turnContext, String connectionName, String userId) { + return signOutUser(turnContext, null, connectionName, userId); } - public CompletableFuture getTokenStatus( - TurnContext turnContext, - String userId, - String includeFilter - ) { - String[] filter = includeFilter == null ? null : includeFilter.split(","); - List records = userTokens.keySet().stream().filter( - x -> StringUtils.equals(x.channelId, turnContext.getActivity().getChannelId()) && StringUtils.equals(x.userId, turnContext.getActivity().getFrom().getId()) && (includeFilter == null || Arrays.binarySearch(filter, x.connectionName) != -1) - ).map(r -> new TokenStatus() { - { - setConnectionName(r.connectionName); - setHasToken(true); - setServiceProviderDisplayName(r.connectionName); - } - }).collect(Collectors.toList()); - - if (records.size() > 0) - return CompletableFuture.completedFuture(records.toArray(new TokenStatus[0])); - return CompletableFuture.completedFuture(null); + public CompletableFuture> getTokenStatus(TurnContext turnContext, String userId, + String includeFilter) { + return getTokenStatus(turnContext, null, userId, includeFilter); } - public CompletableFuture> getAadTokens( - TurnContext turnContext, - String connectionName, - String[] resourceUrls, - String userId - ) { - return CompletableFuture.completedFuture(new HashMap<>()); + public CompletableFuture> getAadTokens(TurnContext turnContext, String connectionName, + String[] resourceUrls, String userId) { + return getAadTokens(turnContext, null, connectionName, resourceUrls, userId); } public static ConversationReference createConversationReference(String name, String user, String bot) { @@ -526,4 +438,249 @@ public void setSendTraceActivity(boolean sendTraceActivity) { public boolean getSendTraceActivity() { return sendTraceActivity; } + + public CompletableFuture getOAuthSignInLink(TurnContext turnContext, String connectionName) { + return getOAuthSignInLink(turnContext, null, connectionName); + } + + public CompletableFuture getOAuthSignInLink(TurnContext turnContext, String connectionName, String userId, + String finalRedirect) { + return getOAuthSignInLink(turnContext, null, connectionName, userId, finalRedirect); + } + + @Override + public CompletableFuture getOAuthSignInLink(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName) { + return CompletableFuture.completedFuture( + String.format("https://fake.com/oauthsignin/%s/%s", + connectionName, + turnContext.getActivity().getChannelId())); + } + + public CompletableFuture getOAuthSignInLink(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName, String userId, String finalRedirect) { + return CompletableFuture.completedFuture( + String.format("https://fake.com/oauthsignin/%s/%s/%s", + connectionName, + turnContext.getActivity().getChannelId(), + userId)); + } + + @Override + public CompletableFuture getUserToken(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName, String magicCode) { + UserTokenKey key = new UserTokenKey(); + key.connectionName = connectionName; + key.channelId = turnContext.getActivity().getChannelId(); + key.userId = turnContext.getActivity().getFrom().getId(); + + if (magicCode != null) { + TokenMagicCode magicCodeRecord = magicCodes.stream() + .filter(tokenMagicCode -> key.equals(tokenMagicCode.key)).findFirst().orElse(null); + if (magicCodeRecord != null && StringUtils.equals(magicCodeRecord.magicCode, magicCode)) { + addUserToken(connectionName, key.channelId, key.userId, magicCodeRecord.userToken, null); + } + } + + if (userTokens.containsKey(key)) { + return CompletableFuture.completedFuture(new TokenResponse() { + { + setConnectionName(connectionName); + setToken(userTokens.get(key)); + } + }); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Adds a fake exchangeable token so it can be exchanged later. + * @param connectionName he connection name. + * @param channelId The channel ID. + * @param userId The user ID. + * @param exchangableItem The exchangeable token or resource URI. + * @param token The token to store. + */ + public void addExchangeableToken(String connectionName, + String channelId, + String userId, + String exchangableItem, + String token) + { + ExchangableTokenKey key = new ExchangableTokenKey(); + key.setConnectionName(connectionName); + key.setChannelId(channelId); + key.setUserId(userId); + key.setExchangableItem(exchangableItem); + + if (exchangableToken.containsKey(key)) { + exchangableToken.replace(key, token); + } else { + exchangableToken.put(key, token); + } + } + + @Override + public CompletableFuture signOutUser(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName, String userId) { + String channelId = turnContext.getActivity().getChannelId(); + final String effectiveUserId = userId == null ? turnContext.getActivity().getFrom().getId() : userId; + + userTokens.keySet().stream() + .filter(t -> StringUtils.equals(t.channelId, channelId) + && StringUtils.equals(t.userId, effectiveUserId) + && connectionName == null || StringUtils.equals(t.connectionName, connectionName)) + .collect(Collectors.toList()).forEach(key -> userTokens.remove(key)); + + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture> getTokenStatus(TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String userId, + String includeFilter) { + String[] filter = includeFilter == null ? null : includeFilter.split(","); + List records = userTokens.keySet().stream() + .filter(x -> StringUtils.equals(x.channelId, turnContext.getActivity().getChannelId()) + && StringUtils.equals(x.userId, turnContext.getActivity().getFrom().getId()) + && (includeFilter == null || Arrays.binarySearch(filter, x.connectionName) != -1)) + .map(r -> new TokenStatus() { + { + setConnectionName(r.connectionName); + setHasToken(true); + setServiceProviderDisplayName(r.connectionName); + } + }).collect(Collectors.toList()); + + if (records.size() > 0) { + return CompletableFuture.completedFuture(records); + } + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture> getAadTokens(TurnContext context, + AppCredentials oAuthAppCredentials, String connectionName, String[] resourceUrls, String userId) { + return CompletableFuture.completedFuture(new HashMap<>()); + } + + @Override + public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName) { + String id = null; + if (turnContext != null + && turnContext.getActivity() != null + && turnContext.getActivity().getRecipient() != null + && turnContext.getActivity().getRecipient().getId() != null) { + id = turnContext.getActivity().getRecipient().getId(); + } + + return getSignInResource(turnContext, connectionName, id, null); + } + + @Override + public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName, + String userId, String finalRedirect) { + return getSignInResource(turnContext, null, connectionName, userId, finalRedirect); + } + + @Override + public CompletableFuture getSignInResource(TurnContext turnContext, + AppCredentials oAuthAppCredentials, String connectionName, String userId, String finalRedirect) { + + SignInResource signInResource = new SignInResource(); + signInResource.setSignInLink( + String.format("https://fake.com/oauthsignin/%s/%s/%s", + connectionName, + turnContext.getActivity().getChannelId(), + userId)); + TokenExchangeResource tokenExchangeResource = new TokenExchangeResource(); + tokenExchangeResource.setId(UUID.randomUUID().toString()); + tokenExchangeResource.setProviderId(null); + tokenExchangeResource.setUri(String.format("api://%s/resource", connectionName)); + signInResource.setTokenExchangeResource(tokenExchangeResource); + return CompletableFuture.completedFuture(signInResource); + } + + @Override + public CompletableFuture exchangeToken(TurnContext turnContext, String connectionName, String userId, + TokenExchangeRequest exchangeRequest) { + return exchangeToken(turnContext, null, connectionName, userId, exchangeRequest); + } + + @Override + public CompletableFuture exchangeToken(TurnContext turnContext, AppCredentials oAuthAppCredentials, + String connectionName, String userId, TokenExchangeRequest exchangeRequest) { + + String exchangableValue = null; + if (exchangeRequest.getToken() != null) { + if (StringUtils.isNotBlank(exchangeRequest.getToken())) { + exchangableValue = exchangeRequest.getToken(); + } + } else { + if (exchangeRequest.getUri() != null) { + exchangableValue = exchangeRequest.getUri(); + } + } + + ExchangableTokenKey key = new ExchangableTokenKey(); + if (turnContext != null + && turnContext.getActivity() != null + && turnContext.getActivity().getChannelId() != null) { + key.setChannelId(turnContext.getActivity().getChannelId()); + } + key.setConnectionName(connectionName); + key.setExchangableItem(exchangableValue); + key.setUserId(userId); + + String token = exchangableToken.get(key); + if (token != null) { + if (token.equals(exceptionExpected)) { + return Async.completeExceptionally( + new RuntimeException("Exception occurred during exchanging tokens") + ); + } + return CompletableFuture.completedFuture(new TokenResponse() { + { + setChannelId(key.getChannelId()); + setConnectionName(key.getConnectionName()); + setToken(token); + } + }); + } else { + return CompletableFuture.completedFuture(null); + } + + } + + class ExchangableTokenKey extends UserTokenKey { + + private String exchangableItem = ""; + + public String getExchangableItem() { + return exchangableItem; + } + + public void setExchangableItem(String withExchangableItem) { + exchangableItem = withExchangableItem; + } + + @Override + public boolean equals(Object rhs) { + if (!(rhs instanceof ExchangableTokenKey)) { + return false; + } + return StringUtils.equals(exchangableItem, ((ExchangableTokenKey) rhs).exchangableItem) + && super.equals(rhs); + } + + @Override + public int hashCode() { + return Objects.hash(exchangableItem != null ? exchangableItem : "") + super.hashCode(); + } + + + } + } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/BotSignIn.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/BotSignIn.java index 83efb4bad..18b7689cd 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/BotSignIn.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/BotSignIn.java @@ -8,6 +8,8 @@ import java.util.concurrent.CompletableFuture; +import com.microsoft.bot.schema.SignInResource; + /** * An instance of this class provides access to all the operations defined in * BotSignIns. @@ -36,4 +38,27 @@ CompletableFuture getSignInUrl( String emulatorUrl, String finalRedirect ); + + /** + * + * @param state the String value + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the String object + */ + CompletableFuture getSignInResource(String state); + /** + * + * @param state the String value + * @param codeChallenge the String value + * @param emulatorUrl the String value + * @param finalRedirect the String value + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the String object + */ + CompletableFuture getSignInResource( + String state, + String codeChallenge, + String emulatorUrl, + String finalRedirect + ); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java index 6ec7b3cf6..6422a030e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java @@ -7,6 +7,7 @@ package com.microsoft.bot.connector; import com.microsoft.bot.schema.AadResourceUrls; +import com.microsoft.bot.schema.TokenExchangeRequest; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; import java.util.List; @@ -43,6 +44,22 @@ CompletableFuture getToken( String code ); + /** + * + * @param userId the String value + * @param connectionName the String value + * @param channelId the String value + * @param exchangeRequest a TokenExchangeRequest + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the TokenResponse object + */ + CompletableFuture exchangeToken( + String userId, + String connectionName, + String channelId, + TokenExchangeRequest exchangeRequest + ); + /** * * @param userId the String value diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java index 742b48623..2f19d4145 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestBotSignIn.java @@ -3,21 +3,23 @@ * Licensed under the MIT License. See License.txt in the project root for * license information. */ - package com.microsoft.bot.connector.rest; +import com.google.common.reflect.TypeToken; import com.microsoft.bot.connector.Async; import retrofit2.Retrofit; import com.microsoft.bot.connector.BotSignIn; import com.microsoft.bot.restclient.ServiceResponse; -import java.util.concurrent.CompletableFuture; +import com.microsoft.bot.schema.SignInResource; +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.CompletableFuture; import okhttp3.ResponseBody; import retrofit2.http.GET; import retrofit2.http.Headers; import retrofit2.http.Query; import retrofit2.Response; - /** * An instance of this class provides access to all the operations defined in * BotSignIns. @@ -28,7 +30,6 @@ public class RestBotSignIn implements BotSignIn { private BotSignInsService service; /** The service client containing this operation class. */ private RestOAuthClient client; - /** * Initializes an instance of BotSignInsImpl. * @@ -40,7 +41,6 @@ public RestBotSignIn(Retrofit withRetrofit, RestOAuthClient withClient) { this.service = withRetrofit.create(BotSignInsService.class); this.client = withClient; } - /** * The interface defining all the services for BotSignIns to be used by Retrofit * to perform actually REST calls. @@ -56,8 +56,16 @@ CompletableFuture> getSignInUrl( @Query("emulatorUrl") String emulatorUrl, @Query("finalRedirect") String finalRedirect ); + @Headers({"Content-Type: application/json; charset=utf-8", + "x-ms-logging-context: com.microsoft.bot.schema.BotSignIns GetSignInResource"}) + @GET("api/botsignin/GetSignInResource") + CompletableFuture> getSignInResource( + @Query("state") String state, + @Query("code_challenge") String codeChallenge, + @Query("emulatorUrl") String emulatorUrl, + @Query("finalRedirect") String finalRedirect + ); } - /** * * @param state the String value @@ -70,7 +78,6 @@ public CompletableFuture getSignInUrl(String state) { "Parameter state is required and cannot be null." )); } - final String codeChallenge = null; final String emulatorUrl = null; final String finalRedirect = null; @@ -85,7 +92,6 @@ public CompletableFuture getSignInUrl(String state) { } }); } - /** * * @param state the String value @@ -106,7 +112,6 @@ public CompletableFuture getSignInUrl( "Parameter state is required and cannot be null." )); } - return service.getSignInUrl(state, codeChallenge, emulatorUrl, finalRedirect) .thenApply(responseBodyResponse -> { try { @@ -118,14 +123,87 @@ public CompletableFuture getSignInUrl( } }); } - private ServiceResponse getSignInUrlDelegate( Response response ) throws ErrorResponseException, IllegalArgumentException { if (!response.isSuccessful()) { throw new ErrorResponseException("getSignInUrl", response); } - return new ServiceResponse<>(response.body().source().buffer().readUtf8(), response); } + /** + * + * @param state the String value + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the String object + */ + public CompletableFuture getSignInResource(String state) { + if (state == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter state is required and cannot be null." + )); + } + final String codeChallenge = null; + final String emulatorUrl = null; + final String finalRedirect = null; + return service.getSignInResource(state, codeChallenge, emulatorUrl, finalRedirect) + .thenApply(responseBodyResponse -> { + try { + return getSignInResourceDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("getSignInResource", responseBodyResponse); + } + }); + } + /** + * + * @param state the String value + * @param codeChallenge the String value + * @param emulatorUrl the String value + * @param finalRedirect the String value + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the String object + */ + public CompletableFuture getSignInResource( + String state, + String codeChallenge, + String emulatorUrl, + String finalRedirect + ) { + if (state == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter state is required and cannot be null." + )); + } + return service.getSignInResource(state, codeChallenge, emulatorUrl, finalRedirect) + .thenApply(responseBodyResponse -> { + try { + return getSignInResourceDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("getSignInResource", responseBodyResponse); + } + }); + } + private ServiceResponse getSignInResourceDelegate( + Response response + ) throws ErrorResponseException, IllegalArgumentException, IOException { + if (!response.isSuccessful()) { + throw new ErrorResponseException("getSignInResource", response); + } + return this.client.restClient() + .responseBuilderFactory() + .newInstance(client.serializerAdapter()) + .register(HttpURLConnection.HTTP_OK, new TypeToken() { + }.getType()) + .register(HttpURLConnection.HTTP_MOVED_PERM, new TypeToken() { + }.getType()) + .register(HttpURLConnection.HTTP_MOVED_TEMP, new TypeToken() { + }.getType()) + .registerError(ErrorResponseException.class) + .build(response); + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java index 40821b4a0..bb7c7c27c 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java @@ -11,6 +11,7 @@ import com.microsoft.bot.connector.UserToken; import com.google.common.reflect.TypeToken; import com.microsoft.bot.schema.AadResourceUrls; +import com.microsoft.bot.schema.TokenExchangeRequest; import com.microsoft.bot.schema.TokenResponse; import com.microsoft.bot.schema.TokenStatus; import com.microsoft.bot.restclient.ServiceResponse; @@ -68,6 +69,17 @@ CompletableFuture> getToken( @Query("code") String code ); + @Headers({ "Content-Type: application/json; charset=utf-8", + "x-ms-logging-context: com.microsoft.bot.schema.UserTokens exchangeToken" }) + @GET("api/usertoken/GetToken") + CompletableFuture> exchangeToken( + @Query("userId") String userId, + @Query("connectionName") String connectionName, + @Query("channelId") String channelId, + @Body TokenExchangeRequest exchangeRequest + ); + + @Headers({ "Content-Type: application/json; charset=utf-8", "x-ms-logging-context: com.microsoft.bot.schema.UserTokens getAadTokens" }) @POST("api/usertoken/GetAadTokens") @@ -191,6 +203,71 @@ private ServiceResponse getTokenDelegate( .build(response); } + /** + * + * @param userId the String value + * @param connectionName the String value + * @param channelId the String value + * @param exchangeRequest a TokenExchangeRequest + * @throws IllegalArgumentException thrown if parameters fail the validation + * @return the observable to the TokenResponse object + */ + @Override + public CompletableFuture exchangeToken( + String userId, + String connectionName, + String channelId, + TokenExchangeRequest exchangeRequest + ) { + if (userId == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter userId is required and cannot be null." + )); + } + if (connectionName == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter connectionName is required and cannot be null." + )); + } + if (channelId == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter channelId is required and cannot be null." + )); + } + if (exchangeRequest == null) { + return Async.completeExceptionally(new IllegalArgumentException( + "Parameter exchangeRequest is required and cannot be null." + )); + } + + return service.exchangeToken(userId, connectionName, channelId, exchangeRequest) + .thenApply(responseBodyResponse -> { + try { + return exchangeTokenDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("getToken", responseBodyResponse); + } + }); + } + + private ServiceResponse exchangeTokenDelegate( + Response response + ) throws ErrorResponseException, IOException, IllegalArgumentException { + + return this.client.restClient() + .responseBuilderFactory() + .newInstance(this.client.serializerAdapter()) + + .register(HttpURLConnection.HTTP_OK, new TypeToken() { + }.getType()) + .register(HttpURLConnection.HTTP_NOT_FOUND, new TypeToken() { + }.getType()) + .registerError(ErrorResponseException.class) + .build(response); + } + /** * * @param userId the String value diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java new file mode 100644 index 000000000..d6192c74e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java @@ -0,0 +1,761 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.dialogs.prompts; + +import java.net.HttpURLConnection; +import java.time.Duration; +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.time.temporal.ChronoUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.BotAssert; +import com.microsoft.bot.builder.ConnectorClientBuilder; +import com.microsoft.bot.builder.InvokeResponse; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnStateConstants; +import com.microsoft.bot.builder.UserTokenProvider; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.connector.ConnectorClient; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.bot.schema.OAuthCard; +import com.microsoft.bot.schema.SignInConstants; +import com.microsoft.bot.schema.SignInResource; +import com.microsoft.bot.schema.SigninCard; +import com.microsoft.bot.schema.TokenExchangeInvokeRequest; +import com.microsoft.bot.schema.TokenExchangeInvokeResponse; +import com.microsoft.bot.schema.TokenExchangeRequest; +import com.microsoft.bot.schema.TokenResponse; + +import org.apache.commons.lang3.StringUtils; + +/** + * Creates a new prompt that asks the user to sign in using the Bot Frameworks + * Single Sign On (SSO)service. + * + * The prompt will attempt to retrieve the users current token and if the user + * isn't signed in, itwill send them an `OAuthCard` containing a button they can + * press to signin. Depending on thechannel, the user will be sent through one + * of two possible signin flows:- The automatic signin flow where once the user + * signs in and the SSO service will forward the botthe users access token using + * either an `event` or `invoke` activity.- The "magic code" flow where once the + * user signs in they will be prompted by the SSOservice to send the bot a six + * digit code confirming their identity. This code will be sent as astandard + * `message` activity. Both flows are automatically supported by the + * `OAuthPrompt` and the only thing you need to becareful of is that you don't + * block the `event` and `invoke` activities that the prompt mightbe waiting on. + * **Note**:You should avoid persisting the access token with your bots other + * state. The Bot FrameworksSSO service will securely store the token on your + * behalf. If you store it in your bots stateit could expire or be revoked in + * between turns. When calling the prompt from within a waterfall step you + * should use the token within the stepfollowing the prompt and then let the + * token go out of scope at the end of your function. + */ +public class OAuthPrompt extends Dialog { + + private static final String PERSISTED_OPTIONS = "options"; + private static final String PERSISTED_STATE = "state"; + private static final String PERSISTED_EXPIRES = "expires"; + private static final String PERSISTED_CALLER = "caller"; + + private final OAuthPromptSettings settings; + private final PromptValidator validator; + + /** + * Initializes a new instance of the {@link OAuthPrompt} class. + * + * @param dialogId The D to assign to this prompt. + * @param settings Additional OAuth settings to use with this instance of the + * prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public OAuthPrompt(String dialogId, OAuthPromptSettings settings) { + this(dialogId, settings, null); + } + + /** + * Initializes a new instance of the {@link OAuthPrompt} class. + * + * @param dialogId The D to assign to this prompt. + * @param settings Additional OAuth settings to use with this instance of the + * prompt. + * @param validator Optional, a {@link PromptValidator{FoundChoice}} that + * contains additional, custom validation for this prompt. + * + * The value of {@link dialogId} must be unique within the + * {@link DialogSet} or {@link ComponentDialog} to which the + * prompt is added. + */ + public OAuthPrompt(String dialogId, OAuthPromptSettings settings, PromptValidator validator) { + super(dialogId); + + if (StringUtils.isEmpty(dialogId)) { + throw new IllegalArgumentException("dialogId cannot be null."); + } + + if (settings == null) { + throw new IllegalArgumentException("settings cannot be null."); + } + + this.settings = settings; + this.validator = validator; + } + + /** + * Shared implementation of the SendOAuthCard function. This is intended for + * internal use, to consolidate the implementation of the OAuthPrompt and + * OAuthInput. Application logic should use those dialog classes. + * + * @param settings OAuthSettings. + * @param turnContext TurnContext. + * @param prompt MessageActivity. + * + * @return A {@link CompletableFuture} representing the result of the hronous + * operation. + */ + public static CompletableFuture sendOAuthCard(OAuthPromptSettings settings, TurnContext turnContext, + Activity prompt) { + BotAssert.contextNotNull(turnContext); + + BotAdapter adapter = turnContext.getAdapter(); + + if (!(adapter instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException("OAuthPrompt.Prompt(): not supported by the current adapter")); + } + + UserTokenProvider tokenAdapter = (UserTokenProvider) adapter; + + // Ensure prompt initialized + if (prompt == null) { + prompt = Activity.createMessageActivity(); + } + + if (prompt.getAttachments() == null) { + prompt.setAttachments(new ArrayList<>()); + } + + // Append appropriate card if missing + if (!channelSupportsOAuthCard(turnContext.getActivity().getChannelId())) { + if (!prompt.getAttachments().stream().anyMatch(s -> s.getContent() instanceof SigninCard)) { + SignInResource signInResource = tokenAdapter + .getSignInResource(turnContext, settings.getOAuthAppCredentials(), settings.getConnectionName(), + turnContext.getActivity().getFrom().getId(), null) + .join(); + + CardAction cardAction = new CardAction(); + cardAction.setTitle(settings.getTitle()); + cardAction.setValue(signInResource.getSignInLink()); + cardAction.setType(ActionTypes.SIGNIN); + + ArrayList cardList = new ArrayList(); + cardList.add(cardAction); + + SigninCard signInCard = new SigninCard(); + signInCard.setText(settings.getText()); + signInCard.setButtons(cardList); + + Attachment attachment = new Attachment(); + attachment.setContentType(SigninCard.CONTENTTYPE); + attachment.setContent(signInCard); + + prompt.getAttachments().add(attachment); + + } + } else if (!prompt.getAttachments().stream().anyMatch(s -> s.getContent() instanceof OAuthCard)) { + ActionTypes cardActionType = ActionTypes.SIGNIN; + SignInResource signInResource = tokenAdapter + .getSignInResource(turnContext, settings.getOAuthAppCredentials(), settings.getConnectionName(), + turnContext.getActivity().getFrom().getId(), null) + .join(); + String value = signInResource.getSignInLink(); + + // use the SignInLink when + // in speech channel or + // bot is a skill or + // an extra OAuthAppCredentials is being passed in + ClaimsIdentity botIdentity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (turnContext.getActivity().isFromStreamingConnection() + || botIdentity != null && SkillValidation.isSkillClaim(botIdentity.claims()) + || settings.getOAuthAppCredentials() != null) { + if (turnContext.getActivity().getChannelId().equals(Channels.EMULATOR)) { + cardActionType = ActionTypes.OPEN_URL; + } + } else if (!channelRequiresSignInLink(turnContext.getActivity().getChannelId())) { + value = null; + } + + CardAction cardAction = new CardAction(); + cardAction.setTitle(settings.getTitle()); + cardAction.setText(settings.getText()); + cardAction.setType(cardActionType); + cardAction.setValue(value); + + ArrayList cardList = new ArrayList(); + cardList.add(cardAction); + + OAuthCard oAuthCard = new OAuthCard(); + oAuthCard.setText(settings.getText()); + oAuthCard.setButtons(cardList); + oAuthCard.setConnectionName(settings.getConnectionName()); + oAuthCard.setTokenExchangeResource(signInResource.getTokenExchangeResource()); + + Attachment attachment = new Attachment(); + attachment.setContentType(OAuthCard.CONTENTTYPE); + attachment.setContent(oAuthCard); + + prompt.getAttachments().add(attachment); + } + + // Add the login timeout specified in OAuthPromptSettings to TurnState so it can + // be referenced if polling is needed + if (!turnContext.getTurnState().containsKey(TurnStateConstants.OAUTH_LOGIN_TIMEOUT_KEY) + && settings.getTimeout() != null) { + turnContext.getTurnState().add(TurnStateConstants.OAUTH_LOGIN_TIMEOUT_KEY, + Duration.ofMillis(settings.getTimeout())); + } + + // Set input hint + if (prompt.getInputHint() == null) { + prompt.setInputHint(InputHints.ACCEPTING_INPUT); + } + + return turnContext.sendActivity(prompt).thenApply(result -> null); + } + + /** + * Shared implementation of the RecognizeToken function. This is intended for internal use, to + * consolidate the implementation of the OAuthPrompt and OAuthInput. Application logic should + * use those dialog classes. + * + * @param settings OAuthPromptSettings. + * @param dc DialogContext. + * + * @return PromptRecognizerResult. + */ + @SuppressWarnings({"checkstyle:MethodLength", "PMD.EmptyCatchBlock"}) + public static CompletableFuture> recognizeToken( + OAuthPromptSettings settings, + DialogContext dc) { + TurnContext turnContext = dc.getContext(); + PromptRecognizerResult result = new PromptRecognizerResult(); + if (isTokenResponseEvent(turnContext)) { + Object tokenResponseObject = turnContext.getActivity().getValue(); + TokenResponse token = null; + if (tokenResponseObject != null) { + token = (TokenResponse) tokenResponseObject; + } + result.setSucceeded(true); + result.setValue(token); + + // fixup the turnContext's state context if this was received from a skill host caller + CallerInfo callerInfo = (CallerInfo) dc.getActiveDialog().getState().get(PERSISTED_CALLER); + if (callerInfo != null) { + // set the ServiceUrl to the skill host's Url + dc.getContext().getActivity().setServiceUrl(callerInfo.getCallerServiceUrl()); + + Object adapter = turnContext.getAdapter(); + // recreate a ConnectorClient and set it in TurnState so replies use the correct one + if (!(adapter instanceof ConnectorClientBuilder)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt: ConnectorClientProvider interface not implemented by the current adapter" + )); + } + + ConnectorClientBuilder connectorClientProvider = (ConnectorClientBuilder) adapter; + ClaimsIdentity claimsIdentity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + ConnectorClient connectorClient = connectorClientProvider.createConnectorClient( + dc.getContext().getActivity().getServiceUrl(), + claimsIdentity, + callerInfo.getScope()).join(); + + if (turnContext.getTurnState().get(ConnectorClient.class) != null) { + turnContext.getTurnState().replace(connectorClient); + } else { + turnContext.getTurnState().add(connectorClient); + } + } + } else if (isTeamsVerificationInvoke(turnContext)) { + String magicCode = (String) turnContext.getActivity().getValue(); + //var magicCode = magicCodeObject.GetValue("state", StringComparison.Ordinal)?.toString(); + + Object adapterObject = turnContext.getAdapter(); + if (!(adapterObject instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.Recognize(): not supported by the current adapter" + )); + } + + UserTokenProvider adapter = (UserTokenProvider) adapterObject; + + // Getting the token follows a different flow in Teams. At the signin completion, Teams + // will send the bot an "invoke" activity that contains a "magic" code. This code MUST + // then be used to try fetching the token from Botframework service within some time + // period. We try here. If it succeeds, we return 200 with an empty body. If it fails + // with a retriable error, we return 500. Teams will re-send another invoke in this case. + // If it fails with a non-retriable error, we return 404. Teams will not (still work in + // progress) retry in that case. + try { + TokenResponse token = adapter.getUserToken( + turnContext, + settings.getOAuthAppCredentials(), + settings.getConnectionName(), + magicCode).join(); + + if (token != null) { + result.setSucceeded(true); + result.setValue(token); + + turnContext.sendActivity(new Activity(ActivityTypes.INVOKE_RESPONSE)); + } else { + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_NOT_FOUND, null); + } + } catch (Exception e) { + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_INTERNAL_ERROR, null); + } + } else if (isTokenExchangeRequestInvoke(turnContext)) { + TokenExchangeInvokeRequest tokenExchangeRequest = + turnContext.getActivity().getValue() instanceof TokenExchangeInvokeRequest + ? (TokenExchangeInvokeRequest) turnContext.getActivity().getValue() : null; + + if (tokenExchangeRequest == null) { + TokenExchangeInvokeResponse response = new TokenExchangeInvokeResponse(); + response.setId(null); + response.setConnectionName(settings.getConnectionName()); + response.setFailureDetail("The bot received an InvokeActivity that is missing a " + + "TokenExchangeInvokeRequest value. This is required to be " + + "sent with the InvokeActivity."); + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_BAD_REQUEST, response).join(); + } else if (tokenExchangeRequest.getConnectionName() != settings.getConnectionName()) { + TokenExchangeInvokeResponse response = new TokenExchangeInvokeResponse(); + response.setId(tokenExchangeRequest.getId()); + response.setConnectionName(settings.getConnectionName()); + response.setFailureDetail("The bot received an InvokeActivity with a " + + "TokenExchangeInvokeRequest containing a ConnectionName that does not match the " + + "ConnectionName expected by the bot's active OAuthPrompt. Ensure these names match " + + "when sending the InvokeActivityInvalid ConnectionName in the " + + "TokenExchangeInvokeRequest"); + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_BAD_REQUEST, response).join(); + } else if (!(turnContext.getAdapter() instanceof UserTokenProvider)) { + TokenExchangeInvokeResponse response = new TokenExchangeInvokeResponse(); + response.setId(tokenExchangeRequest.getId()); + response.setConnectionName(settings.getConnectionName()); + response.setFailureDetail("The bot's BotAdapter does not support token exchange " + + "operations. Ensure the bot's Adapter supports the UserTokenProvider interface."); + + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_BAD_REQUEST, response).join(); + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.Recognize(): not supported by the current adapter" + )); + } else { + TokenResponse tokenExchangeResponse = null; + try { + UserTokenProvider adapter = (UserTokenProvider) turnContext.getAdapter(); + TokenExchangeRequest tokenExchangeReq = new TokenExchangeRequest(); + tokenExchangeReq.setToken(tokenExchangeRequest.getToken()); + tokenExchangeResponse = adapter.exchangeToken( + turnContext, + settings.getConnectionName(), + turnContext.getActivity().getFrom().getId(), + tokenExchangeReq).join(); + } catch (Exception ex) { + // Ignore Exceptions + // If token exchange failed for any reason, tokenExchangeResponse above stays null, and + // hence we send back a failure invoke response to the caller. + // This ensures that the caller shows + } + + if (tokenExchangeResponse == null || StringUtils.isBlank(tokenExchangeResponse.getToken())) { + TokenExchangeInvokeResponse tokenEIR = new TokenExchangeInvokeResponse(); + tokenEIR.setId(tokenExchangeRequest.getId()); + tokenEIR.setConnectionName(tokenExchangeRequest.getConnectionName()); + tokenEIR.setFailureDetail("The bot is unable to exchange token. Proceed with regular login."); + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_PRECON_FAILED, tokenEIR).join(); + } else { + TokenExchangeInvokeResponse tokenEIR = new TokenExchangeInvokeResponse(); + tokenEIR.setId(tokenExchangeRequest.getId()); + tokenEIR.setConnectionName(settings.getConnectionName()); + sendInvokeResponse(turnContext, HttpURLConnection.HTTP_OK, tokenEIR); + + result.setSucceeded(true); + TokenResponse finalResponse = tokenExchangeResponse; + result.setValue(new TokenResponse() { { + setChannelId(finalResponse.getChannelId()); + setConnectionName(finalResponse.getConnectionName()); + setToken(finalResponse.getToken()); + }}); + } + } + } else if (turnContext.getActivity().getType().equals(ActivityTypes.MESSAGE)) { + // regex to check if code supplied is a 6 digit numerical code (hence, a magic code). + String pattern = "(\\d{6})"; + Pattern r = Pattern.compile(pattern); + Matcher m = r.matcher(turnContext.getActivity().getText()); + + if (m.find()) { + if (!(turnContext.getAdapter() instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.Recognize(): not supported by the current adapter" + )); + } + UserTokenProvider adapter = (UserTokenProvider) turnContext.getAdapter(); + TokenResponse token = adapter.getUserToken(turnContext, + settings.getOAuthAppCredentials(), + settings.getConnectionName(), + m.group(0)).join(); + if (token != null) { + result.setSucceeded(true); + result.setValue(token); + } + } + } + + return CompletableFuture.completedFuture(result); + } + + /** + * Shared implementation of the SetCallerInfoInDialogState function. This is + * intended for internal use, to consolidate the implementation of the + * OAuthPrompt and OAuthInput. Application logic should use those dialog + * classes. + * + * @param state The dialog state. + * @param context TurnContext. + */ + public static void setCallerInfoInDialogState(Map state, TurnContext context) { + state.put(PERSISTED_CALLER, createCallerInfo(context)); + } + + /** + * Called when a prompt dialog is pushed onto the dialog stack and is being activated. + * + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being started. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the prompt is still active after the + * turn has been processed by the prompt. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (dc == null) { + return Async.completeExceptionally( + new IllegalArgumentException( + "dc cannot be null." + )); + } + + if (options != null && !(options instanceof PromptOptions)) { + return Async.completeExceptionally( + new IllegalArgumentException( + "Parameter options should be an instance of to PromptOptions if provided." + )); + } + + PromptOptions opt = (PromptOptions) options; + if (opt != null) { + // Ensure prompts have input hint set + if (opt.getPrompt() != null && opt.getPrompt().getInputHint() == null) { + opt.getPrompt().setInputHint(InputHints.ACCEPTING_INPUT); + } + + if (opt.getRetryPrompt() != null && opt.getRetryPrompt() == null) { + opt.getRetryPrompt().setInputHint(InputHints.ACCEPTING_INPUT); + } + } + + // Initialize state + int timeout = settings.getTimeout() != null ? settings.getTimeout() + : (int) TurnStateConstants.OAUTH_LOGIN_TIMEOUT_VALUE.toMillis(); + Map state = dc.getActiveDialog().getState(); + state.put(PERSISTED_OPTIONS, opt); + HashMap hMap = new HashMap(); + hMap.put(Prompt.ATTEMPTCOUNTKEY, 0); + state.put(PERSISTED_STATE, hMap); + + state.put(PERSISTED_EXPIRES, OffsetDateTime.now(ZoneId.of("UTC")).plus(timeout, ChronoUnit.MILLIS)); + setCallerInfoInDialogState(state, dc.getContext()); + + // Attempt to get the users token + if (!(dc.getContext().getAdapter() instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.Recognize(): not supported by the current adapter" + )); + } + + UserTokenProvider adapter = (UserTokenProvider) dc.getContext().getAdapter(); + TokenResponse output = adapter.getUserToken(dc.getContext(), + settings.getOAuthAppCredentials(), + settings.getConnectionName(), + null).join(); + if (output != null) { + // Return token + return dc.endDialog(output); + } + + // Prompt user to login + sendOAuthCard(settings, dc.getContext(), opt != null ? opt.getPrompt() : null).join(); + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Called when a prompt dialog is the active dialog and the user replied with a new activity. + * + * @param dc The dialog context for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is still active after the + * turn has been processed by the dialog. The prompt generally continues to receive the user's + * replies until it accepts the user's reply as valid input for the prompt. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (dc == null) { + return Async.completeExceptionally( + new IllegalArgumentException( + "dc cannot be null." + )); + } + + // Check for timeout + Map state = dc.getActiveDialog().getState(); + OffsetDateTime expires = (OffsetDateTime) state.get(PERSISTED_EXPIRES); + boolean isMessage = dc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE); + + // If the incoming Activity is a message, or an Activity Type normally handled by OAuthPrompt, + // check to see if this OAuthPrompt Expiration has elapsed, and end the dialog if so. + boolean isTimeoutActivityType = isMessage + || isTokenResponseEvent(dc.getContext()) + || isTeamsVerificationInvoke(dc.getContext()) + || isTokenExchangeRequestInvoke(dc.getContext()); + + + boolean hasTimedOut = isTimeoutActivityType && OffsetDateTime.now(ZoneId.of("UTC")).compareTo(expires) > 0; + + if (hasTimedOut) { + // if the token fetch request times out, complete the prompt with no result. + return dc.endDialog(); + } + + // Recognize token + PromptRecognizerResult recognized = recognizeToken(settings, dc).join(); + + Map promptState = (Map) state.get(PERSISTED_STATE); + PromptOptions promptOptions = (PromptOptions) state.get(PERSISTED_OPTIONS); + + // Increment attempt count + // Convert.ToInt32 For issue https://github.com/Microsoft/botbuilder-dotnet/issues/1859 + promptState.put(Prompt.ATTEMPTCOUNTKEY, (int) promptState.get(Prompt.ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + boolean isValid = false; + if (validator != null) { + PromptValidatorContext promptContext = new PromptValidatorContext( + dc.getContext(), + recognized, + promptState, + promptOptions); + isValid = validator.promptValidator(promptContext).join(); + } else if (recognized.getSucceeded()) { + isValid = true; + } + + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } else if (isMessage && settings.getEndOnInvalidMessage()) { + // If EndOnInvalidMessage is set, complete the prompt with no result. + return dc.endDialog(); + } + + if (!dc.getContext().getResponded() + && isMessage + && promptOptions != null + && promptOptions.getRetryPrompt() != null) { + dc.getContext().sendActivity(promptOptions.getRetryPrompt()); + } + + return CompletableFuture.completedFuture(END_OF_TURN); + } + + /** + * Attempts to get the user's token. + * + * @param turnContext Context for the current turn of conversation with the + * user. + * + * @return A task that represents the work queued to execute. + * + * If the task is successful and user already has a token or the user + * successfully signs in, the result contains the user's token. + */ + public CompletableFuture getUserToken(TurnContext turnContext) { + if (!(turnContext.getAdapter() instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.GetUserToken(): not supported by the current adapter" + )); + } + return ((UserTokenProvider) turnContext.getAdapter()).getUserToken(turnContext, + settings.getOAuthAppCredentials(), + settings.getConnectionName(), null); + } + + /** + * Signs out the user. + * + * @param turnContext Context for the current turn of conversation with the user. + * + * @return A task that represents the work queued to execute. + */ + public CompletableFuture signOutUser(TurnContext turnContext) { + if (!(turnContext.getAdapter() instanceof UserTokenProvider)) { + return Async.completeExceptionally( + new UnsupportedOperationException( + "OAuthPrompt.SignOutUser(): not supported by the current adapter" + )); + } + String id = ""; + if (turnContext.getActivity() != null + && turnContext.getActivity() != null + && turnContext.getActivity().getFrom() != null) { + id = turnContext.getActivity().getFrom().getId(); + } + + // Sign out user + return ((UserTokenProvider) turnContext.getAdapter()).signOutUser(turnContext, + settings.getOAuthAppCredentials(), + settings.getConnectionName(), + id); + } + + private static CallerInfo createCallerInfo(TurnContext turnContext) { + ClaimsIdentity botIdentity = + turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY) instanceof ClaimsIdentity + ? (ClaimsIdentity) turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY) + : null; + + if (botIdentity != null && SkillValidation.isSkillClaim(botIdentity.claims())) { + CallerInfo callerInfo = new CallerInfo(); + callerInfo.setCallerServiceUrl(turnContext.getActivity().getServiceUrl()); + callerInfo.setScope(JwtTokenValidation.getAppIdFromClaims(botIdentity.claims())); + + return callerInfo; + } + + return null; + } + + private static boolean isTokenResponseEvent(TurnContext turnContext) { + Activity activity = turnContext.getActivity(); + return activity.getType().equals(ActivityTypes.EVENT) + && activity.getName().equals(SignInConstants.TOKEN_RESPONSE_EVENT_NAME); + } + + private static boolean isTeamsVerificationInvoke(TurnContext turnContext) { + Activity activity = turnContext.getActivity(); + return activity.getType().equals(ActivityTypes.INVOKE) + && activity.getName().equals(SignInConstants.VERIFY_STATE_OPERATION_NAME); + } + + private static boolean isTokenExchangeRequestInvoke(TurnContext turnContext) { + Activity activity = turnContext.getActivity(); + return activity.getType().equals(ActivityTypes.INVOKE) + && activity.getName().equals(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + } + + private static boolean channelSupportsOAuthCard(String channelId) { + switch (channelId) { + case Channels.CORTANA: + case Channels.SKYPE: + case Channels.SKYPEFORBUSINESS: + return false; + default: + return true; + } + } + + private static boolean channelRequiresSignInLink(String channelId) { + switch (channelId) { + case Channels.MSTEAMS: + return true; + default: + return false; + } + } + + private static CompletableFuture sendInvokeResponse(TurnContext turnContext, int statusCode, + Object body) { + Activity activity = new Activity(ActivityTypes.INVOKE_RESPONSE); + activity.setValue(new InvokeResponse(statusCode, body)); + return turnContext.sendActivity(activity).thenApply(result -> null); + } + + /** + * Class to contain CallerInfo data including callerServiceUrl and scope. + */ + private static class CallerInfo { + + private String callerServiceUrl; + + private String scope; + + /** + * @return the CallerServiceUrl value as a String. + */ + public String getCallerServiceUrl() { + return this.callerServiceUrl; + } + + /** + * @param withCallerServiceUrl The CallerServiceUrl value. + */ + public void setCallerServiceUrl(String withCallerServiceUrl) { + this.callerServiceUrl = withCallerServiceUrl; + } + /** + * @return the Scope value as a String. + */ + public String getScope() { + return this.scope; + } + + /** + * @param withScope The Scope value. + */ + public void setScope(String withScope) { + this.scope = withScope; + } + + } +} + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPromptSettings.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPromptSettings.java new file mode 100644 index 000000000..91e2ff436 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPromptSettings.java @@ -0,0 +1,145 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.dialogs.prompts; + +import com.microsoft.bot.connector.authentication.AppCredentials; + +/** + * Contains settings for an {@link OAuthPrompt}/>. + */ +public class OAuthPromptSettings { + + private AppCredentials oAuthAppCredentials; + + private String connectionName; + + private String title; + + private String text; + + private Integer timeout; + + private boolean endOnInvalidMessage; + + /** + * Gets the OAuthAppCredentials for OAuthPrompt. + * + * @return the OAuthAppCredentials value as a AppCredentials. + */ + public AppCredentials getOAuthAppCredentials() { + return this.oAuthAppCredentials; + } + + /** + * Sets the OAuthAppCredentials for OAuthPrompt. + * + * @param withOAuthAppCredentials The OAuthAppCredentials value. + */ + public void setOAuthAppCredentials(AppCredentials withOAuthAppCredentials) { + this.oAuthAppCredentials = withOAuthAppCredentials; + } + + /** + * Gets the name of the OAuth connection. + * + * @return the ConnectionName value as a String. + */ + public String getConnectionName() { + return this.connectionName; + } + + /** + * Sets the name of the OAuth connection. + * + * @param withConnectionName The ConnectionName value. + */ + public void setConnectionName(String withConnectionName) { + this.connectionName = withConnectionName; + } + + /** + * Gets the title of the sign-in card. + * + * @return the Title value as a String. + */ + public String getTitle() { + return this.title; + } + + /** + * Sets the title of the sign-in card. + * + * @param withTitle The Title value. + */ + public void setTitle(String withTitle) { + this.title = withTitle; + } + + /** + * Gets any additional text to include in the sign-in card. + * + * @return the Text value as a String. + */ + public String getText() { + return this.text; + } + + /** + * Sets any additional text to include in the sign-in card. + * + * @param withText The Text value. + */ + public void setText(String withText) { + this.text = withText; + } + + /** + * Gets the number of milliseconds the prompt waits for the user to + * authenticate. Default is 900,000 (15 minutes). + * + * @return the Timeout value as a int?. + */ + public Integer getTimeout() { + return this.timeout; + } + + /** + * Sets the number of milliseconds the prompt waits for the user to + * authenticate. Default is 900,000 (15 minutes). + * + * @param withTimeout The Timeout value. + */ + public void setTimeout(Integer withTimeout) { + this.timeout = withTimeout; + } + + /** + * Gets a value indicating whether the {@link OAuthPrompt} should end upon + * receiving an invalid message. Generally the {@link OAuthPrompt} will ignore + * incoming messages from the user during the auth flow, if they are not related + * to the auth flow. This flag enables ending the {@link OAuthPrompt} rather + * than ignoring the user's message. Typically, this flag will be set to 'true', + * but is 'false' by default for backwards compatibility. + * + * @return the EndOnInvalidMessage value as a boolean. + */ + public boolean getEndOnInvalidMessage() { + return this.endOnInvalidMessage; + } + + /** + * Sets a value indicating whether the {@link OAuthPrompt} should end upon + * receiving an invalid message. Generally the {@link OAuthPrompt} will ignore + * incoming messages from the user during the auth flow, if they are not related + * to the auth flow. This flag enables ending the {@link OAuthPrompt} rather + * than ignoring the user's message. Typically, this flag will be set to 'true', + * but is 'false' by default for backwards compatibility. + * + * @param withEndOnInvalidMessage The EndOnInvalidMessage value. + */ + public void setEndOnInvalidMessage(boolean withEndOnInvalidMessage) { + this.endOnInvalidMessage = withEndOnInvalidMessage; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java index 8188870f8..ee87f2967 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -103,14 +103,12 @@ public CompletableFuture beginDialog(DialogContext dc, Object // Ensure prompts have input hint set PromptOptions opt = (PromptOptions) options; - if (opt.getPrompt() != null && (opt.getPrompt().getInputHint() == null - || StringUtils.isEmpty(opt.getPrompt().getInputHint().toString()))) { - opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + if (opt.getPrompt() != null && opt.getPrompt().getInputHint() == null) { + opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); } - if (opt.getRetryPrompt() != null && (opt.getRetryPrompt().getInputHint() == null - || StringUtils.isEmpty(opt.getRetryPrompt().getInputHint().toString()))) { - opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + if (opt.getRetryPrompt() != null && opt.getRetryPrompt().getInputHint() == null) { + opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java new file mode 100644 index 000000000..8e07128c4 --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java @@ -0,0 +1,751 @@ +// Copyright (c) Microsoft Corporation. All rights reserved +// Licensed under the MT License + +package com.microsoft.bot.dialogs.prompts; + +import java.util.HashMap; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.InvokeResponse; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.bot.schema.OAuthCard; +import com.microsoft.bot.schema.SignInConstants; +import com.microsoft.bot.schema.TokenExchangeInvokeRequest; +import com.microsoft.bot.schema.TokenExchangeInvokeResponse; +import com.microsoft.bot.schema.TokenExchangeRequest; +import com.microsoft.bot.schema.TokenResponse; + +import org.junit.Assert; +import org.junit.Test; + +public class OAuthPromptTests { + + @Test + public void OAuthPromptWithEmptySettingsShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new OAuthPrompt("abc", null, null)); + } + + @Test + public void OAuthPromptWithEmptyIdShouldFail() { + Assert.assertThrows(IllegalArgumentException.class, () -> new OAuthPrompt("", new OAuthPromptSettings(), null)); + } + + @Test + public void OAuthPromptWithDefaultTypeHandlingForStorage() { + OAuthPrompt(new MemoryStorage()); + } + + @Test + public void OAuthPromptBeginDialogWithNoDialogContext() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + OAuthPrompt prompt = new OAuthPrompt("abc", new OAuthPromptSettings(), null); + prompt.beginDialog(null).join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void OAuthPromptBeginDialogWithWrongOptions() { + Assert.assertThrows(NullPointerException.class, () -> { + OAuthPrompt prompt = new OAuthPrompt("abc", new OAuthPromptSettings(), null); + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(prompt); + ConversationAccount conversation = new ConversationAccount(); + conversation.setId("123"); + TurnContextImpl tc = new TurnContextImpl(adapter, new Activity(ActivityTypes.MESSAGE) { + { + setConversation(conversation); + setChannelId("test"); + } + }); + + DialogContext dc = dialogs.createContext(tc).join(); + + prompt.beginDialog(dc).join(); + }); + } + + @Test + public void OAuthPromptWithNoneTypeHandlingForStorage() { + OAuthPrompt(new MemoryStorage(new HashMap())); + } + + @Test + public void OAuthPromptWithMagicCode() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String token = "abc123"; + String magicCode = "888999"; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + OAuthPrompt oAuthPrompt = new OAuthPrompt("OAuthPrompt", settings); + dialogs.add(oAuthPrompt); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + + // Add a magic code to the adapter + adapter.addUserToken(connectionName, activity.getChannelId(), + activity.getRecipient().getId(), + token, + magicCode); + }) + .send(magicCode) + .assertReply("Logged in.") + .startTest() + .join(); + } + + @Test + public void OAuthPromptTimesOut_Message() { + PromptTimeoutEndsDialogTest(MessageFactory.text("hi")); + } + + @Test + public void OAuthPromptTimesOut_TokenResponseEvent() { + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setName(SignInConstants.TOKEN_RESPONSE_EVENT_NAME); + activity.setValue(new TokenResponse(Channels.MSTEAMS, "connectionName", "token", null)); + PromptTimeoutEndsDialogTest(activity); + } + + @Test + public void OAuthPromptTimesOut_VerifyStateOperation() { + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName(SignInConstants.VERIFY_STATE_OPERATION_NAME); + activity.setValue("888999"); + PromptTimeoutEndsDialogTest(activity); + } + + @Test + public void OAuthPromptTimesOut_TokenExchangeOperation() { + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + + TokenExchangeInvokeRequest tokenExchangeRequest = new TokenExchangeInvokeRequest(); + tokenExchangeRequest.setConnectionName(connectionName); + tokenExchangeRequest.setToken(exchangeToken); + + activity.setValue(tokenExchangeRequest); + + PromptTimeoutEndsDialogTest(activity); + } + + @Test + public void OAuthPromptDoesNotDetectCodeInBeginDialog() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String token = "abc123"; + String magicCode = "888999"; + + // Create new DialogSet + DialogSet dialogs = new DialogSet(dialogState); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + // Add a magic code to the adapter preemptively so that we can test if the + // message that triggers BeginDialogAsync uses magic code detection + adapter.addUserToken(connectionName, turnContext.getActivity().getChannelId(), + turnContext.getActivity().getFrom().getId(), token, magicCode); + + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + + if (results.getStatus() == DialogTurnStatus.EMPTY) { + // If magicCode is detected when prompting, this will end the dialog and + // return the token in tokenResult + DialogTurnResult tokenResult = dc.prompt("OAuthPrompt", new PromptOptions()).join(); + if (tokenResult.getResult() instanceof TokenResponse) { + throw new RuntimeException(); + } + } + return CompletableFuture.completedFuture(null); + }; + + // Call BeginDialogAsync by sending the magic code as the first message. It + // SHOULD respond with an OAuthPrompt since we haven't authenticated yet + new TestFlow(adapter, botCallbackHandler) + .send(magicCode) + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + }) + .startTest() + .join(); + } + + @Test + public void OAuthPromptWithTokenExchangeInvoke() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + String token = "abc123"; + + // Create new DialogSet + DialogSet dialogs = new DialogSet(dialogState); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + + // Add an exchangable token to the adapter + adapter.addExchangeableToken(connectionName, activity.getChannelId(), + activity.getRecipient().getId(), exchangeToken, token); + }) + .send(new Activity(ActivityTypes.INVOKE) { + { + setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + setValue(new TokenExchangeInvokeRequest() { + { + setConnectionName(connectionName); + setToken(exchangeToken); + } + }); + }}) + .assertReply(a -> { + Assert.assertEquals("invokeResponse", a.getType()); + InvokeResponse response = (InvokeResponse) ((Activity)a).getValue(); + Assert.assertNotNull(response); + Assert.assertEquals(200, response.getStatus()); + TokenExchangeInvokeResponse body = (TokenExchangeInvokeResponse) response.getBody(); + Assert.assertEquals(connectionName, body.getConnectionName()); + Assert.assertNull(body.getFailureDetail()); + }) + .assertReply("Logged in.") + .startTest() + .join(); + } + + @Test + public void OAuthPromptWithTokenExchangeFail() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + + // Create new DialogSet + DialogSet dialogs = new DialogSet(dialogState); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + // No exchangable token is added to the adapter + }) + .send(new Activity(ActivityTypes.INVOKE) { + { + setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + setValue(new TokenExchangeInvokeRequest() { + { + setConnectionName(connectionName); + setToken(exchangeToken); + } + }); + }}) + .assertReply(a -> { + Assert.assertEquals("invokeResponse", a.getType()); + InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); + Assert.assertNotNull(response); + Assert.assertEquals(412, response.getStatus()); + TokenExchangeInvokeResponse body = (TokenExchangeInvokeResponse) response.getBody(); + Assert.assertEquals(connectionName, body.getConnectionName()); + Assert.assertNotNull(body.getFailureDetail()); + }) + .startTest() + .join(); + } + + @Test + public void OAuthPromptWithTokenExchangeNoBodyFails() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + + // Create new DialogSet + DialogSet dialogs = new DialogSet(dialogState); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + // No exchangable token is added to the adapter + }) + .send(new Activity(ActivityTypes.INVOKE) { + { + setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + } + }) + .assertReply(a -> { + Assert.assertEquals("invokeResponse", a.getType()); + InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); + Assert.assertNotNull(response); + Assert.assertEquals(400, response.getStatus()); + TokenExchangeInvokeResponse body = (TokenExchangeInvokeResponse) response.getBody(); + Assert.assertEquals(connectionName, body.getConnectionName()); + Assert.assertNotNull(body.getFailureDetail()); + }) + .startTest() + .join(); + } + + @Test + public void OAuthPromptWithTokenExchangeWrongConnectionNameFail() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + + // Create new DialogSet + DialogSet dialogs = new DialogSet(dialogState); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + // No exchangable token is added to the adapter + }) + .send(new Activity(ActivityTypes.INVOKE) { + { + setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + setValue(new TokenExchangeInvokeRequest() { + { + setConnectionName("beepboop"); + setToken(exchangeToken); + } + }); + }}) + .assertReply(a -> { + Assert.assertEquals("invokeResponse", a.getType()); + InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); + Assert.assertNotNull(response); + Assert.assertEquals(400, response.getStatus()); + TokenExchangeInvokeResponse body = (TokenExchangeInvokeResponse) response.getBody(); + Assert.assertEquals(connectionName, body.getConnectionName()); + Assert.assertNotNull(body.getFailureDetail()); + }) + .startTest() + .join(); + } + + @Test + public void TestAdapterTokenExchange() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + String token = "abc123"; + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + String userId = "fred"; + adapter.addExchangeableToken(connectionName, + turnContext.getActivity().getChannelId(), + userId, + exchangeToken, + token); + + // Positive case: Token + TokenResponse result = adapter.exchangeToken(turnContext, connectionName, userId, + new TokenExchangeRequest() {{ setToken(exchangeToken); }}).join(); + Assert.assertNotNull(result); + Assert.assertEquals(token, result.getToken()); + Assert.assertEquals(connectionName, result.getConnectionName()); + + // Positive case: URI + result = adapter.exchangeToken(turnContext, connectionName, userId, + new TokenExchangeRequest() { { setUri(exchangeToken); }}).join(); + Assert.assertNotNull(result); + Assert.assertEquals(token, result.getToken()); + Assert.assertEquals(connectionName, result.getConnectionName()); + + // Negative case: Token + result = adapter.exchangeToken(turnContext, connectionName, userId, + new TokenExchangeRequest() {{ setToken("beeboop"); }}).join(); + Assert.assertNull(result); + + // Negative case: URI + result = adapter.exchangeToken(turnContext, connectionName, userId, + new TokenExchangeRequest() {{ setUri("beeboop"); }}).join(); + Assert.assertNull(result); + + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .startTest() + .join(); + } + + private void OAuthPrompt(Storage storage) { + ConversationState convoState = new ConversationState(storage); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String token = "abc123"; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + OAuthPromptSettings oauthPromptSettings = new OAuthPromptSettings(); + oauthPromptSettings.setText("Please sign in"); + oauthPromptSettings.setConnectionName(connectionName); + oauthPromptSettings.setTitle("Sign in"); + dialogs.add(new OAuthPrompt("OAuthPrompt", oauthPromptSettings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity(MessageFactory.text("Logged in.")); + } else { + turnContext.sendActivity(MessageFactory.text("Failed.")); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler).send("hello").assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); + + // Prepare an EventActivity with a TokenResponse and send it to the + // botCallbackHandler + Activity eventActivity = createEventResponse(adapter, activity, connectionName, token); + TurnContextImpl ctx = new TurnContextImpl(adapter, (Activity) eventActivity); + botCallbackHandler.invoke(ctx); + }).assertReply("Logged in.").startTest().join(); + } + + private void PromptTimeoutEndsDialogTest(Activity oauthPromptActivity) { + ConversationState convoState = new ConversationState(new MemoryStorage()); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(convoState)); + + String connectionName = "myConnection"; + String exchangeToken = "exch123"; + String magicCode = "888999"; + String token = "abc123"; + + // Create new DialogSet. + DialogSet dialogs = new DialogSet(dialogState); + + // Set timeout to zero, so the prompt will end immediately. + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please sign in"); + settings.setConnectionName(connectionName); + settings.setTitle("Sign in"); + settings.setTimeout(0); + dialogs.add(new OAuthPrompt("OAuthPrompt", settings)); + + BotCallbackHandler botCallbackHandler = (turnContext) -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + + DialogTurnResult results = dc.continueDialog().join(); + if (results.getStatus() == DialogTurnStatus.EMPTY) { + dc.prompt("OAuthPrompt", new PromptOptions()).join(); + } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { + // If the TokenResponse comes back, the timeout did not occur. + if (results.getResult() instanceof TokenResponse) { + turnContext.sendActivity("failed").join(); + } else { + turnContext.sendActivity("ended").join(); + } + } + return CompletableFuture.completedFuture(null); + }; + + new TestFlow(adapter, botCallbackHandler) + .send("hello") + .assertReply(activity -> { + Assert.assertTrue(((Activity) activity).getAttachments().size() == 1); + Assert.assertEquals(OAuthCard.CONTENTTYPE, ((Activity) activity).getAttachments().get(0).getContentType()); + + // Add a magic code to the adapter + adapter.addUserToken(connectionName, + activity.getChannelId(), + activity.getRecipient().getId(), + token, + magicCode); + + // Add an exchangable token to the adapter + adapter.addExchangeableToken(connectionName, + activity.getChannelId(), + activity.getRecipient().getId(), + exchangeToken, + token); + }) + .send(oauthPromptActivity) + .assertReply("ended") + .startTest() + .join(); + } + + private Activity createEventResponse(TestAdapter adapter, Activity activity, String connectionName, String token) { + // add the token to the TestAdapter + adapter.addUserToken(connectionName, activity.getChannelId(), activity.getRecipient().getId(), token, null); + + // send an event TokenResponse activity to the botCallback handler + Activity eventActivity = ((Activity) activity).createReply(); + eventActivity.setType(ActivityTypes.EVENT); + ChannelAccount from = eventActivity.getFrom(); + eventActivity.setFrom(eventActivity.getRecipient()); + eventActivity.setRecipient(from); + eventActivity.setName(SignInConstants.TOKEN_RESPONSE_EVENT_NAME); + TokenResponse tokenResponse = new TokenResponse(); + tokenResponse.setConnectionName(connectionName); + tokenResponse.setToken(token); + eventActivity.setValue(tokenResponse); + + return eventActivity; + } + + // private void OAuthPromptEndOnInvalidMessageSetting() { + // var convoState = new ConversationState(new MemoryStorage()); + // var dialogState = convoState.CreateProperty("dialogState"); + + // var adapter = new TestAdapter() + // .Use(new AutoSaveStateMiddleware(convoState)); + + // var connectionName = "myConnection"; + + // // Create new DialogSet. + // var dialogs = new DialogSet(dialogState); + // dialogs.Add(new OAuthPrompt("OAuthPrompt", new OAuthPromptSettings() { Text = + // "Please sign in", ConnectionName = connectionName, Title = "Sign in", + // EndOnInvalidMessage = true })); + + // BotCallbackHandler botCallbackHandler = (turnContext) -> { + // var dc = dialogs.CreateContext(turnContext); + + // var results = dc.ContinueDialog(); + // if (results.Status == DialogTurnStatus.Empty) { + // dc.Prompt("OAuthPrompt", new PromptOptions()); + // } else if (results.Status == DialogTurnStatus.Waiting) { + // throw new InvalidOperationException("Test + // OAuthPromptEndOnInvalidMessageSetting expected DialogTurnStatus.Complete"); + // } else if (results.Status == DialogTurnStatus.Complete) { + // if (results.Result is TokenResponse) { + // turnContext.SendActivity(MessageFactory.Text("Logged in.")); + // } else { + // turnContext.SendActivity(MessageFactory.Text("Ended.")); + // } + // } + // }; + + // new TestFlow(adapter, botCallbackHandler) + // .send("hello") + // .assertReply(activity -> { + // Assert.Single(((Activity)activity).Attachments); + // Assert.Equal(OAuthCard.ContentType, + // ((Activity)activity).Attachments[0].ContentType); + // }) + // .send("blah") + // .assertReply("Ended.") + // .startTest(); + // } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java index 1fa140ddc..7524eafb0 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java @@ -38,6 +38,11 @@ public class OAuthCard { @JsonInclude(JsonInclude.Include.NON_EMPTY) private List buttons; + /** + * The resource to try to perform token exchange with. + */ + private TokenExchangeResource tokenExchangeResource; + /** * Get the text value. * @@ -103,7 +108,7 @@ public void setButtons(CardAction... withButtons) { /** * Creates an @{link Attachment} for this card. - * + * * @return An Attachment object containing the card. */ public Attachment toAttachment() { @@ -114,4 +119,20 @@ public Attachment toAttachment() { } }; } + + /** + * Gets the resource to try to perform token exchange with. + * @return The tokenExchangeResource value. + */ + public TokenExchangeResource getTokenExchangeResource() { + return tokenExchangeResource; + } + + /** + * Sets the resource to try to perform token exchange with. + * @param withExchangeResource The tokenExchangeResource value. + */ + public void setTokenExchangeResource(TokenExchangeResource withExchangeResource) { + tokenExchangeResource = withExchangeResource; + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInResource.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInResource.java new file mode 100644 index 000000000..a662d40d0 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SignInResource.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A type containing information for single sign-on. + */ +public class SignInResource { + + @JsonProperty(value = "signInLink") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String signInLink; + + @JsonProperty(value = "tokenExchangeResource") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private TokenExchangeResource tokenExchangeResource; + + /** + * Initializes a new instance of the SignInUrlResponse class. + */ + public SignInResource() { + customInit(); + } + + /** + * Initializes a new instance of the SignInUrlResponse class. + * @param signInLink the sign in link to initialize this instance to. + * @param tokenExchangeResource the tokenExchangeResource to initialize this instance to. + */ + public SignInResource(String signInLink, TokenExchangeResource tokenExchangeResource) { + this.signInLink = signInLink; + this.tokenExchangeResource = tokenExchangeResource; + customInit(); + } + + /** + * An initialization method that performs custom operations like setting. + * defaults + */ + void customInit() { + } + + /** + * The sign-in link. + * @return the SignInLink value as a String. + */ + public String getSignInLink() { + return this.signInLink; + } + + /** + * The sign-in link. + * @param withSignInLink The SignInLink value. + */ + public void setSignInLink(String withSignInLink) { + this.signInLink = withSignInLink; + } + /** + * Additional properties that cna be used for token exchange operations. + * @return the TokenExchangeResource value as a TokenExchangeResource. + */ + public TokenExchangeResource getTokenExchangeResource() { + return this.tokenExchangeResource; + } + + /** + * Additional properties that cna be used for token exchange operations. + * @param withTokenExchangeResource The TokenExchangeResource value. + */ + public void setTokenExchangeResource(TokenExchangeResource withTokenExchangeResource) { + this.tokenExchangeResource = withTokenExchangeResource; + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeRequest.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeRequest.java new file mode 100644 index 000000000..2db369daf --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeRequest.java @@ -0,0 +1,70 @@ +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A request to exchange a token. + */ +public class TokenExchangeInvokeRequest { + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "connectionName") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String connectionName; + + @JsonProperty(value = "token") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String token; + + /** + * Gets the id from the TokenExchangeInvokeRequest. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * Sets the id from the TokenExchangeInvokeRequest. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + + /** + * Gets the connection name. + * @return the ConnectionName value as a String. + */ + public String getConnectionName() { + return this.connectionName; + } + + /** + * Sets the connection name. + * @param withConnectionName The ConnectionName value. + */ + public void setConnectionName(String withConnectionName) { + this.connectionName = withConnectionName; + } + + /** + * Gets the details of why the token exchange failed. + * @return the Token value as a String. + */ + public String getToken() { + return this.token; + } + + /** + * Sets the details of why the token exchange failed. + * @param withToken The FailureDetail value. + */ + public void setToken(String withToken) { + this.token = withToken; + } + +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeResponse.java new file mode 100644 index 000000000..71012fcf9 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeInvokeResponse.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The response Object of a token exchange invoke. + */ +public class TokenExchangeInvokeResponse { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "connectionName") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String connectionName; + + @JsonProperty(value = "failureDetail") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String failureDetail; + + /** + * Gets the id from the TokenExchangeInvokeRequest. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * Sets the id from the TokenExchangeInvokeRequest. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + + /** + * Gets the connection name. + * @return the ConnectionName value as a String. + */ + public String getConnectionName() { + return this.connectionName; + } + + /** + * Sets the connection name. + * @param withConnectionName The ConnectionName value. + */ + public void setConnectionName(String withConnectionName) { + this.connectionName = withConnectionName; + } + + /** + * Gets the details of why the token exchange failed. + * @return the FailureDetail value as a String. + */ + public String getFailureDetail() { + return this.failureDetail; + } + + /** + * Sets the details of why the token exchange failed. + * @param withFailureDetail The FailureDetail value. + */ + public void setFailureDetail(String withFailureDetail) { + this.failureDetail = withFailureDetail; + } +} + diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeRequest.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeRequest.java new file mode 100644 index 000000000..07a7c17eb --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeRequest.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Request payload to be sent to the Bot Framework Token Service for Single Sign + * On.If the URI is set to a custom scope, then Token Service will exchange the + * token in its cache for a token targeting the custom scope and return it in + * the response.If a Token is sent in the payload, then Token Service will + * exchange the token for a token targetting the scopes specified in the + * corresponding OAauth connection. + */ +public class TokenExchangeRequest { + + @JsonProperty(value = "uri") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String uri; + + @JsonProperty(value = "token") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String token; + + + /** + * Initializes a new instance of the {@link TokenExchangeRequest} class. + */ + public TokenExchangeRequest() { + customInit(); + } + + /** + * Initializes a new instance of the TokenExchangeRequest class. + * @param uri The uri to intialize this instance with. + * @param token The token to initialize this instance with. + */ + public TokenExchangeRequest(String uri, String token) { + this.uri = uri; + this.token = token; + customInit(); + } + + /** + * An initialization method that performs custom operations like setting. + * defaults + */ + void customInit() { + + } + + /** + * Gets a URI String. + * @return the Uri value as a String. + */ + public String getUri() { + return this.uri; + } + + /** + * Sets a URI String. + * @param withUri The Uri value. + */ + public void setUri(String withUri) { + this.uri = withUri; + } + /** + * Gets a token String. + * @return the Token value as a String. + */ + public String getToken() { + return this.token; + } + + /** + * Sets a token String. + * @param withToken The Token value. + */ + public void setToken(String withToken) { + this.token = withToken; + } +} + diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeResource.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeResource.java new file mode 100644 index 000000000..df4c55462 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeResource.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Response schema sent back from Bot Framework Token Service required to + * initiate a user single sign on. + */ + public class TokenExchangeResource { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "uri") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String uri; + + @JsonProperty(value = "providerId") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String providerId; + + /** + * Initializes a new instance of the TokenExchangeResource class. + */ + public TokenExchangeResource() { + customInit(); + } + + /** + * Initializes a new instance of the TokenExchangeResource class. + * @param id The id to initialize this instance to. + * @param uri The uri to initialize this instance to. + * @param providerId the providerId to initialize this instance to. + */ + public TokenExchangeResource(String id, String uri, String providerId) { + this.id = id; + this.uri = uri; + this.providerId = providerId; + customInit(); + } + + /** + * An initialization method that performs custom operations like setting + * defaults. + */ + void customInit() { + } + + /** + * A unique identifier for this token exchange instance. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * A unique identifier for this token exchange instance. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + /** + * The application D / resource identifier with which to exchange a token. + * on behalf of + * @return the Uri value as a String. + */ + public String getUri() { + return this.uri; + } + + /** + * The application D / resource identifier with which to exchange a token. + * on behalf of + * @param withUri The Uri value. + */ + public void setUri(String withUri) { + this.uri = withUri; + } + /** + * The identifier of the provider with which to attempt a token exchange A + * value of null or empty will default to Azure Active Directory. + * @return the ProviderId value as a String. + */ + public String getProviderId() { + return this.providerId; + } + + /** + * The identifier of the provider with which to attempt a token exchange A + * value of null or empty will default to Azure Active Directory. + * @param withProviderId The ProviderId value. + */ + public void setProviderId(String withProviderId) { + this.providerId = withProviderId; + } + } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeState.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeState.java index 3546a9db8..3c78f7361 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeState.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenExchangeState.java @@ -10,7 +10,7 @@ * State object passed to the bot token service. */ public class TokenExchangeState { - @JsonProperty("msAppId") + @JsonProperty(value = "msAppId") @JsonInclude(JsonInclude.Include.NON_EMPTY) private String msAppId; @@ -22,11 +22,11 @@ public class TokenExchangeState { @JsonInclude(JsonInclude.Include.NON_EMPTY) private ConversationReference conversation; - @JsonProperty("botUrl") + @JsonProperty(value = "botUrl") @JsonInclude(JsonInclude.Include.NON_EMPTY) private String botUrl; - @JsonProperty("relatesTo") + @JsonProperty(value = "relatesTo") @JsonInclude(JsonInclude.Include.NON_EMPTY) private ConversationReference relatesTo; diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenResponse.java index b3d228c07..4ad9fac6d 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenResponse.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/TokenResponse.java @@ -10,6 +10,28 @@ * A response that includes a user token. */ public class TokenResponse { + + /** + * Initializes a new instance of the TokenResponse class. + */ + public TokenResponse() { + + } + + /** + * Initializes a new instance of the TokenResponse class. + * @param channelId The channelId. + * @param connectionName The connectionName. + * @param token The token. + * @param expiration the expiration. + */ + public TokenResponse(String channelId, String connectionName, String token, String expiration) { + this.channelId = channelId; + this.connectionName = connectionName; + this.token = token; + this.expiration = expiration; + } + /** * The channelId of the TokenResponse. */ @@ -40,7 +62,7 @@ public class TokenResponse { /** * Gets the channelId value. - * + * * @return THe channel id. */ public String getChannelId() { diff --git a/samples/18.bot-authentication/LICENSE b/samples/18.bot-authentication/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/18.bot-authentication/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/18.bot-authentication/README.md b/samples/18.bot-authentication/README.md new file mode 100644 index 000000000..456bab485 --- /dev/null +++ b/samples/18.bot-authentication/README.md @@ -0,0 +1,94 @@ +# Bot Authentication + +Bot Framework v4 bot authentication sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use authentication in your bot using OAuth. + +The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. + +NOTE: Microsoft Teams currently differs slightly in the way auth is integrated with the bot. Refer to sample 46.teams-auth. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-authentication-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "authenticationBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="authenticationBotPlan" newWebAppName="authenticationBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="authenticationBot" newAppServicePlanName="authenticationBotPlan" appServicePlanLocation="westus" --name "authenticationBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json b/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json b/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/18.bot-authentication/pom.xml b/samples/18.bot-authentication/pom.xml new file mode 100644 index 000000000..ac2acf198 --- /dev/null +++ b/samples/18.bot-authentication/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-authentication + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Bot Authentication sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.authentication.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.authentication.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java new file mode 100644 index 000000000..28dfd8148 --- /dev/null +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java @@ -0,0 +1,48 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + * + * This class also provides overrides for dependency injections. A class that + * extends the {@link com.microsoft.bot.builder.Bot} interface should be + * annotated with @Component. + * + * @see RichCardsBot + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java new file mode 100644 index 000000000..bc691d4af --- /dev/null +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.concurrent.CompletableFuture; +import java.util.List; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.ChannelAccount; + +import org.springframework.stereotype.Component; +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +@Component +public class AuthBot extends DialogBot { + + public AuthBot(ConversationState conversationState, UserState userState, MainDialog dialog) { + super(conversationState, userState, dialog); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + Activity reply = MessageFactory.text("Welcome to AuthBot." + + " Type anything to get logged in. Type 'logout' to sign-out."); + + return turnContext.sendActivity(reply); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + @Override + protected CompletableFuture onTokenResponseEvent(TurnContext turnContext) { + // Run the Dialog with the new Token Response Event Activity. + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } + +} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java new file mode 100644 index 000000000..e7d4a833a --- /dev/null +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + T withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java new file mode 100644 index 000000000..e31510940 --- /dev/null +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActivityTypes; + +public class LogoutDialog extends ComponentDialog { + + private final String connectionName; + + public LogoutDialog(String id, String connectionName) { + super(id); + this.connectionName = connectionName; + } + + + @Override + protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onBeginDialog(innerDc, options); + } + + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onContinueDialog(innerDc); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + if (text.equals("logout")) { + // The bot adapter encapsulates the authentication processes. + BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext().getAdapter(); + botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); + innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")).join(); + return innerDc.cancelAllDialogs(); + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * @return the ConnectionName value as a String. + */ + protected String getConnectionName() { + return this.connectionName; + } + +} + diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java new file mode 100644 index 000000000..5bbadb1da --- /dev/null +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPromptSettings; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.TokenResponse; + +import org.springframework.stereotype.Component; + +@Component + class MainDialog extends LogoutDialog { + + public MainDialog(Configuration configuration) { + super("MainDialog", configuration.getProperty("ConnectionName")); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setConnectionName(""); + settings.setText("Please Sign In"); + settings.setTitle("Sign In"); + settings.setConnectionName(configuration.getProperty("ConnectionName")); + settings.setTimeout(300000); // User has 5 minutes to login (1000 * 60 * 5) + + addDialog(new OAuthPrompt("OAuthPrompt", settings)); + + addDialog(new ConfirmPrompt("ConfirmPrompt")); + + WaterfallStep[] waterfallSteps = { + this::promptStep, + this::loginStep, + this::displayTokenPhase1, + this::displayTokenPhase2 + }; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture promptStep(WaterfallStepContext stepContext) { + return stepContext.beginDialog("OAuthPrompt", null); + } + + private CompletableFuture loginStep(WaterfallStepContext stepContext) { + // Get the token from the previous step. Note that we could also have gotten the + // token directly from the prompt itself. There instanceof an example of this in the next method. + TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + if (tokenResponse != null) { + stepContext.getContext().sendActivity(MessageFactory.text("You are now logged in.")); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Would you like to view your token?")); + return stepContext.prompt("ConfirmPrompt", options); + } + + stepContext.getContext().sendActivity(MessageFactory.text("Login was not successful please try again.")); + return stepContext.endDialog(); + } + + private CompletableFuture displayTokenPhase1(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")); + + boolean result = (boolean)stepContext.getResult(); + if (result) { + // Call the prompt again because we need the token. The reasons for this are: + // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry + // about refreshing it. We can always just call the prompt again to get the token. + // 2. We never know how long it will take a user to respond. By the time the + // user responds the token may have expired. The user would then be prompted to login again. + // + // There instanceof no reason to store the token locally in the bot because we can always just call + // the OAuth prompt to get the token or get a new token if needed. + return stepContext.beginDialog("OAuthPrompt"); + } + + return stepContext.endDialog(); + } + + private CompletableFuture displayTokenPhase2(WaterfallStepContext stepContext) { + TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + if (tokenResponse != null) { + stepContext.getContext().sendActivity(MessageFactory.text( + String.format("Here instanceof your token %s", tokenResponse.getToken() + ))); + } + + return stepContext.endDialog(); + } +} + diff --git a/samples/18.bot-authentication/src/main/resources/application.properties b/samples/18.bot-authentication/src/main/resources/application.properties new file mode 100644 index 000000000..9af36b70a --- /dev/null +++ b/samples/18.bot-authentication/src/main/resources/application.properties @@ -0,0 +1,4 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 +ConnectionName= diff --git a/samples/18.bot-authentication/src/main/resources/log4j2.json b/samples/18.bot-authentication/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/18.bot-authentication/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF b/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml b/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/18.bot-authentication/src/main/webapp/index.html b/samples/18.bot-authentication/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/18.bot-authentication/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +

+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java b/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java new file mode 100644 index 000000000..fcb0cdd43 --- /dev/null +++ b/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.authentication; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 5301ef23a9e6b1d9fb279db247d0f5bc5bb1f7c9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Feb 2021 15:04:00 -0600 Subject: [PATCH 065/221] Refactored 18.bot-authentication to match other samples (#968) --- .../sample/authentication/Application.java | 44 ++++++++++++++----- .../bot/sample/authentication/AuthBot.java | 8 +--- .../bot/sample/authentication/DialogBot.java | 15 ++++--- .../sample/authentication/LogoutDialog.java | 16 ++++--- .../bot/sample/authentication/MainDialog.java | 40 +++++++++-------- 5 files changed, 74 insertions(+), 49 deletions(-) diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java index 28dfd8148..55f589332 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java @@ -3,6 +3,10 @@ package com.microsoft.bot.sample.authentication; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -10,31 +14,51 @@ import com.microsoft.bot.integration.spring.BotDependencyConfiguration; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Import; -/** - * This is the starting point of the Sprint Boot Bot application. - * - * This class also provides overrides for dependency injections. A class that - * extends the {@link com.microsoft.bot.builder.Bot} interface should be - * annotated with @Component. - * - * @see RichCardsBot - */ +// +// This is the starting point of the Sprint Boot Bot application. +// @SpringBootApplication // Use the default BotController to receive incoming Channel messages. A custom // controller could be used by eliminating this import and creating a new -// RestController. +// org.springframework.web.bind.annotation.RestController. // The default controller is created by the Spring Boot container using // dependency injection. The default route is /api/messages. @Import({BotController.class}) +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ public class Application extends BotDependencyConfiguration { public static void main(String[] args) { SpringApplication.run(Application.class, args); } + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + ConversationState conversationState, + UserState userState, + MainDialog dialog + ) { + return new AuthBot(conversationState, userState, new MainDialog(configuration)); + } + /** * Returns a custom Adapter that provides error handling. * diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java index bc691d4af..83d4524bc 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java @@ -13,16 +13,10 @@ import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.schema.ChannelAccount; -import org.springframework.stereotype.Component; import com.codepoetics.protonpack.collectors.CompletableFutures; import com.microsoft.bot.schema.Activity; import org.apache.commons.lang3.StringUtils; - -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -@Component public class AuthBot extends DialogBot { public AuthBot(ConversationState conversationState, UserState userState, MainDialog dialog) { @@ -49,7 +43,7 @@ protected CompletableFuture onMembersAdded( @Override protected CompletableFuture onTokenResponseEvent(TurnContext turnContext) { // Run the Dialog with the new Token Response Event Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); } } diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java index e7d4a833a..83db0b694 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java @@ -12,15 +12,16 @@ import java.util.concurrent.CompletableFuture; /** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to - * allows multiple different bots to be run at different endpoints within the same project. This - * can be achieved by defining distinct Controller types each with dependency on distinct IBot - * types, this way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have - * been used in a Dialog implementation, and the requirement is that all BotState objects are - * saved at the end of a turn. + * This Bot implementation can run any type of Dialog. The use of type parameterization is to allows + * multiple different bots to be run at different endpoints within the same project. This can be + * achieved by defining distinct Controller types each with dependency on distinct IBot types, this + * way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been + * used in a Dialog implementation, and the requirement is that all BotState objects are saved at + * the end of a turn. */ public class DialogBot extends ActivityHandler { + protected Dialog dialog; protected BotState conversationState; protected BotState userState; diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java index e31510940..dcb42dd31 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java @@ -23,8 +23,10 @@ public LogoutDialog(String id, String connectionName) { @Override - protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { - DialogTurnResult result = interrupt(innerDc).join(); + protected CompletableFuture onBeginDialog( + DialogContext innerDc, Object options + ) { + DialogTurnResult result = interrupt(innerDc).join(); if (result != null) { return CompletableFuture.completedFuture(result); } @@ -34,7 +36,7 @@ protected CompletableFuture onBeginDialog(DialogContext innerD @Override protected CompletableFuture onContinueDialog(DialogContext innerDc) { - DialogTurnResult result = interrupt(innerDc).join(); + DialogTurnResult result = interrupt(innerDc).join(); if (result != null) { return CompletableFuture.completedFuture(result); } @@ -48,10 +50,12 @@ private CompletableFuture interrupt(DialogContext innerDc) { if (text.equals("logout")) { // The bot adapter encapsulates the authentication processes. - BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext().getAdapter(); + BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext() + .getAdapter(); botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); - innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")).join(); - return innerDc.cancelAllDialogs(); + innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")) + .join(); + return innerDc.cancelAllDialogs(); } } diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java index 5bbadb1da..a182e8b57 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java @@ -18,10 +18,7 @@ import com.microsoft.bot.integration.Configuration; import com.microsoft.bot.schema.TokenResponse; -import org.springframework.stereotype.Component; - -@Component - class MainDialog extends LogoutDialog { +class MainDialog extends LogoutDialog { public MainDialog(Configuration configuration) { super("MainDialog", configuration.getProperty("ConnectionName")); @@ -51,13 +48,13 @@ public MainDialog(Configuration configuration) { } private CompletableFuture promptStep(WaterfallStepContext stepContext) { - return stepContext.beginDialog("OAuthPrompt", null); + return stepContext.beginDialog("OAuthPrompt", null); } private CompletableFuture loginStep(WaterfallStepContext stepContext) { // Get the token from the previous step. Note that we could also have gotten the // token directly from the prompt itself. There instanceof an example of this in the next method. - TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); if (tokenResponse != null) { stepContext.getContext().sendActivity(MessageFactory.text("You are now logged in.")); PromptOptions options = new PromptOptions(); @@ -65,14 +62,17 @@ private CompletableFuture loginStep(WaterfallStepContext stepC return stepContext.prompt("ConfirmPrompt", options); } - stepContext.getContext().sendActivity(MessageFactory.text("Login was not successful please try again.")); - return stepContext.endDialog(); + stepContext.getContext() + .sendActivity(MessageFactory.text("Login was not successful please try again.")); + return stepContext.endDialog(); } - private CompletableFuture displayTokenPhase1(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")); + private CompletableFuture displayTokenPhase1( + WaterfallStepContext stepContext + ) { + stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")); - boolean result = (boolean)stepContext.getResult(); + boolean result = (boolean) stepContext.getResult(); if (result) { // Call the prompt again because we need the token. The reasons for this are: // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry @@ -82,21 +82,23 @@ private CompletableFuture displayTokenPhase1(WaterfallStepCont // // There instanceof no reason to store the token locally in the bot because we can always just call // the OAuth prompt to get the token or get a new token if needed. - return stepContext.beginDialog("OAuthPrompt"); + return stepContext.beginDialog("OAuthPrompt"); } - return stepContext.endDialog(); + return stepContext.endDialog(); } - private CompletableFuture displayTokenPhase2(WaterfallStepContext stepContext) { - TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + private CompletableFuture displayTokenPhase2( + WaterfallStepContext stepContext + ) { + TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); if (tokenResponse != null) { - stepContext.getContext().sendActivity(MessageFactory.text( - String.format("Here instanceof your token %s", tokenResponse.getToken() - ))); + stepContext.getContext().sendActivity(MessageFactory.text( + String.format("Here instanceof your token %s", tokenResponse.getToken() + ))); } - return stepContext.endDialog(); + return stepContext.endDialog(); } } From 12aa58d58caa229c97a10fb00351afcf1dc64dd2 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Feb 2021 16:04:45 -0600 Subject: [PATCH 066/221] Fixed bug with AppCredentials passed to adapter not being used. (#969) --- .../main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java | 1 + 1 file changed, 1 insertion(+) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 54ba59213..942430d84 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -1215,6 +1215,7 @@ private CompletableFuture getAppCredentials(String appId, String // moving forward if (appCredentials != null) { appCredentialMap.put(cacheKey, appCredentials); + return CompletableFuture.completedFuture(appCredentials); } return credentialProvider.getAppPassword(appId).thenApply(appPassword -> { From 497c35ee557a88a6e8d86c9a70e072fd259136f9 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 11 Feb 2021 14:21:07 -0600 Subject: [PATCH 067/221] Sample 19.custom-dialogs (#971) --- samples/19.custom-dialogs/LICENSE | 21 + samples/19.custom-dialogs/README.md | 102 +++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/19.custom-dialogs/pom.xml | 244 ++++++++++ .../bot/sample/customdialogs/Application.java | 86 ++++ .../bot/sample/customdialogs/DialogBot.java | 53 +++ .../bot/sample/customdialogs/RootDialog.java | 132 ++++++ .../bot/sample/customdialogs/SlotDetails.java | 75 ++++ .../customdialogs/SlotFillingDialog.java | 170 +++++++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/customdialogs/ApplicationTest.java | 19 + 16 files changed, 1906 insertions(+) create mode 100644 samples/19.custom-dialogs/LICENSE create mode 100644 samples/19.custom-dialogs/README.md create mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/19.custom-dialogs/pom.xml create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java create mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java create mode 100644 samples/19.custom-dialogs/src/main/resources/application.properties create mode 100644 samples/19.custom-dialogs/src/main/resources/log4j2.json create mode 100644 samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/19.custom-dialogs/src/main/webapp/index.html create mode 100644 samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java diff --git a/samples/19.custom-dialogs/LICENSE b/samples/19.custom-dialogs/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/19.custom-dialogs/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/19.custom-dialogs/README.md b/samples/19.custom-dialogs/README.md new file mode 100644 index 000000000..de0766982 --- /dev/null +++ b/samples/19.custom-dialogs/README.md @@ -0,0 +1,102 @@ +# Custom Dialogs + +Bot Framework v4 custom dialogs bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to sub-class the `Dialog` class to create different bot control mechanism like simple slot filling. + +BotFramework provides a built-in base class called `Dialog`. By subclassing `Dialog`, developers can create new ways to define and control dialog flows used by the bot. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-customdialogs-sample.jar` + +## Testing the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot + +BotFramework provides a built-in base class called `Dialog`. By subclassing Dialog, developers +can create new ways to define and control dialog flows used by the bot. By adhering to the +features of this class, developers will create custom dialogs that can be used side-by-side +with other dialog types, as well as built-in or custom prompts. + +This example demonstrates a custom Dialog class called `SlotFillingDialog`, which takes a +series of "slots" which define a value the bot needs to collect from the user, as well +as the prompt it should use. The bot will iterate through all of the slots until they are +all full, at which point the dialog completes. + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Dialog class reference](https://docs.microsoft.com/en-us/javascript/api/botbuilder-dialogs/dialog) +- [Manage complex conversation flows with dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/19.custom-dialogs/pom.xml b/samples/19.custom-dialogs/pom.xml new file mode 100644 index 000000000..725f8f839 --- /dev/null +++ b/samples/19.custom-dialogs/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-customdialogs + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains the Custom Dialogs sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.customdialogs.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.customdialogs.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java new file mode 100644 index 000000000..4997b04e0 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog dialog + ) { + return new DialogBot(conversationState, userState, dialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog(UserState userState) { + return new RootDialog(userState); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java new file mode 100644 index 000000000..ce884a210 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This IBot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends ActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + Dialog withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java new file mode 100644 index 000000000..bfaf3b445 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.NumberPrompt; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class RootDialog extends ComponentDialog { + + private StatePropertyAccessor>> userStateAccessor; + + public RootDialog(UserState withUserState) { + super("root"); + + userStateAccessor = withUserState.createProperty("result"); + + // Rather than explicitly coding a Waterfall we have only to declare what properties we + // want collected. + // In this example we will want two text prompts to run, one for the first name and one + // for the last. + List fullname_slots = Arrays.asList( + new SlotDetails("first", "text", "Please enter your first name."), + new SlotDetails("last", "text", "Please enter your last name.") + ); + + // This defines an address dialog that collects street, city and zip properties. + List address_slots = Arrays.asList( + new SlotDetails("street", "text", "Please enter the street."), + new SlotDetails("city", "text", "Please enter the city."), + new SlotDetails("zip", "text", "Please enter the zip.") + ); + + // Dialogs can be nested and the slot filling dialog makes use of that. In this example + // some of the child dialogs are slot filling dialogs themselves. + List slots = Arrays.asList( + new SlotDetails("fullname", "fullname"), + new SlotDetails("age", "number", "Please enter your age."), + new SlotDetails( + "shoesize", "shoesize", "Please enter your shoe size.", + "You must enter a size between 0 and 16. Half sizes are acceptable." + ), + new SlotDetails("address", "address") + ); + + // Add the various dialogs that will be used to the DialogSet. + addDialog(new SlotFillingDialog("address", address_slots)); + addDialog(new SlotFillingDialog("fullname", fullname_slots)); + addDialog(new TextPrompt("text")); + addDialog(new NumberPrompt<>("number", Integer.class)); + addDialog(new NumberPrompt("shoesize", this::shoeSize, Float.class)); + addDialog(new SlotFillingDialog("slot-dialog", slots)); + + // Defines a simple two step Waterfall to test the slot dialog. + WaterfallStep[] waterfallSteps = { + this::startDialog, + this::processResult + }; + addDialog(new WaterfallDialog("waterfall", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("waterfall"); + } + + private CompletableFuture startDialog(WaterfallStepContext stepContext) { + // Start the child dialog. This will run the top slot dialog than will complete when + // all the properties are gathered. + return stepContext.beginDialog("slot-dialog"); + } + + private CompletableFuture processResult(WaterfallStepContext stepContext) { + Map result = stepContext.getResult() instanceof Map + ? (Map) stepContext.getResult() + : null; + + // To demonstrate that the slot dialog collected all the properties we will echo them back to the user. + if (result != null && result.size() > 0) { + // Now the waterfall is complete, save the data we have gathered into UserState. + return userStateAccessor.get(stepContext.getContext(), HashMap::new) + .thenApply(obj -> { + Map fullname = (Map) result.get("fullname"); + Float shoesize = (Float) result.get("shoesize"); + Map address = (Map) result.get("address"); + + Map data = new HashMap<>(); + data.put("fullname", String.format("%s %s", fullname.get("first"), fullname.get("last"))); + data.put("shoesize", Float.toString(shoesize)); + data.put("address", String.format("%s, %s, %s", address.get("street"), address.get("city"), address.get("zip"))); + + obj.put("data", data); + return obj; + }) + .thenCompose(obj -> stepContext.getContext().sendActivities( + MessageFactory.text(obj.get("data").get("fullname")), + MessageFactory.text(obj.get("data").get("shoesize")), + MessageFactory.text(obj.get("data").get("address")) + )) + .thenCompose(resourceResponse -> stepContext.endDialog()); + } + + // Remember to call EndAsync to indicate to the runtime that this is the end of our waterfall. + return stepContext.endDialog(); + } + + private CompletableFuture shoeSize(PromptValidatorContext promptContext) { + Float shoesize = promptContext.getRecognized().getValue(); + + // show sizes can range from 0 to 16 + if (shoesize >= 0 && shoesize < 16) { + // we only accept round numbers or half sizes + if (Math.floor(shoesize) == shoesize || Math.floor(shoesize * 2) == shoesize * 2) { + return CompletableFuture.completedFuture(true); + } + } + + return CompletableFuture.completedFuture(false); + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java new file mode 100644 index 000000000..6531a9741 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.prompts.PromptOptions; + +/** + * A list of SlotDetails defines the behavior of our SlotFillingDialog. This class represents a + * description of a single 'slot'. It contains the name of the property we want to gather and the id + * of the corresponding dialog that should be used to gather that property. The id is that used when + * the dialog was added to the current DialogSet. Typically this id is that of a prompt but it could + * also be the id of another slot dialog. + */ +public class SlotDetails { + + private String name; + private String dialogId; + private PromptOptions options; + + public SlotDetails( + String withName, String withDialogId + ) { + this(withName, withDialogId, null, null); + } + + public SlotDetails( + String withName, String withDialogId, String withPrompt + ) { + this(withName, withDialogId, withPrompt, null); + } + + public SlotDetails( + String withName, String withDialogId, String withPrompt, String withRetryPrompt + ) { + name = withName; + dialogId = withDialogId; + options = new PromptOptions(); + options.setPrompt(MessageFactory.text(withPrompt)); + options.setRetryPrompt(MessageFactory.text(withRetryPrompt)); + } + + public SlotDetails( + String withName, String withDialogId, PromptOptions withPromptOptions + ) { + name = withName; + dialogId = withDialogId; + options = withPromptOptions; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getDialogId() { + return dialogId; + } + + public void setDialogId(String withDialogId) { + dialogId = withDialogId; + } + + public PromptOptions getOptions() { + return options; + } + + public void setOptions(PromptOptions withOptions) { + options = withOptions; + } +} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java new file mode 100644 index 000000000..a63e48371 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java @@ -0,0 +1,170 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActivityTypes; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +/** + * This is an example of implementing a custom Dialog class. This is similar to the Waterfall dialog + * in the framework; however, it is based on a Dictionary rather than a sequential set of functions. + * The dialog is defined by a list of 'slots', each slot represents a property we want to gather and + * the dialog we will be using to collect it. Often the property is simply an atomic piece of data + * such as a number or a date. But sometimes the property is itself a complex object, in which case + * we can use the slot dialog to collect that compound property. + */ +public class SlotFillingDialog extends Dialog { + + // Custom dialogs might define their own custom state. + // Similarly to the Waterfall dialog we will have a set of values in the ConversationState. + // However, rather than persisting an index we will persist the last property we prompted for. + // This way when we resume this code following a prompt we will have remembered what property + // we were filling. + private static final String SLOT_NAME = "slot"; + private static final String PERSISTED_VALUES = "values"; + + // The list of slots defines the properties to collect and the dialogs to use to collect them. + private List slots; + + public SlotFillingDialog(String withDialogId, List withSlots) { + super(withDialogId); + if (withSlots == null) { + throw new IllegalArgumentException("slot details are required"); + } + slots = withSlots; + } + + /** + * Called when the dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @param options Initial information to pass to the dialog. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Don't do anything for non-message activities. + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + return dc.endDialog(new HashMap()); + } + + // Run prompt + return runPrompt(dc); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. + * + *

If this method is *not* overridden, the dialog automatically ends when the user + * replies.

+ * + * @param dc The {@link DialogContext} for the current turn of conversation. + * @return If the task is successful, the result indicates whether the dialog is still active + * after the turn has been processed by the dialog. The result may also contain a return value. + */ + @Override + public CompletableFuture continueDialog( + DialogContext dc + ) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Don't do anything for non-message activities. + if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + return CompletableFuture.completedFuture(END_OF_TURN); + } + + // Run next step with the message text as the result. + return runPrompt(dc); + } + + /** + * Called when a child dialog completed this turn, returning control to this dialog. + * + *

Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the {@link + * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may + * be different than the original.

+ * + *

If this method is *not* overridden, the dialog automatically ends when the user + * replies.

+ * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The type of the value + * returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog is still active + * after this dialog turn has been processed. + */ + @Override + public CompletableFuture resumeDialog( + DialogContext dc, DialogReason reason, Object result + ) { + if (dc == null) { + return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); + } + + // Update the state with the result from the child prompt. + String slotName = (String) dc.getActiveDialog().getState().get(SLOT_NAME); + Map values = getPersistedValues(dc.getActiveDialog()); + values.put(slotName, result); + + // Run prompt + return runPrompt(dc); + } + + // This helper function contains the core logic of this dialog. The main idea is to compare + // the state we have gathered with the list of slots we have been asked to fill. When we find + // an empty slot we execute the corresponding prompt. + private CompletableFuture runPrompt(DialogContext dc) { + Map state = getPersistedValues(dc.getActiveDialog()); + + // Run through the list of slots until we find one that hasn't been filled yet. + Optional optionalSlot = slots.stream() + .filter(item -> !state.containsKey(item.getName())) + .findFirst(); + + if (!optionalSlot.isPresent()) { + // No more slots to fill so end the dialog. + return dc.endDialog(state); + } else { + // If we have an unfilled slot we will try to fill it + SlotDetails unfilledSlot = optionalSlot.get(); + + // The name of the slot we will be prompting to fill. + dc.getActiveDialog().getState().put(SLOT_NAME, unfilledSlot.getName()); + + // Run the child dialog + return dc.beginDialog(unfilledSlot.getDialogId(), unfilledSlot.getOptions()); + } + } + + // Helper function to deal with the persisted values we collect in this dialog. + private Map getPersistedValues(DialogInstance dialogInstance) { + Map state = (Map) dialogInstance.getState() + .get(PERSISTED_VALUES); + if (state == null) { + state = new HashMap(); + dialogInstance.getState().put(PERSISTED_VALUES, state); + } + return state; + } +} diff --git a/samples/19.custom-dialogs/src/main/resources/application.properties b/samples/19.custom-dialogs/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/19.custom-dialogs/src/main/resources/log4j2.json b/samples/19.custom-dialogs/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF b/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml b/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/19.custom-dialogs/src/main/webapp/index.html b/samples/19.custom-dialogs/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/19.custom-dialogs/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java b/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java new file mode 100644 index 000000000..89c3838fd --- /dev/null +++ b/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.customdialogs; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From c057023b617a0dd25022ca7d74f374ed240f3c43 Mon Sep 17 00:00:00 2001 From: Emilio Munoz Date: Thu, 11 Feb 2021 12:35:46 -0800 Subject: [PATCH 068/221] Luis Recognizer package (#962) * Adding first Iteration of Luis Recognizer * Adding tests, comments and fixing bugs * Enabling live testing, commenting Dialog Recognizer dependency * Removing spaces and fixing not implemented try catch * Fixing linting errors * Fixing style check errors * Fixing style checks * Adding package-info.java file * Adding missing Doc on Constructor * Adding missing javadoc at classes * Adding test on send trace Activity * Adding missing test * Added Dialogs Recognizer * Uncommented ExternalEntityRecognizer accessors * Added missing Dialog.Recognizer.recognize method * Adding test for DialogContext scenarios * Adding missing javadoc * Throwing Exception * Fixing style * Missing error message Co-authored-by: tracyboehrer --- libraries/bot-ai-luis-v3/pom.xml | 32 + .../microsoft/bot/ai/luis/DynamicList.java | 83 + .../microsoft/bot/ai/luis/ExternalEntity.java | 122 + .../microsoft/bot/ai/luis/ListElement.java | 88 + .../bot/ai/luis/LuisApplication.java | 228 ++ .../microsoft/bot/ai/luis/LuisRecognizer.java | 620 ++++ .../bot/ai/luis/LuisRecognizerOptions.java | 143 + .../bot/ai/luis/LuisRecognizerOptionsV3.java | 769 +++++ .../com/microsoft/bot/ai/luis/LuisSlot.java | 26 + .../bot/ai/luis/LuisTelemetryConstants.java | 75 + .../bot/ai/luis/TelemetryRecognizer.java | 84 + .../microsoft/bot/ai/luis/package-info.java | 9 + .../bot/ai/luis/LuisApplicationTests.java | 89 + .../ai/luis/LuisRecognizerOptionsV3Tests.java | 334 +++ .../bot/ai/luis/LuisRecognizerTests.java | 369 +++ .../bot/ai/luis/testdata/Composite1.json | 1971 +++++++++++++ .../bot/ai/luis/testdata/Composite2.json | 435 +++ .../bot/ai/luis/testdata/Composite3.json | 461 +++ .../bot/ai/luis/testdata/Contoso App.json | 2541 +++++++++++++++++ .../ai/luis/testdata/DateTimeReference.json | 75 + .../ai/luis/testdata/DynamicListsAndList.json | 221 ++ .../testdata/ExternalEntitiesAndBuiltin.json | 167 ++ .../ExternalEntitiesAndComposite.json | 196 ++ .../testdata/ExternalEntitiesAndList.json | 177 ++ .../testdata/ExternalEntitiesAndRegex.json | 166 ++ .../testdata/ExternalEntitiesAndSimple.json | 232 ++ .../ExternalEntitiesAndSimpleOverride.json | 239 ++ .../ai/luis/testdata/ExternalRecognizer.json | 201 ++ .../ai/luis/testdata/GeoPeopleOrdinal.json | 436 +++ .../bot/ai/luis/testdata/Minimal.json | 143 + .../bot/ai/luis/testdata/MinimalWithGeo.json | 672 +++++ .../luis/testdata/NoEntitiesInstanceTrue.json | 41 + .../bot/ai/luis/testdata/Patterns.json | 363 +++ .../bot/ai/luis/testdata/Prebuilt.json | 351 +++ .../testdata/TestRecognizerResultConvert.java | 15 + .../bot/ai/luis/testdata/TraceActivity.json | 247 ++ .../microsoft/bot/ai/luis/testdata/Typed.json | 1971 +++++++++++++ .../bot/ai/luis/testdata/TypedPrebuilt.json | 351 +++ .../luis/testdata/V1DatetimeResolution.json | 19 + .../microsoft/bot/ai/luis/testdata/roles.json | 2252 +++++++++++++++ .../bot/builder/RecognizerResult.java | 21 +- .../com/microsoft/bot/dialogs/Recognizer.java | 272 ++ .../microsoft/bot/schema/Serialization.java | 24 + 43 files changed, 17327 insertions(+), 4 deletions(-) create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/DynamicList.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ExternalEntity.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ListElement.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizer.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptions.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisSlot.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisTelemetryConstants.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/TelemetryRecognizer.java create mode 100644 libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/package-info.java create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisApplicationTests.java create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite1.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite2.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite3.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Contoso App.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DateTimeReference.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DynamicListsAndList.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndBuiltin.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndComposite.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndList.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndRegex.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimple.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimpleOverride.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalRecognizer.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/GeoPeopleOrdinal.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Minimal.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/MinimalWithGeo.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/NoEntitiesInstanceTrue.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Patterns.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Prebuilt.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TestRecognizerResultConvert.java create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TraceActivity.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Typed.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TypedPrebuilt.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/V1DatetimeResolution.json create mode 100644 libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/roles.json create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java diff --git a/libraries/bot-ai-luis-v3/pom.xml b/libraries/bot-ai-luis-v3/pom.xml index 7622682ee..9b96d30d7 100644 --- a/libraries/bot-ai-luis-v3/pom.xml +++ b/libraries/bot-ai-luis-v3/pom.xml @@ -62,6 +62,38 @@ com.microsoft.bot bot-applicationinsights + + com.microsoft.bot + bot-dialogs + + + com.squareup.okhttp3 + okhttp + 4.9.0 + + + org.json + json + 20190722 + + + com.squareup.okhttp3 + mockwebserver + 4.9.0 + test + + + org.junit.jupiter + junit-jupiter-params + 5.7.0 + test + + + org.mockito + mockito-junit-jupiter + 3.6.0 + test + diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/DynamicList.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/DynamicList.java new file mode 100644 index 000000000..2c54b76fe --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/DynamicList.java @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; + +/** + * Request Body element to use when passing Dynamic lists to the Luis Service call. + * + */ +public class DynamicList { + + /** + * Initializes a new instance of the DynamicList class. + */ + public DynamicList() { + } + + /** + * Initializes a new instance of the DynamicList class. + * @param entity Entity field. + * @param requestLists List Elements to use when querying Luis Service. + */ + public DynamicList(String entity, List requestLists) { + this.entity = entity; + this.list = requestLists; + } + + @JsonProperty(value = "listEntityName") + private String entity; + + @JsonProperty(value = "requestLists") + private List list; + + /** + * Gets the entity. + * @return Entity name. + */ + public String getEntity() { + return entity; + } + + /** + * Sets the entity name. + * @param entity entity name. + */ + public void setEntity(String entity) { + this.entity = entity; + } + + /** + * Gets the List. + * @return Element list of the Dynamic List. + */ + public List getList() { + return list; + } + + /** + * Sets the List. + * @param list Element list of the Dynamic List. + */ + public void setList(List list) { + this.list = list; + } + + /** + * Validate the object. + * @throws IllegalArgumentException on null or invalid values. + */ + public void validate() throws IllegalArgumentException { + // Required: ListEntityName, RequestLists + if (entity == null || list == null) { + throw new IllegalArgumentException("ExternalEntity requires an EntityName and EntityLength > 0"); + } + + for (ListElement e: list) { + e.validate(); + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ExternalEntity.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ExternalEntity.java new file mode 100644 index 000000000..b2bf28893 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ExternalEntity.java @@ -0,0 +1,122 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; + +/** + * Request Body element to use when passing External Entities to the Luis Service call. + * + */ +public class ExternalEntity { + + /** + * Initializes a new instance of ExternalEntity. + */ + public ExternalEntity() { + } + + /** + * Initializes a new instance of ExternalEntity. + * @param entity name of the entity to extend. + * @param start start character index of the predicted entity. + * @param length length of the predicted entity. + * @param resolution supplied custom resolution to return as the entity's prediction. + */ + public ExternalEntity(String entity, int start, int length, JsonNode resolution) { + this.entity = entity; + this.start = start; + this.length = length; + this.resolution = resolution; + } + + @JsonProperty(value = "entityName") + private String entity; + + + @JsonProperty(value = "startIndex") + private int start; + + + @JsonProperty(value = "entityLength") + private int length = -1; + + @JsonProperty(value = "resolution") + private JsonNode resolution; + + /** + * Gets the start character index of the predicted entity. + * @return start character index of the predicted entity. + */ + public int getStart() { + return start; + } + + /** + * Sets the start character index of the predicted entity. + * @param start character index of the predicted entity. + */ + public void setStart(int start) { + this.start = start; + } + + /** + * Gets the name of the entity to extend. + * @return name of the entity to extend. + */ + public String getEntity() { + return entity; + } + + /** + * Sets the name of the entity to extend. + * @param entity name of the entity to extend. + */ + public void setEntity(String entity) { + this.entity = entity; + } + + /** + * Gets the length of the predicted entity. + * @return length of the predicted entity. + */ + public int getLength() { + return length; + } + + /** + * Sets the length of the predicted entity. + * @param length of the predicted entity. + */ + public void setLength(int length) { + this.length = length; + } + + /** + * Gets a user supplied custom resolution to return as the entity's prediction. + * @return custom resolution to return as the entity's prediction. + */ + public JsonNode getResolution() { + return resolution; + } + + /** + * Sets External entities to be recognized in query. + * @param resolution custom resolution to return as the entity's prediction. + */ + public void setResolution(JsonNode resolution) { + this.resolution = resolution; + } + + /** + * Validate the object. + * @throws IllegalArgumentException on null or invalid values + */ + public void validate() throws IllegalArgumentException { + if (entity == null || length == -1) { + throw new IllegalArgumentException("ExternalEntity requires an EntityName and EntityLength > 0"); + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ListElement.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ListElement.java new file mode 100644 index 000000000..efeaa4d62 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/ListElement.java @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.List; + +/** + * List Element for Dynamic Lists. + * + */ +public class ListElement { + + /** + * Initializes a new instance of the ListElement class. + */ + public ListElement() { + } + + /** + * Initializes a new instance of the ListElement class. + * @param canonicalForm The canonical form of the sub-list. + * @param synonyms The synonyms of the canonical form. + */ + public ListElement(String canonicalForm, List synonyms) { + this.canonicalForm = canonicalForm; + this.synonyms = synonyms; + } + + /** + * The canonical form of the sub-list. + */ + @JsonProperty(value = "canonicalForm") + private String canonicalForm; + + /** + * The synonyms of the canonical form. + */ + @JsonProperty(value = "synonyms") + @JsonInclude(JsonInclude.Include.NON_NULL) + private List synonyms; + + /** + * Gets the canonical form of the sub-list. + * @return String canonical form of the sub-list. + */ + public String getCanonicalForm() { + return canonicalForm; + } + + /** + * Sets the canonical form of the sub-list. + * @param canonicalForm the canonical form of the sub-list. + */ + public void setCanonicalForm(String canonicalForm) { + this.canonicalForm = canonicalForm; + } + + /** + * Gets the synonyms of the canonical form. + * @return the synonyms List of the canonical form. + */ + public List getSynonyms() { + return synonyms; + } + + /** + * Sets the synonyms of the canonical form. + * @param synonyms List of synonyms of the canonical form. + */ + public void setSynonyms(List synonyms) { + this.synonyms = synonyms; + } + + /** + * Validate the object. + * @throws IllegalArgumentException if canonicalForm is null. + */ + public void validate() throws IllegalArgumentException { + if (canonicalForm == null) { + throw new IllegalArgumentException("RequestList requires CanonicalForm to be defined."); + } + } + +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java new file mode 100644 index 000000000..b5afbfd89 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java @@ -0,0 +1,228 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import org.apache.http.NameValuePair; +import org.apache.http.client.utils.URIBuilder; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.util.UUID; + +/** + * Luis Application representation with information necessary to query the specific Luis Application. + * + */ +public class LuisApplication { + + /** + * Luis application ID. + */ + private String applicationId; + + /** + * Luis subscription or endpoint key. + */ + private String endpointKey; + + /** + * Luis subscription or endpoint key. + */ + private String endpoint; + + /** + * Luis endpoint like https://westus.api.cognitive.microsoft.com. + */ + public LuisApplication() { + } + + /** + * Initializes a new instance of the Luis Application class. + * @param applicationId Luis Application ID to query + * @param endpointKey LUIS subscription or endpoint key. + * @param endpoint LUIS endpoint to use like https://westus.api.cognitive.microsoft.com + */ + public LuisApplication( + String applicationId, + String endpointKey, + String endpoint) { + setLuisApplication( + applicationId, + endpointKey, + endpoint); + } + + /** + * Initializes a new instance of the Luis Application class. + * @param applicationEndpoint LUIS application query endpoint containing subscription key + * and application id as part of the url. + */ + public LuisApplication( + String applicationEndpoint) { + parse(applicationEndpoint); + } + + /** + * Sets Luis application ID to query. + * @param applicationId Luis application ID to query. + */ + public void setApplicationId(String applicationId) { + this.applicationId = applicationId; + } + + /** + * Gets Luis application ID. + * @return applicationId. + */ + public String getApplicationId() { + return applicationId; + } + + /** + * Sets the LUIS subscription or endpoint key. + * @param endpointKey LUIS subscription or endpoint key. + */ + public void setEndpointKey(String endpointKey) { + this.endpointKey = endpointKey; + } + + /** + * Gets the LUIS subscription or endpoint key. + * @return endpointKey. + */ + public String getEndpointKey() { + return endpointKey; + } + + /** + * Sets Luis endpoint like https://westus.api.cognitive.microsoft.com. + * @param endpoint endpoint like https://westus.api.cognitive.microsoft.com. + */ + public void setEndpoint(String endpoint) { + this.endpoint = endpoint; + } + + /** + * Gets the LUIS endpoint where application is hosted. + * @return endpoint. + */ + public String getEndpoint() { + return endpoint; + } + + /** + * Helper method to set and validate Luis arguments passed. + */ + private void setLuisApplication( + String applicationId, + String endpointKey, + String endpoint) { + + if (!isValidUUID(applicationId)) { + throw new IllegalArgumentException(String.format("%s is not a valid LUIS application id.", applicationId)); + } + + + if (!isValidUUID(endpointKey)) { + throw new IllegalArgumentException(String.format("%s is not a valid LUIS subscription key.", endpointKey)); + } + + if (endpoint == null || endpoint.isEmpty()) { + endpoint = "https://westus.api.cognitive.microsoft.com"; + } + + if (!isValidURL(endpoint)) { + throw new IllegalArgumentException(String.format("%s is not a valid LUIS endpoint.", endpoint)); + } + + this.applicationId = applicationId; + this.endpointKey = endpointKey; + this.endpoint = endpoint; + } + + /** + * Helper method to parse validate and set Luis application members from the full application full endpoint. + */ + private void parse(String applicationEndpoint) { + String appId = ""; + try { + String[] segments = new URL(applicationEndpoint) + .getPath() + .split("/"); + for (int segment = 0; segment < segments.length - 1; segment++) { + if (segments[segment].equals("apps")) { + appId = segments[segment + 1].trim(); + break; + } + } + } catch (MalformedURLException e) { + throw new IllegalArgumentException( + String.format( + "Unable to create the LUIS endpoint with the given %s.", + applicationEndpoint + ) + ); + } + + + if (appId.isEmpty()) { + throw new IllegalArgumentException( + String.format( + "Could not find application Id in %s", + applicationEndpoint + ) + ); + } + + try { + + String endpointKeyParsed = new URIBuilder(applicationEndpoint) + .getQueryParams() + .stream() + .filter(param -> param.getName() + .equalsIgnoreCase("subscription-key")) + .map(NameValuePair::getValue) + .findFirst() + .orElse(""); + + String endpointPared = String.format( + "%s://%s", + new URL(applicationEndpoint).getProtocol(), + new URL(applicationEndpoint).toURI().getHost() + ); + + setLuisApplication(appId, endpointKeyParsed, endpointPared); + } catch (URISyntaxException | MalformedURLException e) { + throw new IllegalArgumentException( + String.format( + "Unable to create the LUIS endpoint with the given %s.", + applicationEndpoint + )); + } + + } + + private boolean isValidUUID(String uuid) { + try { + if (!uuid.contains("-")) { + uuid = uuid.replaceAll( + "(.{8})(.{4})(.{4})(.{4})(.+)", + "$1-$2-$3-$4-$5" + ); + } + + return UUID.fromString(uuid).toString().equals(uuid); + } catch (IllegalArgumentException e) { + return false; + } + } + + private boolean isValidURL(String uri) { + try { + return new URL(uri).toURI().isAbsolute(); + } catch (URISyntaxException | MalformedURLException exception) { + return false; + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizer.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizer.java new file mode 100644 index 000000000..c7a5d91cf --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizer.java @@ -0,0 +1,620 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.IntentScore; +import com.microsoft.bot.builder.RecognizerConvert; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.schema.Activity; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Luis Recognizer class to query the LUIS Service using the configuration set by the LuisRecognizeroptions. + * + */ +public class LuisRecognizer extends TelemetryRecognizer { + /** + * Luis Recognizer options to query the Luis Service. + */ + private LuisRecognizerOptions luisRecognizerOptions; + + /** + * Initializes a new instance of the Luis Recognizer . + * @param recognizerOptions Luis Recognizer options to use when calling th LUIS Service. + */ + public LuisRecognizer(LuisRecognizerOptions recognizerOptions) { + this.luisRecognizerOptions = recognizerOptions; + this.setLogPersonalInformation(recognizerOptions.isLogPersonalInformation()); + this.setTelemetryClient(recognizerOptions.getTelemetryClient()); + } + + /** + * Returns the name of the top scoring intent from a set of LUIS results. + * @param results The Recognizer Result with the list of Intents to filter. + * Defaults to a value of "None" and a min score value of `0.0` + * @return The top scoring intent name. + */ + public static String topIntent( + RecognizerResult results) { + return topIntent(results, "None"); + } + + /** + * Returns the name of the top scoring intent from a set of LUIS results. + * @param results The Recognizer Result with the list of Intents to filter + * @param defaultIntent Intent name to return should a top intent be found. + * Defaults to a value of "None" and a min score value of `0.0` + * @return The top scoring intent name. + */ + public static String topIntent( + RecognizerResult results, + String defaultIntent) { + return topIntent(results, defaultIntent, 0.0); + } + + /** + * Returns the name of the top scoring intent from a set of LUIS results. + * @param results The Recognizer Result with the list of Intents to filter. + * @param minScore Minimum score needed for an intent to be considered as a top intent. + * @return The top scoring intent name. + */ + public static String topIntent( + RecognizerResult results, + double minScore) { + return topIntent(results, "None", minScore); + } + + /** + * Returns the name of the top scoring intent from a set of LUIS results. + * @param results The Recognizer Result with the list of Intents to filter + * @param defaultIntent Intent name to return should a top intent be found. Defaults to a value of "None + * @param minScore Minimum score needed for an intent to be considered as a top intent. + * @return The top scoring intent name. + */ + public static String topIntent( + RecognizerResult results, + String defaultIntent, + double minScore) { + if (results == null) { + throw new IllegalArgumentException("RecognizerResult"); + } + + defaultIntent = defaultIntent == null || defaultIntent.equals("") ? "None" : defaultIntent; + + String topIntent = null; + double topScore = -1.0; + if (!results.getIntents().isEmpty()) { + for (Map.Entry intent : results.getIntents().entrySet()) { + + double score = intent.getValue().getScore(); + if (score > topScore && score >= minScore) { + topIntent = intent.getKey(); + topScore = score; + } + } + } + + return topIntent != null && !topIntent.equals("") ? topIntent : defaultIntent; + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + @Override + public CompletableFuture recognize( + TurnContext turnContext) { + return recognizeInternal( + turnContext, + null, + null, + null); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity) { + return recognizeInternal( + dialogContext, + activity, + null, + null, + null); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + Class c) { + return recognizeInternal( + turnContext, + null, + null, + null) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + Class c) { + return recognizeInternal( + dialogContext, + activity, + null, + null, + null) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + @Override + public CompletableFuture recognize( + TurnContext turnContext, + Map telemetryProperties, + Map telemetryMetrics) { + return recognizeInternal( + turnContext, + null, + telemetryProperties, + telemetryMetrics); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + Map telemetryProperties, + Map telemetryMetrics) { + return recognizeInternal( + dialogContext, + activity, + null, + telemetryProperties, + telemetryMetrics); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + Map telemetryProperties, + Map telemetryMetrics, + Class c) { + return recognizeInternal( + turnContext, + null, + telemetryProperties, + telemetryMetrics) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + Map telemetryProperties, + Map telemetryMetrics, + Class c) { + return recognizeInternal( + dialogContext, + activity, + null, + telemetryProperties, + telemetryMetrics) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + LuisRecognizerOptions recognizerOptions) { + return recognizeInternal( + turnContext, + recognizerOptions, + null, + null); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + LuisRecognizerOptions recognizerOptions) { + return recognizeInternal( + dialogContext, + activity, + recognizerOptions, + null, + null); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + LuisRecognizerOptions recognizerOptions, + Class c) { + return recognizeInternal( + turnContext, + recognizerOptions, + null, + null) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + LuisRecognizerOptions recognizerOptions, + Class c) { + return recognizeInternal( + dialogContext, + activity, + recognizerOptions, + null, + null) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param recognizerOptions LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + LuisRecognizerOptions recognizerOptions, + Map telemetryProperties, + Map telemetryMetrics) { + return recognizeInternal( + turnContext, + recognizerOptions, + telemetryProperties, + telemetryMetrics); + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + LuisRecognizerOptions recognizerOptions, + Map telemetryProperties, + Map telemetryMetrics) { + return recognizeInternal( + dialogContext, + activity, + recognizerOptions, + telemetryProperties, + telemetryMetrics); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param recognizerOptions A LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + TurnContext turnContext, + LuisRecognizerOptions recognizerOptions, + Map telemetryProperties, + Map telemetryMetrics, + Class c) { + return recognizeInternal( + turnContext, + recognizerOptions, + telemetryProperties, + telemetryMetrics) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Runs an utterance through a recognizer and returns a strongly-typed recognizer result. + * @param dialogContext Context object containing information for a single turn of conversation with a user. + * @param activity Activity to recognize. + * @param recognizerOptions LuisRecognizerOptions instance to be used by the call. This parameter overrides the + * default LuisRecognizerOptions passed in the constructor. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @param Type of result. + * @param c RecognizerConvert implemented class to convert the Recognizer Result into. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + LuisRecognizerOptions recognizerOptions, + Map telemetryProperties, + Map telemetryMetrics, + Class c) { + return recognizeInternal( + dialogContext, + activity, + recognizerOptions, + telemetryProperties, + telemetryMetrics) + .thenApply(recognizerResult -> convertRecognizerResult(recognizerResult, c)); + } + + /** + * Invoked prior to a LuisResult being logged. + * @param recognizerResult The Luis Results for the call. + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + */ + public void onRecognizerResult( + RecognizerResult recognizerResult, + TurnContext turnContext, + Map telemetryProperties, + Map telemetryMetrics) { + Map properties = fillLuisEventPropertiesAsync( + recognizerResult, + turnContext, + telemetryProperties); + // Track the event + this.getTelemetryClient().trackEvent( + LuisTelemetryConstants.LUIS_RESULT, + properties, + telemetryMetrics); + } + + private Map fillLuisEventPropertiesAsync( + RecognizerResult recognizerResult, + TurnContext turnContext, + Map telemetryProperties) { + + Map sortedIntents = sortIntents(recognizerResult); + ArrayList topTwoIntents = new ArrayList<>(); + Iterator> iterator = sortedIntents.entrySet().iterator(); + int intentCounter = 0; + while (iterator.hasNext() + && intentCounter < 2) { + intentCounter++; + Map.Entry intent = iterator.next(); + topTwoIntents.add(intent.getKey()); + } + + // Add the intent score and conversation id properties + Map properties = new HashMap<>(); + properties.put( + LuisTelemetryConstants.APPLICATION_ID_PROPERTY, + luisRecognizerOptions.getApplication().getApplicationId()); + properties.put( + LuisTelemetryConstants.INTENT_PROPERTY, + topTwoIntents.size() > 0 ? topTwoIntents.get(0) : ""); + properties.put( + LuisTelemetryConstants.INTENT_SCORE_PROPERTY, + topTwoIntents.size() > 0 + ? "" + recognizerResult.getIntents().get(topTwoIntents.get(0)).getScore() + : "0.00"); + properties.put( + LuisTelemetryConstants.INTENT_2_PROPERTY, + topTwoIntents.size() > 1 ? topTwoIntents.get(1) : ""); + properties.put( + LuisTelemetryConstants.INTENT_SCORE_2_PROPERTY, + topTwoIntents.size() > 1 + ? "" + recognizerResult.getIntents().get(topTwoIntents.get(1)).getScore() + : "0.00"); + properties.put( + LuisTelemetryConstants.FROM_ID_PROPERTY, turnContext.getActivity().getFrom().getId()); + + if (recognizerResult.getProperties().containsKey("sentiment")) { + JsonNode sentiment = recognizerResult.getProperties().get("sentiment"); + if (sentiment.has("label")) { + properties.put( + LuisTelemetryConstants.SENTIMENT_LABEL_PROPERTY, + sentiment.get("label").textValue()); + } + + if (sentiment.has("score")) { + properties.put( + LuisTelemetryConstants.SENTIMENT_SCORE_PROPERTY, + sentiment.get("score").textValue()); + } + } + + properties.put( + LuisTelemetryConstants.ENTITIES_PROPERTY, + recognizerResult.getEntities().toString()); + + // Use the LogPersonalInformation flag to toggle logging PII data, text is a common example + if (isLogPersonalInformation() + && turnContext.getActivity().getText() != null + && !turnContext.getActivity().getText().equals("")) { + properties.put( + LuisTelemetryConstants.QUESTION_PROPERTY, + turnContext.getActivity().getText()); + } + + // Additional Properties can override "stock" properties. + if (telemetryProperties == null) { + telemetryProperties = new HashMap<>(); + } + + properties.putAll(telemetryProperties); + + return properties; + } + + private T convertRecognizerResult( + RecognizerResult recognizerResult, + Class clazz) { + T result; + try { + result = clazz.newInstance(); + result.convert(recognizerResult); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException( + String.format("Exception thrown when converting " + + "Recgonizer Result to strongly typed: %s : %s", + clazz.getName(), + e.getMessage())); + } + return result; + } + + /** + * Returns a RecognizerResult object. This method will call the internal recognize implementation of the + * Luis Recognizer Options. + */ + private CompletableFuture recognizeInternal( + TurnContext turnContext, + LuisRecognizerOptions options, + Map telemetryProperties, + Map telemetryMetrics) { + LuisRecognizerOptions predictionOptionsToRun = options == null ? luisRecognizerOptions : options; + return predictionOptionsToRun.recognizeInternal(turnContext) + .thenApply(recognizerResult -> { + onRecognizerResult( + recognizerResult, + turnContext, + telemetryProperties, + telemetryMetrics); + return recognizerResult; + }); + } + + /** + * Returns a RecognizerResult object. This method will call the internal recognize implementation of the + * Luis Recognizer Options. + */ + private CompletableFuture recognizeInternal( + DialogContext dialogContext, + Activity activity, + LuisRecognizerOptions options, + Map telemetryProperties, + Map telemetryMetrics) { + LuisRecognizerOptions predictionOptionsToRun = options == null ? luisRecognizerOptions : options; + return predictionOptionsToRun.recognizeInternal( + dialogContext, + activity) + .thenApply(recognizerResult -> { + onRecognizerResult( + recognizerResult, + dialogContext.getContext(), + telemetryProperties, + telemetryMetrics); + return recognizerResult; + }); + } + + private Map sortIntents(RecognizerResult recognizerResult) { + Map sortedIntents = new LinkedHashMap<>(); + recognizerResult.getIntents().entrySet() + .stream() + .sorted(Map.Entry.comparingByValue(Comparator.comparingDouble(IntentScore::getScore).reversed())) + .forEachOrdered(x -> sortedIntents.put(x.getKey(), x.getValue())); + return sortedIntents; + } +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptions.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptions.java new file mode 100644 index 000000000..c1cb374fc --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptions.java @@ -0,0 +1,143 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.schema.Activity; +import java.util.concurrent.CompletableFuture; + +/** + * Abstract class to enforce the Strategy pattern consumed by the Luis Recognizer through the options selected. + * + */ +public abstract class LuisRecognizerOptions { + + /** + * Initializes an instance of the LuisRecognizerOptions implementation. + * @param application An instance of LuisApplication". + */ + protected LuisRecognizerOptions(LuisApplication application) { + if (application == null) { + throw new IllegalArgumentException("Luis Application may not be null"); + } + this.application = application; + } + + /** + * Luis Application instance. + */ + private LuisApplication application; + + /** + * Bot Telemetry Client instance. + */ + private BotTelemetryClient telemetryClient = null; + + /** + * Controls if personal information should be sent as telemetry. + */ + private boolean logPersonalInformation = false; + + /** + * Controls if full results from the LUIS API should be returned with the recognizer result. + */ + private boolean includeAPIResults = false; + + /** + * Gets the Luis Application instance. + * + * @return The Luis Application instance used with this Options. + */ + public LuisApplication getApplication() { + return application; + } + + /** + * Sets the Luis Application. + * + * @param application A Luis Application instance which sets the Luis specifics to work with + */ + public void setApplication( + LuisApplication application) { + this.application = application; + } + + /** + * Gets the currently configured Bot Telemetry Client that logs the LuisResult event. + * + * @return The Bot Telemetry Client. + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the Bot Telemetry Client to log telemetry with. + * + * @param telemetryClient A Bot Telemetry Client instance + */ + public void setTelemetryClient( + BotTelemetryClient telemetryClient) { + this.telemetryClient = telemetryClient; + } + + /** + * Indicates if personal information should be sent as telemetry. + * @return value boolean value to control personal information logging. + */ + public boolean isLogPersonalInformation() { + return logPersonalInformation; + } + + /** + * Indicates if personal information should be sent as telemetry. + * @param logPersonalInformation to set personal information logging preference. + */ + public void setLogPersonalInformation( + boolean logPersonalInformation) { + this.logPersonalInformation = logPersonalInformation; + } + + /** + * Indicates if full results from the LUIS API should be returned with the recognizer result. + * @return boolean value showing preference on LUIS API full response added to recognizer result. + */ + public boolean isIncludeAPIResults() { + return includeAPIResults; + } + + /** + * Indicates if full results from the LUIS API should be returned with the recognizer result. + * @param includeAPIResults to set full Luis API response to be added to the recognizer result. + */ + public void setIncludeAPIResults( + boolean includeAPIResults) { + this.includeAPIResults = includeAPIResults; + } + + /** + * Implementation of the Luis API http call and result processing. + * This is intended to follow a Strategy pattern and + * should only be consumed through the LuisRecognizer class. + * @param turnContext used to extract the text utterance to be sent to Luis. + * @return Recognizer Result populated by the Luis response. + */ + abstract CompletableFuture recognizeInternal( + TurnContext turnContext); + + /** + * Implementation of the Luis API http call and result processing. + * This is intended to follow a Strategy pattern and + * should only be consumed through the LuisRecognizer class. + * @param context Dialog Context to extract turn context. + * @param activity to extract the text utterance to be sent to Luis. + * @return Recognizer Result populated by the Luis response. + */ + abstract CompletableFuture recognizeInternal( + DialogContext context, + Activity activity); +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java new file mode 100644 index 000000000..1a870fc88 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java @@ -0,0 +1,769 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.IntentScore; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.Recognizer; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ResourceResponse; +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.CompletableFuture; + +/** + * Luis Recognizer Options for V3 LUIS Runtime. + * + */ +public class LuisRecognizerOptionsV3 extends LuisRecognizerOptions { + private final HashSet dateSubtypes = new HashSet<>( + Arrays.asList( + "date", + "daterange", + "datetime", + "datetimerange", + "duration", + "set", + "time", + "timerange" + )); + + private final HashSet geographySubtypes = new HashSet<>( + Arrays.asList( + "poi", + "city", + "countryRegion", + "continent", + "state" + )); + + private final String metadataKey = "$instance"; + + /** + * DatetimeV2 offset. The format for the datetimeReference is ISO 8601. + */ + private String dateTimeReference = null; + + /** + * Dynamic lists used to recognize entities for a particular query. + */ + private List dynamicLists = null; + + /** + * External entities recognized in query. + */ + private List externalEntities = null; + + /** + * External entity recognizer to recognize external entities to pass to LUIS. + */ + private Recognizer externalEntityRecognizer = null; + + /** + * Value indicating whether all intents come back or only the top one. True for returning all intents. + */ + private boolean includeAllIntents = false; + + /** + * Value indicating whether or not instance data should be included in response. + */ + private boolean includeInstanceData = false; + + /** + * Value indicating whether queries should be logged in LUIS. If queries should be logged in LUIS in order to help + * build better models through active learning + */ + private boolean log = true; + + /** + * Value indicating whether external entities should override other means of recognizing entities. True if external + * entities should be preferred to the results from LUIS models + */ + private boolean preferExternalEntities = true; + + /** + * The LUIS slot to use for the application. By default this uses the production slot. You can find other standard + * slots in LuisSlot. If you specify a Version, then a private version of the application is used instead of a slot. + */ + private String slot = LuisSlot.PRODUCTION; + + /** + * The specific version of the application to access. LUIS supports versions and this is the version to use instead + * of a slot. If this is specified, then the Slot is ignored. + */ + private String version = null; + + /** + * The HttpClient instance to use for http calls against the LUIS endpoint. + */ + private OkHttpClient httpClient = new OkHttpClient(); + + /** + * The value type for a LUIS trace activity. + */ + public static final String LUIS_TRACE_TYPE = "https://www.luis.ai/schemas/trace"; + + /** + * The context label for a LUIS trace activity. + */ + public static final String LUIS_TRACE_LABEL = "LuisV3 Trace"; + + /** + * Gets External entity recognizer to recognize external entities to pass to LUIS. + * @return externalEntityRecognizer + */ + public Recognizer getExternalEntityRecognizer() { + return externalEntityRecognizer; + } + + /** + * Sets External entity recognizer to recognize external entities to pass to LUIS. + * @param externalEntityRecognizer External Recognizer instance. + */ + public void setExternalEntityRecognizer(Recognizer externalEntityRecognizer) { + this.externalEntityRecognizer = externalEntityRecognizer; + } + + /** + * Gets indicating whether all intents come back or only the top one. True for returning all intents. + * @return True for returning all intents. + */ + public boolean isIncludeAllIntents() { + return includeAllIntents; + } + + /** + * Sets indicating whether all intents come back or only the top one. + * @param includeAllIntents True for returning all intents. + */ + public void setIncludeAllIntents(boolean includeAllIntents) { + this.includeAllIntents = includeAllIntents; + } + + /** + * Gets value indicating whether or not instance data should be included in response. + * @return True if instance data should be included in response. + */ + public boolean isIncludeInstanceData() { + return includeInstanceData; + } + + /** + * Sets value indicating whether or not instance data should be included in response. + * @param includeInstanceData True if instance data should be included in response. + */ + public void setIncludeInstanceData(boolean includeInstanceData) { + this.includeInstanceData = includeInstanceData; + } + + /** + * Value indicating whether queries should be logged in LUIS. If queries should be logged in LUIS in order to help + * build better models through active learning + * @return True if queries should be logged in LUIS. + */ + public boolean isLog() { + return log; + } + + /** + * Value indicating whether queries should be logged in LUIS. If queries should be logged in LUIS in order to help + * build better models through active learning. + * @param log True if queries should be logged in LUIS. + */ + public void setLog(boolean log) { + this.log = log; + } + + /** + * Returns Dynamic lists used to recognize entities for a particular query. + * @return Dynamic lists used to recognize entities for a particular query + */ + public List getDynamicLists() { + return dynamicLists; + } + + /** + * Sets Dynamic lists used to recognize entities for a particular query. + * @param dynamicLists to recognize entities for a particular query. + */ + public void setDynamicLists(List dynamicLists) { + this.dynamicLists = dynamicLists; + } + + /** + * Gets External entities to be recognized in query. + * @return External entities to be recognized in query. + */ + public List getExternalEntities() { + return externalEntities; + } + + /** + * Sets External entities to be recognized in query. + * @param externalEntities External entities to be recognized in query. + */ + public void setExternalEntities(List externalEntities) { + this.externalEntities = externalEntities; + } + + /** + * Gets value indicating whether external entities should override other means of recognizing entities. + * @return True if external entities should be preferred to the results from LUIS models. + */ + public boolean isPreferExternalEntities() { + return preferExternalEntities; + } + + /** + * Sets value indicating whether external entities should override other means of recognizing entities. + * @param preferExternalEntities True if external entities should be preferred to the results from LUIS models. + */ + public void setPreferExternalEntities(boolean preferExternalEntities) { + this.preferExternalEntities = preferExternalEntities; + } + + /** + * Gets datetimeV2 offset. The format for the datetimeReference is ISO 8601. + * @return The format for the datetimeReference in ISO 8601. + */ + public String getDateTimeReference() { + return dateTimeReference; + } + + /** + * Sets datetimeV2 offset. + * @param dateTimeReference The format for the datetimeReference is ISO 8601. + */ + public void setDateTimeReference(String dateTimeReference) { + this.dateTimeReference = dateTimeReference; + } + + /** + * Gets the LUIS slot to use for the application. By default this uses the production slot. + * You can find other standard slots in LuisSlot. If you specify a Version, + * then a private version of the application is used instead of a slot. + * @return LuisSlot constant. + */ + public String getSlot() { + return slot; + } + + /** + * Sets the LUIS slot to use for the application. By default this uses the production slot. + * You can find other standard slots in LuisSlot. If you specify a Version, + * then a private version of the application is used instead of a slot. + * @param slot LuisSlot value to use. + */ + public void setSlot(String slot) { + this.slot = slot; + } + + /** + * Gets the specific version of the application to access. + * LUIS supports versions and this is the version to use instead of a slot. + * If this is specified, then the Slot is ignored. + * @return Luis application version to Query. + */ + public String getVersion() { + return version; + } + + /** + * Sets the specific version of the application to access. + * LUIS supports versions and this is the version to use instead of a slot. + * @param version Luis Application version. If this is specified, then the Slot is ignored. + */ + public void setVersion(String version) { + this.version = version; + } + + /** + * Gets whether the http client. + * @return OkHttpClient used to query the Luis Service. + */ + public OkHttpClient getHttpClient() { + return httpClient; + } + + /** + * Sets the http client. + * @param httpClient to use for Luis Service http calls. + */ + public void setHttpClient(OkHttpClient httpClient) { + this.httpClient = httpClient; + } + + /** + * Initializes a new instance of the LuisRecognizerOptionsV3. + * @param application Luis Application instance to query. + */ + public LuisRecognizerOptionsV3(LuisApplication application) { + super(application); + } + + /** + * Internal implementation of the http request to the LUIS service and parsing of the response to a + * Recognizer Result instance. + * @param dialogContext Context Object. + * @param activity Activity object to extract the utterance. + */ + @Override + CompletableFuture recognizeInternal( + DialogContext dialogContext, + Activity activity + ) { + if (externalEntityRecognizer == null) { + return recognizeInternal( + dialogContext.getContext(), + activity.getText()); + } + + // call external entity recognizer + List originalExternalEntities = externalEntities; + return externalEntityRecognizer + .recognize(dialogContext, activity) + .thenCompose( + matches -> { + if (matches.getEntities() == null + || matches.getEntities().toString().equals("{}")) { + return recognizeInternal( + dialogContext.getContext(), + activity.getText()); + } + + List recognizerExternalEntities = new ArrayList<>(); + JsonNode entities = matches.getEntities(); + JsonNode instance = entities.get("$instance"); + + if (instance == null) { + return recognizeInternal( + dialogContext.getContext(), + activity.getText()); + } + + Iterator> instanceEntitiesIterator = instance.fields(); + + while (instanceEntitiesIterator.hasNext()) { + Map.Entry property = instanceEntitiesIterator.next(); + + if (property.getKey().equals("text") + || property.getKey().equals("$instance")) { + continue; + } + + ArrayNode instances = (ArrayNode) instance.get(property.getKey()); + ArrayNode values = (ArrayNode) property.getValue(); + + if (instances == null + || values == null + || instances.size() != values.size()) { + continue; + } + + for (JsonNode childInstance : values) { + if (childInstance != null + && childInstance.has("startIndex") + && childInstance.has("endIndex")) { + int start = childInstance.get("startIndex").asInt(); + int end = childInstance.get("endIndex").asInt(); + recognizerExternalEntities.add(new ExternalEntity( + property.getKey(), + start, + end - start, + property.getValue())); + } + } + recognizerExternalEntities.addAll( + originalExternalEntities == null + ? new ArrayList<>() + : originalExternalEntities + ); + externalEntities = recognizerExternalEntities; + } + + return recognizeInternal(dialogContext.getContext(), activity.getText()) + .thenApply(recognizerResult -> { + externalEntities = originalExternalEntities; + return recognizerResult; + }); + }); + } + + /** + * Internal implementation of the http request to the LUIS service and parsing of the response to a + * Recognizer Result instance. + * @param turnContext Context Object. + */ + @Override + CompletableFuture recognizeInternal( + TurnContext turnContext) { + return recognizeInternal( + turnContext, + turnContext.getActivity().getText()); + } + + private Request buildRequest(RequestBody body) { + StringBuilder path = new StringBuilder(getApplication().getEndpoint()); + path.append(String.format( + "/luis/prediction/v3.0/apps/%s", + getApplication().getApplicationId())); + + if (version == null) { + path.append(String.format("/slots/%s/predict", slot)); + } else { + path.append(String.format("/versions/%s/predict", version)); + } + + HttpUrl.Builder httpBuilder = HttpUrl.parse(path.toString()).newBuilder(); + + httpBuilder.addQueryParameter("verbose", Boolean.toString(includeInstanceData)); + httpBuilder.addQueryParameter("log", Boolean.toString(log)); + httpBuilder.addQueryParameter("show-all-intents", Boolean.toString(includeAllIntents)); + + Request.Builder requestBuilder = new Request.Builder() + .url(httpBuilder.build()) + .addHeader("Ocp-Apim-Subscription-Key", getApplication().getEndpointKey()).post(body); + return requestBuilder.build(); + } + + private RequestBody buildRequestBody(String utterance) throws JsonProcessingException { + + ObjectMapper mapper = new ObjectMapper(); + ObjectNode content = JsonNodeFactory.instance.objectNode().put("query", utterance); + ObjectNode queryOptions = JsonNodeFactory.instance.objectNode().put( + "preferExternalEntities", + preferExternalEntities); + + if (dateTimeReference != null + && !dateTimeReference.isEmpty()) { + queryOptions.put( + "datetimeReference", + dateTimeReference); + } + + content.set("options", queryOptions); + + if (dynamicLists != null) { + content.set("dynamicLists", mapper.valueToTree(dynamicLists)); + } + + if (externalEntities != null) { + for (ExternalEntity entity : externalEntities) { + entity.validate(); + } + content.set("externalEntities", mapper.valueToTree(externalEntities)); + } + + String contentAsText = mapper.writeValueAsString(content); + return RequestBody.create(contentAsText, MediaType.parse("application/json; charset=utf-8")); + } + + private CompletableFuture recognizeInternal( + TurnContext turnContext, + String utterance) { + + RecognizerResult recognizerResult; + JsonNode luisResponse = null; + ObjectMapper mapper = new ObjectMapper(); + + if (utterance == null || utterance.isEmpty()) { + recognizerResult = new RecognizerResult() {{ + setText(utterance); + }}; + } else { + try { + Request request = buildRequest(buildRequestBody(utterance)); + Response response = httpClient.newCall(request).execute(); + luisResponse = mapper.readTree(response.body().string()); + if (!response.isSuccessful()) { + throw new IOException("Unexpected code " + luisResponse.toString()); + } + + } catch (IOException e) { + CompletableFuture exceptionResult = new CompletableFuture<>(); + exceptionResult.completeExceptionally(e); + return exceptionResult; + } + + JsonNode prediction = luisResponse.get("prediction"); + recognizerResult = new RecognizerResult(); + recognizerResult.setText(utterance); + if (prediction.get("alteredQuery") != null) { + recognizerResult.setAlteredText(prediction.get("alteredQuery").asText()); + } + + recognizerResult.setIntents(getIntents(prediction)); + recognizerResult.setEntities(getEntities(prediction)); + + addProperties(prediction, recognizerResult); + if (isIncludeAPIResults()) { + recognizerResult.getProperties().put("luisResult", luisResponse); + } + + if (includeInstanceData + && recognizerResult.getEntities().get(metadataKey) == null) { + ((ObjectNode) recognizerResult.getEntities()).putObject(metadataKey); + } + } + + return sendTraceActivity(recognizerResult, luisResponse, turnContext) + .thenApply(v -> recognizerResult); + + } + + private Map getIntents(JsonNode prediction) { + Map intents = new LinkedHashMap<>(); + + JsonNode intentsObject = prediction.get("intents"); + if (intentsObject == null) { + return intents; + } + + for (Iterator> it = intentsObject.fields(); it.hasNext();) { + Map.Entry intent = it.next(); + double score = intent.getValue() + .get("score") + .asDouble(); + String intentName = intent.getKey() + .replace(".", "_") + .replace(" ", "_"); + intents.put(intentName, new IntentScore() {{ + setScore(score); + }}); + } + + return intents; + } + + private String normalizeEntity(String entity) { + // Type::Role -> Role + String[] type = entity.split(":"); + return type[type.length - 1] + .replace(".", "_") + .replace(" ", "_"); + } + + private JsonNode getEntities(JsonNode prediction) { + if (prediction.get("entities") == null) { + return JsonNodeFactory.instance.objectNode(); + } + + return mapEntitiesRecursive(prediction.get("entities"), false); + } + + // Exact Port from C# + private JsonNode mapEntitiesRecursive( + JsonNode source, + boolean inInstance) { + JsonNode result = source; + if (!source.isArray() + && source.isObject()) { + ObjectNode nobj = JsonNodeFactory.instance.objectNode(); + // Fix datetime by reverting to simple timex + JsonNode obj = source; + JsonNode type = source.get("type"); + + if (!inInstance + && type != null + && dateSubtypes.contains(type.asText())) { + JsonNode timexs = obj.get("values"); + ArrayNode arr = JsonNodeFactory.instance.arrayNode(); + if (timexs != null) { + Set unique = new HashSet<>(); + + for (JsonNode elt: timexs) { + unique.add(elt.get("timex").textValue()); + } + + for (String timex : unique) { + arr.add(timex); + } + + nobj.set("timex", arr); + } + + nobj.set("type", type); + } else { + // Map or remove properties + Iterator> nodes = obj.fields(); + while (nodes.hasNext()) { + Map.Entry property = (Map.Entry) nodes.next(); + String name = normalizeEntity(property.getKey()); + boolean isArray = property.getValue().isArray(); + boolean isString = property.getValue().isTextual(); + boolean isInt = property.getValue().isInt(); + JsonNode val = mapEntitiesRecursive( + property.getValue(), + inInstance || name.equals(metadataKey)); + + if (name.equals("datetime") + && isArray) { + nobj.set("datetimeV1", val); + } else if (name.equals("datetimeV2") + && isArray) { + nobj.set("datetime", val); + } else if (inInstance) { + // Correct $instance issues + if (name.equals("length") && isInt) { + int value = property.getValue().intValue(); + if (obj.get("startIndex") != null) { + value += obj.get("startIndex").intValue(); + } + nobj.put("endIndex", value); + } else if (!((isInt && name.equals("modelTypeId")) || //NOPMD + (isString && name.equals("role")))) { //NOPMD + nobj.set(name, val); + } + } else { + // Correct non-$instance values + if (name.equals("unit") && isString) { + nobj.set("units", val); + } else { + nobj.set(name, val); + } + } + } + } + + result = nobj; + } else if (source.isArray()) { + JsonNode arr = source; + ArrayNode narr = JsonNodeFactory.instance.arrayNode(); + for (JsonNode elt : arr) { + // Check if element is geographyV2 + String isGeographyV2 = ""; + + Iterator> nodes = elt.fields(); + while (nodes.hasNext()) { + Map.Entry props = (Map.Entry) nodes.next(); + + if (props == null) { + break; + } + + if (props.getKey().contains("type") + && geographySubtypes.contains(props.getValue().textValue())) { + isGeographyV2 = props.getValue().textValue(); + break; + } + } + + if (!inInstance && !isGeographyV2.isEmpty()) { + ObjectNode geoEntity = JsonNodeFactory.instance.objectNode(); + nodes = elt.fields(); + while (nodes.hasNext()) { + Map.Entry tokenProp = (Map.Entry) nodes.next(); + + if (tokenProp.getKey().contains("value")) { + geoEntity.set("location", tokenProp.getValue()); + } + } + + geoEntity.put("type", isGeographyV2); + narr.add(geoEntity); + } else { + narr.add(mapEntitiesRecursive(elt, inInstance)); + } + } + result = narr; + } + + return result; + } + + private void addProperties( + JsonNode prediction, + RecognizerResult result) { + JsonNode sentiment = prediction.get("sentiment"); + if (sentiment != null) { + ObjectNode sentimentNode = JsonNodeFactory.instance.objectNode(); + sentimentNode.set("label", sentiment.get("label")); + sentimentNode.set("score", sentiment.get("score")); + result.getProperties().put("sentiment", sentimentNode); + } + } + + private CompletableFuture sendTraceActivity( + RecognizerResult recognizerResult, + JsonNode luisResponse, + TurnContext turnContext) { + ObjectMapper mapper = new ObjectMapper(); + try { + ObjectNode traceInfo = JsonNodeFactory.instance.objectNode(); + traceInfo.put( + "recognizerResult", + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(recognizerResult)); + traceInfo.set( + "luisResult", + luisResponse); + traceInfo.set( + "luisModel", + JsonNodeFactory.instance.objectNode() + .put("ModelId", + getApplication().getApplicationId())); + + ObjectNode luisOptions = JsonNodeFactory.instance.objectNode(); + luisOptions.put("includeAllIntents", includeAllIntents); + luisOptions.put("includeInstanceData", includeInstanceData); + luisOptions.put("log", log); + luisOptions.put("preferExternalEntities", preferExternalEntities); + luisOptions.put("dateTimeReference", dateTimeReference); + luisOptions.put("slot", slot); + luisOptions.put("version", version); + + + if (externalEntities != null) { + ArrayNode externalEntitiesNode = JsonNodeFactory.instance.arrayNode(); + for (ExternalEntity e : externalEntities) { + externalEntitiesNode.add(mapper.valueToTree(e)); + } + luisOptions.put("externalEntities", externalEntitiesNode); + } + + if (dynamicLists != null) { + ArrayNode dynamicListNode = JsonNodeFactory.instance.arrayNode(); + for (DynamicList e : dynamicLists) { + dynamicListNode.add(mapper.valueToTree(e)); + } + luisOptions.put("dynamicLists", dynamicListNode); + } + + traceInfo.set("luisOptions", luisOptions); + + return turnContext.sendActivity( + Activity.createTraceActivity( + "LuisRecognizer", + LUIS_TRACE_TYPE, + traceInfo, + LUIS_TRACE_LABEL)); + + } catch (IOException e) { + CompletableFuture exceptionResult = new CompletableFuture<>(); + exceptionResult.completeExceptionally(e); + return exceptionResult; + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisSlot.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisSlot.java new file mode 100644 index 000000000..6ab42ea68 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisSlot.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +/** + * Utility class to set the Luis endpoint Slot. + * + */ +public final class LuisSlot { + + //Not Called + private LuisSlot() { + + } + + /** + * Production slot on LUIS. + */ + public static final String PRODUCTION = "production"; + + /** + * Staging slot on LUIS. + */ + public static final String STAGING = "staging"; +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisTelemetryConstants.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisTelemetryConstants.java new file mode 100644 index 000000000..f3ae43e84 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisTelemetryConstants.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +/** + * Utility class to set the telemetry values for the Luis Recognizer. + * + */ +public final class LuisTelemetryConstants { + + private LuisTelemetryConstants() { + + } + + /** + * The Key used when storing a LUIS Result in a custom event within telemetry. + */ + public static final String LUIS_RESULT = "LuisResult"; // Event name + + /** + * The Key used when storing a LUIS app ID in a custom event within telemetry. + */ + public static final String APPLICATION_ID_PROPERTY = "applicationId"; + + /** + * The Key used when storing a LUIS intent in a custom event within telemetry. + */ + public static final String INTENT_PROPERTY = "intent"; + + /** + * The Key used when storing a LUIS intent score in a custom event within telemetry. + */ + public static final String INTENT_SCORE_PROPERTY = "intentScore"; + + /** + * The Key used when storing a LUIS intent in a custom event within telemetry. + */ + public static final String INTENT_2_PROPERTY = "intent2"; + + /** + * The Key used when storing a LUIS intent score in a custom event within telemetry. + */ + public static final String INTENT_SCORE_2_PROPERTY = "intentScore2"; + + /** + * The Key used when storing LUIS entities in a custom event within telemetry. + */ + public static final String ENTITIES_PROPERTY = "entities"; + + /** + * The Key used when storing the LUIS query in a custom event within telemetry. + */ + public static final String QUESTION_PROPERTY = "question"; + + /** + * The Key used when storing an Activity ID in a custom event within telemetry. + */ + public static final String ACTIVITY_ID_PROPERTY = "activityId"; + + /** + * The Key used when storing a sentiment label in a custom event within telemetry. + */ + public static final String SENTIMENT_LABEL_PROPERTY = "sentimentLabel"; + + /** + * The Key used when storing a LUIS sentiment score in a custom event within telemetry. + */ + public static final String SENTIMENT_SCORE_PROPERTY = "sentimentScore"; + + /** + * The Key used when storing the FromId in a custom event within telemetry. + */ + public static final String FROM_ID_PROPERTY = "fromId"; +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/TelemetryRecognizer.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/TelemetryRecognizer.java new file mode 100644 index 000000000..9ea56fec3 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/TelemetryRecognizer.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.Recognizer; +import com.microsoft.bot.builder.RecognizerConvert; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Telemetry Recognizer to enforce controls and properties on telemetry logged. + * + */ +public abstract class TelemetryRecognizer implements Recognizer { + + private boolean logPersonalInformation; + + private BotTelemetryClient telemetryClient; + + /** + * Indicates if personal information should be sent as telemetry. + * @return value boolean value to control personal information logging. + */ + public boolean isLogPersonalInformation() { + return logPersonalInformation; + } + + /** + * Indicates if personal information should be sent as telemetry. + * @param logPersonalInformation to set personal information logging preference. + */ + protected void setLogPersonalInformation(boolean logPersonalInformation) { + this.logPersonalInformation = logPersonalInformation; + } + + /** + * Gets the currently configured Bot Telemetry Client that logs the LuisResult event. + * @return The Bot Telemetry Client. + */ + protected BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the currently configured Bot Telemetry Client that logs the LuisResult event. + * @param telemetryClient Bot Telemetry Client. + */ + public void setTelemetryClient(BotTelemetryClient telemetryClient) { + this.telemetryClient = telemetryClient; + } + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + abstract CompletableFuture recognize( + TurnContext turnContext, + Map telemetryProperties, + Map telemetryMetrics); + + /** + * Return results of the analysis (Suggested actions and intents). + * @param turnContext Context object containing information for a single turn of conversation with a user. + * @param telemetryProperties Additional properties to be logged to telemetry with the LuisResult event. + * @param telemetryMetrics Additional metrics to be logged to telemetry with the LuisResult event. + * @param Result type. + * @param c The recognition result type class + * @return The LUIS results of the analysis of the current message text in the current turn's context activity. + */ + abstract CompletableFuture recognize( + TurnContext turnContext, + Map telemetryProperties, + Map telemetryMetrics, + Class c); + +} diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/package-info.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/package-info.java new file mode 100644 index 000000000..181eab803 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/package-info.java @@ -0,0 +1,9 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-AI-LUIS. + */ + +package com.microsoft.bot.ai.luis; diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisApplicationTests.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisApplicationTests.java new file mode 100644 index 000000000..c550451b9 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisApplicationTests.java @@ -0,0 +1,89 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class LuisApplicationTests { + String validUUID = "b31aeaf3-3511-495b-a07f-571fc873214b"; + String invalidUUID = "0000"; + String validEndpoint = "https://www.test.com"; + String invalidEndpoint = "www.test.com"; + + @Test + public void invalidSubscriptionKey() { + + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + LuisApplication lA = new LuisApplication( + validUUID, + invalidUUID, + validEndpoint); + }); + + String expectedMessage = String.format("%s is not a valid LUIS subscription key.", invalidUUID); + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + public void invalidApplicationId () { + + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + LuisApplication lA = new LuisApplication( + invalidUUID, + validUUID, + validEndpoint); + }); + + String expectedMessage = String.format("%s is not a valid LUIS application id.", invalidUUID); + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + public void invalidEndpoint() { + + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + LuisApplication lA = new LuisApplication( + validUUID, + validUUID, + invalidEndpoint); + }); + + String expectedMessage = String.format("%s is not a valid LUIS endpoint.", invalidEndpoint); + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + public void CreatesNewLuisApplication() { + + LuisApplication lA = new LuisApplication( + validUUID, + validUUID, + validEndpoint + ); + + assertTrue(lA.getApplicationId().equals(validUUID)); + assertTrue(lA.getEndpointKey().equals(validUUID)); + assertTrue(lA.getEndpoint().equals(validEndpoint)); + } + + @Test + public void CreatesNewLuisApplicationFromURL() { + String url = "https://westus.api.cognitive.microsoft.com/luis/prediction/v3.0/apps/b31aeaf3-3511-495b-a07f-571fc873214b/slots/production/predict?verbose=true&timezoneOffset=-360&subscription-key=048ec46dc58e495482b0c447cfdbd291"; + LuisApplication lA = new LuisApplication(url); + + assertTrue(lA.getApplicationId().equals("b31aeaf3-3511-495b-a07f-571fc873214b")); + assertTrue(lA.getEndpointKey().equals("048ec46dc58e495482b0c447cfdbd291")); + assertTrue(lA.getEndpoint().equals("https://westus.api.cognitive.microsoft.com")); + } + + +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java new file mode 100644 index 000000000..f8015fec3 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java @@ -0,0 +1,334 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.Recognizer; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.ResourceResponse; +import okhttp3.HttpUrl; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; +import okhttp3.mockwebserver.RecordedRequest; +import org.apache.commons.io.FileUtils; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Paths; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + + +@ExtendWith(MockitoExtension.class) +public class LuisRecognizerOptionsV3Tests { + + @Mock + DialogContext dC; + + @Mock + Recognizer recognizer; + + @Mock + TurnContext turnContext; + + // Set this values to test against the service + String applicationId = "b31aeaf3-3511-495b-a07f-571fc873214b"; + String subscriptionKey = "b31aeaf3-3511-495b-a07f-571fc873214b"; + boolean mockLuisResponse = true; + + @ParameterizedTest + @ValueSource(strings = { + "Composite1.json", + "Composite2.json", + "Composite3.json", + "DateTimeReference.json", + "DynamicListsAndList.json", + "ExternalEntitiesAndBuiltin.json", + "ExternalEntitiesAndComposite.json", + "ExternalEntitiesAndList.json", + "ExternalEntitiesAndRegex.json", + "ExternalEntitiesAndSimple.json", + "ExternalEntitiesAndSimpleOverride.json", + "GeoPeopleOrdinal.json", + "Minimal.json", +// "MinimalWithGeo.json", + "NoEntitiesInstanceTrue.json", + "Patterns.json", + "Prebuilt.json", + "roles.json", + "TraceActivity.json", + "Typed.json", + "TypedPrebuilt.json" + }) // six numbers + public void shouldParseLuisResponsesCorrectly_TurnContextPassed(String fileName) { + RecognizerResult result = null, expected = null; + MockWebServer mockWebServer = new MockWebServer(); + + try { + // Get Oracle file + String content = readFileContent("/src/test/java/com/microsoft/bot/ai/luis/testdata/" + fileName); + + //Extract V3 response + ObjectMapper mapper = new ObjectMapper(); + JsonNode testData = mapper.readTree(content); + JsonNode v3SettingsAndResponse = testData.get("v3"); + JsonNode v3Response = v3SettingsAndResponse.get("response"); + + //Extract V3 Test Settings + JsonNode testSettings = v3SettingsAndResponse.get("options"); + + // Set mock response in MockWebServer + StringBuilder pathToMock = new StringBuilder("/luis/prediction/v3.0/apps/"); + String url = buildUrl(pathToMock, testSettings); + String endpoint = ""; + if (this.mockLuisResponse) { + endpoint = String.format( + "http://localhost:%s", + initializeMockServer( + mockWebServer, + v3Response, + url).port()); + } + + // Set LuisRecognizerOptions data + LuisRecognizerOptionsV3 v3 = buildTestRecognizer(endpoint, testSettings); + + // Run test + Activity activity = new Activity() { + { + setText(testData.get("text").asText()); + setType(ActivityTypes.MESSAGE); + setChannelId("EmptyContext"); + } + }; + doReturn(activity) + .when(turnContext) + .getActivity(); + + doReturn(CompletableFuture.completedFuture(new ResourceResponse())) + .when(turnContext) + .sendActivity(any(Activity.class)); + + result = v3.recognizeInternal(turnContext).get(); + + // Build expected result + expected = mapper.readValue(content, RecognizerResult.class); + Map properties = expected.getProperties(); + properties.remove("v2"); + properties.remove("v3"); + + assertEquals(mapper.writeValueAsString(expected), mapper.writeValueAsString(result)); + + RecordedRequest request = mockWebServer.takeRequest(); + assertEquals(String.format("POST %s HTTP/1.1", pathToMock.toString()), request.getRequestLine()); + assertEquals(pathToMock.toString(), request.getPath()); + + verify(turnContext, times(1)).sendActivity(any (Activity.class)); + + } catch (InterruptedException | ExecutionException | IOException e) { + e.printStackTrace(); + assertFalse(true); + } finally { + try { + mockWebServer.shutdown(); + } catch (IOException e) { + // Empty error + } + } + } + + @Test + public void shouldBuildExternalEntities_DialogContextPassed_ExternalRecognizer() { + MockWebServer mockWebServer = new MockWebServer(); + + try { + // Get Oracle file + String content = readFileContent("/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalRecognizer.json"); + + //Extract V3 response + ObjectMapper mapper = new ObjectMapper(); + JsonNode testData = mapper.readTree(content); + JsonNode v3SettingsAndResponse = testData.get("v3"); + JsonNode v3Response = v3SettingsAndResponse.get("response"); + + //Extract V3 Test Settings + JsonNode testSettings = v3SettingsAndResponse.get("options"); + + // Set mock response in MockWebServer + StringBuilder pathToMock = new StringBuilder("/luis/prediction/v3.0/apps/"); + String url = buildUrl(pathToMock, testSettings); + String endpoint = String.format( + "http://localhost:%s", + initializeMockServer( + mockWebServer, + v3Response, + url).port()); + + // Set LuisRecognizerOptions data + LuisRecognizerOptionsV3 v3 = buildTestRecognizer(endpoint, testSettings); + v3.setExternalEntityRecognizer(recognizer); + + Activity activity = new Activity() { + { + setText(testData.get("text").asText()); + setType(ActivityTypes.MESSAGE); + setChannelId("EmptyContext"); + } + }; + + doReturn(CompletableFuture.completedFuture(new ResourceResponse())) + .when(turnContext) + .sendActivity(any(Activity.class)); + + when(dC.getContext()).thenReturn(turnContext); + + doReturn(CompletableFuture.supplyAsync(() -> new RecognizerResult(){{ + setEntities(testSettings.get("ExternalRecognizerResult")); + }})) + .when(recognizer) + .recognize(any(DialogContext.class), any(Activity.class)); + + v3.recognizeInternal(dC, activity).get(); + + RecordedRequest request = mockWebServer.takeRequest(); + String resultBody = request.getBody().readUtf8(); + assertEquals("{\"query\":\"deliver 35 WA to repent harelquin\"," + + "\"options\":{\"preferExternalEntities\":true}," + + "\"externalEntities\":[{\"entityName\":\"Address\",\"startIndex\":17,\"entityLength\":16," + + "\"resolution\":[{\"endIndex\":33,\"modelType\":\"Composite Entity Extractor\"," + + "\"resolution\":{\"number\":[3],\"State\":[\"France\"]}," + + "\"startIndex\":17,\"text\":\"repent harelquin\",\"type\":\"Address\"}]}]}", + resultBody); + + } catch (InterruptedException | ExecutionException | IOException e) { + e.printStackTrace(); + assertFalse(true); + } finally { + try { + mockWebServer.shutdown(); + } catch (IOException e) { + e.printStackTrace(); + } + } + } + + public static TurnContext createContext(String message) { + + Activity activity = new Activity() { + { + setText(message); + setType(ActivityTypes.MESSAGE); + setChannelId("EmptyContext"); + } + }; + + return new TurnContextImpl(new NotImplementedAdapter(), activity); + } + + private static class NotImplementedAdapter extends BotAdapter { + @Override + public CompletableFuture sendActivities( + TurnContext context, + List activities + ) { + return CompletableFuture.completedFuture(null); + } + + @Override + public CompletableFuture updateActivity( + TurnContext context, + Activity activity + ) { + throw new RuntimeException(); + } + + @Override + public CompletableFuture deleteActivity( + TurnContext context, + ConversationReference reference + ) { + throw new RuntimeException(); + } + } + + private String readFileContent (String pathToFile) throws IOException { + String path = Paths.get("").toAbsolutePath().toString(); + File file = new File(path + pathToFile); + return FileUtils.readFileToString(file, "utf-8"); + } + + private String buildUrl(StringBuilder pathToMock, JsonNode testSettings) { + pathToMock.append(this.applicationId); + + if (testSettings.get("Version") != null ) { + pathToMock.append(String.format("/versions/%s/predict", testSettings.get("Version").asText())); + } else { + pathToMock.append(String.format("/slots/%s/predict", testSettings.get("Slot").asText())); + } + pathToMock.append( + String.format( + "?verbose=%s&log=%s&show-all-intents=%s", + testSettings.get("IncludeInstanceData").asText(), + testSettings.get("Log").asText(), + testSettings.get("IncludeAllIntents").asText() + ) + ); + + return pathToMock.toString(); + } + + private HttpUrl initializeMockServer(MockWebServer mockWebServer, JsonNode v3Response, String url) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + String mockResponse = mapper.writeValueAsString(v3Response); + mockWebServer.enqueue(new MockResponse() + .addHeader("Content-Type", "application/json; charset=utf-8") + .setBody(mockResponse)); + + mockWebServer.start(); + + return mockWebServer.url(url); + } + + private LuisRecognizerOptionsV3 buildTestRecognizer (String endpoint, JsonNode testSettings) throws IOException { + ObjectMapper mapper = new ObjectMapper(); + ObjectReader readerDynamicList = mapper.readerFor(new TypeReference>() {}); + ObjectReader readerExternalentities = mapper.readerFor(new TypeReference>() {}); + return new LuisRecognizerOptionsV3( + new LuisApplication( + this.applicationId, + this.subscriptionKey, + endpoint)) {{ + setIncludeInstanceData(testSettings.get("IncludeInstanceData").asBoolean()); + setIncludeAllIntents(testSettings.get("IncludeAllIntents").asBoolean()); + setVersion(testSettings.get("Version") == null ? null : testSettings.get("Version").asText()); + setDynamicLists(testSettings.get("DynamicLists") == null ? null : readerDynamicList.readValue(testSettings.get("DynamicLists"))); + setExternalEntities(testSettings.get("ExternalEntities") == null ? null : readerExternalentities.readValue(testSettings.get("ExternalEntities"))); + setDateTimeReference(testSettings.get("DateTimeReference") == null ? null : testSettings.get("DateTimeReference").asText()); + }}; + } + +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java new file mode 100644 index 000000000..35ae3ba07 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java @@ -0,0 +1,369 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.ai.luis; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.microsoft.bot.ai.luis.testdata.TestRecognizerResultConvert; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.IntentScore; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; + +import static org.junit.Assert.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +public class LuisRecognizerTests { + + @Mock + LuisRecognizerOptionsV3 options; + + @Mock + BotTelemetryClient telemetryClient; + + @Mock + TurnContext turnContext; + + @Mock + DialogContext dialogContext; + + @Mock + LuisApplication luisApplication; + + private RecognizerResult mockedResult = new RecognizerResult(){{ + setIntents(new HashMap(){{ + put("Test", + new IntentScore(){{ + setScore(0.2); + }}); + put("Greeting", + new IntentScore(){{ + setScore(0.4); + }}); + }}); + setEntities(JsonNodeFactory.instance.objectNode()); + setProperties( + "sentiment", + JsonNodeFactory.instance.objectNode() + .put( + "label", + "neutral")); + }}; + + @Test + public void topIntentReturnsTopIntent() { + String defaultIntent = LuisRecognizer + .topIntent(mockedResult); + assertEquals(defaultIntent, "Greeting"); + } + + @Test + public void topIntentReturnsDefaultIfMinScoreIsHigher() { + String defaultIntent = LuisRecognizer + .topIntent(mockedResult, 0.5); + assertEquals(defaultIntent, "None"); + } + + @Test + public void topIntentReturnsDefaultIfProvided() { + String defaultIntent = LuisRecognizer + .topIntent(mockedResult, "Test2", 0.5); + assertEquals(defaultIntent, "Test2"); + } + + @Test + public void topIntentThrowsIllegalArgumentIfResultIsNull() { + Exception exception = assertThrows(IllegalArgumentException.class, () -> { + LuisRecognizer.topIntent(null); + }); + + String expectedMessage = "RecognizerResult"; + String actualMessage = exception.getMessage(); + + assertTrue(actualMessage.contains(expectedMessage)); + } + + @Test + public void TopIntentReturnsTopIntentIfScoreEqualsMinScore() { + String defaultIntent = LuisRecognizer.topIntent(mockedResult, 0.4); + assertEquals(defaultIntent, "Greeting"); + } + + @Test + public void recognizerResult() { + setMockObjectsForTelemetry(); + LuisRecognizer recognizer = new LuisRecognizer(options); + RecognizerResult expected = new RecognizerResult(){{ + setText("Random Message"); + setIntents(new HashMap(){{ + put("Test", + new IntentScore(){{ + setScore(0.2); + }}); + put("Greeting", + new IntentScore(){{ + setScore(0.4); + }}); + }}); + setEntities(JsonNodeFactory.instance.objectNode()); + setProperties( + "sentiment", + JsonNodeFactory.instance.objectNode() + .put( + "label", + "neutral")); + }}; + RecognizerResult actual = null; + try { + actual = recognizer.recognize(turnContext).get(); + ObjectMapper mapper = new ObjectMapper(); + assertEquals(mapper.writeValueAsString(expected), mapper.writeValueAsString(actual)); + } catch (InterruptedException | ExecutionException | JsonProcessingException e) { + e.printStackTrace(); + } + } + + @Test + public void recognizerResultDialogContext() { + RecognizerResult expected = new RecognizerResult(){{ + setText("Random Message"); + setIntents(new HashMap(){{ + put("Test", + new IntentScore(){{ + setScore(0.2); + }}); + put("Greeting", + new IntentScore(){{ + setScore(0.4); + }}); + }}); + setEntities(JsonNodeFactory.instance.objectNode()); + setProperties( + "sentiment", + JsonNodeFactory.instance.objectNode() + .put( + "label", + "neutral")); + }}; + RecognizerResult actual = null; + when(turnContext.getActivity()) + .thenReturn(new Activity() {{ + setText("Random Message"); + setType(ActivityTypes.MESSAGE); + setChannelId("EmptyContext"); + setFrom(new ChannelAccount(){{ + setId("Activity-from-ID"); + }}); + }}); + + when(luisApplication.getApplicationId()) + .thenReturn("b31aeaf3-3511-495b-a07f-571fc873214b"); + + when(options.getTelemetryClient()).thenReturn(telemetryClient); + + when(options.getApplication()) + .thenReturn(luisApplication); + mockedResult.setText("Random Message"); + when(dialogContext.getContext()) + .thenReturn(turnContext); + + doReturn(CompletableFuture.supplyAsync(() -> mockedResult)) + .when(options) + .recognizeInternal( + any(DialogContext.class), any(Activity.class)); + LuisRecognizer recognizer = new LuisRecognizer(options); + try { + actual = recognizer.recognize(dialogContext, turnContext.getActivity()).get(); + ObjectMapper mapper = new ObjectMapper(); + assertEquals(mapper.writeValueAsString(expected), mapper.writeValueAsString(actual)); + } catch (InterruptedException | ExecutionException | JsonProcessingException e) { + e.printStackTrace(); + } + } + + + @Test + public void recognizerResultConverted() { + + setMockObjectsForTelemetry(); + LuisRecognizer recognizer = new LuisRecognizer(options); + TestRecognizerResultConvert actual = null; + try { + actual = recognizer.recognize(turnContext, TestRecognizerResultConvert.class).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + + TestRecognizerResultConvert expected = new TestRecognizerResultConvert(){{ + recognizerResultText = "Random Message"; + }}; + + assertEquals(expected.recognizerResultText, actual.recognizerResultText); + } + + @Test + public void telemetryPropertiesAreFilledOnRecognizer() { + + setMockObjectsForTelemetry(); + LuisRecognizer recognizer = new LuisRecognizer(options); + + try { + recognizer.recognize(turnContext).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + Map expectedProperties = new HashMap (){{ + put("intentScore", "0.4"); + put("intent2", "Test"); + put("entities", "{}"); + put("intentScore2", "0.2"); + put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + put("intent", "Greeting"); + put("fromId", "Activity-from-ID"); + put("sentimentLabel", "neutral"); + }}; + + verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, null); + } + + @Test + public void telemetry_PiiLogged() { + + setMockObjectsForTelemetry(); + when(options.isLogPersonalInformation()).thenReturn(true); + + LuisRecognizer recognizer = new LuisRecognizer(options); + + try { + recognizer.recognize(turnContext).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + Map expectedProperties = new HashMap (){{ + put("intentScore", "0.4"); + put("intent2", "Test"); + put("entities", "{}"); + put("intentScore2", "0.2"); + put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + put("intent", "Greeting"); + put("fromId", "Activity-from-ID"); + put("sentimentLabel", "neutral"); + put("question", "Random Message"); + }}; + + verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, null); + } + + @Test + public void telemetry_additionalProperties() { + setMockObjectsForTelemetry(); + when(options.isLogPersonalInformation()).thenReturn(true); + + LuisRecognizer recognizer = new LuisRecognizer(options); + Map additionalProperties = new HashMap(){{ + put("test", "testvalue"); + put("foo", "foovalue"); + }}; + Map telemetryMetrics = new HashMap(){{ + put("test", 3.1416); + put("foo", 2.11); + }}; + try { + recognizer.recognize(turnContext, additionalProperties, telemetryMetrics).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + Map expectedProperties = new HashMap (){{ + put("intentScore", "0.4"); + put("intent2", "Test"); + put("entities", "{}"); + put("intentScore2", "0.2"); + put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + put("intent", "Greeting"); + put("fromId", "Activity-from-ID"); + put("sentimentLabel", "neutral"); + put("question", "Random Message"); + put("test", "testvalue"); + put("foo", "foovalue"); + }}; + + verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, telemetryMetrics); + } + + @Test + public void telemetry_additionalPropertiesOverrideProperty() { + setMockObjectsForTelemetry(); + when(options.isLogPersonalInformation()).thenReturn(true); + + LuisRecognizer recognizer = new LuisRecognizer(options); + Map additionalProperties = new HashMap(){{ + put("intentScore", "1.15"); + put("foo", "foovalue"); + }}; + Map telemetryMetrics = new HashMap(){{ + put("test", 3.1416); + put("foo", 2.11); + }}; + try { + recognizer.recognize(turnContext, additionalProperties, telemetryMetrics).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + } + Map expectedProperties = new HashMap (){{ + put("intentScore", "1.15"); + put("intent2", "Test"); + put("entities", "{}"); + put("intentScore2", "0.2"); + put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + put("intent", "Greeting"); + put("fromId", "Activity-from-ID"); + put("sentimentLabel", "neutral"); + put("question", "Random Message"); + put("foo", "foovalue"); + }}; + + verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, telemetryMetrics); + } + + private void setMockObjectsForTelemetry() { + when(turnContext.getActivity()) + .thenReturn(new Activity() {{ + setText("Random Message"); + setType(ActivityTypes.MESSAGE); + setChannelId("EmptyContext"); + setFrom(new ChannelAccount(){{ + setId("Activity-from-ID"); + }}); + }}); + + when(luisApplication.getApplicationId()) + .thenReturn("b31aeaf3-3511-495b-a07f-571fc873214b"); + + when(options.getTelemetryClient()).thenReturn(telemetryClient); + + when(options.getApplication()) + .thenReturn(luisApplication); + mockedResult.setText("Random Message"); + doReturn(CompletableFuture.supplyAsync(() -> mockedResult)) + .when(options) + .recognizeInternal( + any(TurnContext.class)); + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite1.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite1.json new file mode 100644 index 000000000..69ef1c20a --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite1.json @@ -0,0 +1,1971 @@ +{ + "entities": { + "$instance": { + "begin": [ + { + "endIndex": 12, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old", + "type": "builtin.age" + } + ], + "Composite1": [ + { + "endIndex": 306, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "type": "Composite1" + } + ], + "end": [ + { + "endIndex": 27, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days old", + "type": "builtin.age" + } + ], + "endpos": [ + { + "endIndex": 47, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 44, + "text": "3rd", + "type": "builtin.ordinalV2" + } + ], + "max": [ + { + "endIndex": 167, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 162, + "text": "$4.25", + "type": "builtin.currency" + } + ], + "ordinalV2": [ + { + "endIndex": 199, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 194, + "text": "first", + "type": "builtin.ordinalV2" + }, + { + "endIndex": 285, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 277, + "text": "next one", + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 306, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 294, + "text": "previous one", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "begin": [ + { + "number": 12, + "units": "Year" + } + ], + "Composite1": [ + { + "$instance": { + "datetime": [ + { + "endIndex": 8, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years", + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 23, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days", + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 53, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 32, + "text": "monday july 3rd, 2019", + "type": "builtin.datetimeV2.date" + }, + { + "endIndex": 70, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 58, + "text": "every monday", + "type": "builtin.datetimeV2.set" + }, + { + "endIndex": 97, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 75, + "text": "between 3am and 5:30am", + "type": "builtin.datetimeV2.timerange" + } + ], + "dimension": [ + { + "endIndex": 109, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4 acres", + "type": "builtin.dimension" + }, + { + "endIndex": 127, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4 pico meters", + "type": "builtin.dimension" + } + ], + "email": [ + { + "endIndex": 150, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 132, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "money": [ + { + "endIndex": 157, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 155, + "text": "$4", + "type": "builtin.currency" + } + ], + "number": [ + { + "endIndex": 2, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12", + "type": "builtin.number" + }, + { + "endIndex": 18, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3", + "type": "builtin.number" + }, + { + "endIndex": 53, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "2019", + "type": "builtin.number" + }, + { + "endIndex": 92, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 91, + "text": "5", + "type": "builtin.number" + }, + { + "endIndex": 103, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 115, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 157, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 167, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 163, + "text": "4.25", + "type": "builtin.number" + }, + { + "endIndex": 179, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 177, + "text": "32", + "type": "builtin.number" + }, + { + "endIndex": 189, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 184, + "text": "210.4", + "type": "builtin.number" + }, + { + "endIndex": 206, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10", + "type": "builtin.number" + }, + { + "endIndex": 216, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5", + "type": "builtin.number" + }, + { + "endIndex": 225, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 222, + "text": "425", + "type": "builtin.number" + }, + { + "endIndex": 229, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "555", + "type": "builtin.number" + }, + { + "endIndex": 234, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 230, + "text": "1234", + "type": "builtin.number" + }, + { + "endIndex": 240, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3", + "type": "builtin.number" + }, + { + "endIndex": 258, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5", + "type": "builtin.number" + }, + { + "endIndex": 285, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 282, + "text": "one", + "type": "builtin.number" + }, + { + "endIndex": 306, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 303, + "text": "one", + "type": "builtin.number" + } + ], + "percentage": [ + { + "endIndex": 207, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10%", + "type": "builtin.percentage" + }, + { + "endIndex": 217, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5%", + "type": "builtin.percentage" + } + ], + "phonenumber": [ + { + "endIndex": 234, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 222, + "text": "425-555-1234", + "type": "builtin.phonenumber" + } + ], + "temperature": [ + { + "endIndex": 248, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3 degrees", + "type": "builtin.temperature" + }, + { + "endIndex": 268, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5 degrees c", + "type": "builtin.temperature" + } + ] + }, + "datetime": [ + { + "timex": [ + "P12Y" + ], + "type": "duration" + }, + { + "timex": [ + "P3D" + ], + "type": "duration" + }, + { + "timex": [ + "2019-07-03" + ], + "type": "date" + }, + { + "timex": [ + "XXXX-WXX-1" + ], + "type": "set" + }, + { + "timex": [ + "(T03,T05:30,PT2H30M)" + ], + "type": "timerange" + } + ], + "dimension": [ + { + "number": 4, + "units": "Acre" + }, + { + "number": 4, + "units": "Picometer" + } + ], + "email": [ + "chrimc@hotmail.com" + ], + "money": [ + { + "number": 4, + "units": "Dollar" + } + ], + "number": [ + 12, + 3, + 2019, + 5, + 4, + 4, + 4, + 4.25, + 32, + 210.4, + 10, + 10.5, + 425, + 555, + 1234, + 3, + -27.5, + 1, + 1 + ], + "percentage": [ + 10, + 10.5 + ], + "phonenumber": [ + "425-555-1234" + ], + "temperature": [ + { + "number": 3, + "units": "Degree" + }, + { + "number": -27.5, + "units": "C" + } + ] + } + ], + "end": [ + { + "number": 3, + "units": "Day" + } + ], + "endpos": [ + { + "offset": 3, + "relativeTo": "start" + } + ], + "max": [ + { + "number": 4.25, + "units": "Dollar" + } + ], + "ordinalV2": [ + { + "offset": 1, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "current" + }, + { + "offset": -1, + "relativeTo": "current" + } + ] + }, + "intents": { + "Cancel": { + "score": 1.54311692E-06 + }, + "Delivery": { + "score": 0.000280677923 + }, + "EntityTests": { + "score": 0.958614767 + }, + "Greeting": { + "score": 8.076372E-07 + }, + "Help": { + "score": 4.74059061E-06 + }, + "None": { + "score": 0.0101076821 + }, + "Roles": { + "score": 0.191202149 + }, + "search": { + "score": 0.00475360872 + }, + "SpecifyName": { + "score": 7.367716E-05 + }, + "Travel": { + "score": 0.00232480234 + }, + "Weather_GetForecast": { + "score": 0.0141556319 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "builtin.datetimeV2.duration", + "value": "12 years" + }, + { + "type": "builtin.datetimeV2.duration", + "value": "3 days" + }, + { + "type": "builtin.datetimeV2.date", + "value": "monday july 3rd, 2019" + }, + { + "type": "builtin.datetimeV2.set", + "value": "every monday" + }, + { + "type": "builtin.datetimeV2.timerange", + "value": "between 3am and 5:30am" + }, + { + "type": "builtin.dimension", + "value": "4 acres" + }, + { + "type": "builtin.dimension", + "value": "4 pico meters" + }, + { + "type": "builtin.email", + "value": "chrimc@hotmail.com" + }, + { + "type": "builtin.currency", + "value": "$4" + }, + { + "type": "builtin.number", + "value": "12" + }, + { + "type": "builtin.number", + "value": "3" + }, + { + "type": "builtin.number", + "value": "2019" + }, + { + "type": "builtin.number", + "value": "5" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4.25" + }, + { + "type": "builtin.number", + "value": "32" + }, + { + "type": "builtin.number", + "value": "210.4" + }, + { + "type": "builtin.number", + "value": "10" + }, + { + "type": "builtin.number", + "value": "10.5" + }, + { + "type": "builtin.number", + "value": "425" + }, + { + "type": "builtin.number", + "value": "555" + }, + { + "type": "builtin.number", + "value": "1234" + }, + { + "type": "builtin.number", + "value": "3" + }, + { + "type": "builtin.number", + "value": "-27.5" + }, + { + "type": "builtin.number", + "value": "one" + }, + { + "type": "builtin.number", + "value": "one" + }, + { + "type": "builtin.percentage", + "value": "10%" + }, + { + "type": "builtin.percentage", + "value": "10.5%" + }, + { + "type": "builtin.phonenumber", + "value": "425-555-1234" + }, + { + "type": "builtin.temperature", + "value": "3 degrees" + }, + { + "type": "builtin.temperature", + "value": "-27.5 degrees c" + } + ], + "parentType": "Composite1", + "value": "12 years old and 3 days old and monday july 3rd , 2019 and every monday and between 3am and 5 : 30am and 4 acres and 4 pico meters and chrimc @ hotmail . com and $ 4 and $ 4 . 25 and also 32 and 210 . 4 and first and 10 % and 10 . 5 % and 425 - 555 - 1234 and 3 degrees and - 27 . 5 degrees c and the next one and the previous one" + } + ], + "entities": [ + { + "endIndex": 305, + "entity": "12 years old and 3 days old and monday july 3rd , 2019 and every monday and between 3am and 5 : 30am and 4 acres and 4 pico meters and chrimc @ hotmail . com and $ 4 and $ 4 . 25 and also 32 and 210 . 4 and first and 10 % and 10 . 5 % and 425 - 555 - 1234 and 3 degrees and - 27 . 5 degrees c and the next one and the previous one", + "score": 0.9074669, + "startIndex": 0, + "type": "Composite1" + }, + { + "endIndex": 1, + "entity": "12", + "resolution": { + "subtype": "integer", + "value": "12" + }, + "startIndex": 0, + "type": "builtin.number" + }, + { + "endIndex": 17, + "entity": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "startIndex": 17, + "type": "builtin.number" + }, + { + "endIndex": 52, + "entity": "2019", + "resolution": { + "subtype": "integer", + "value": "2019" + }, + "startIndex": 49, + "type": "builtin.number" + }, + { + "endIndex": 91, + "entity": "5", + "resolution": { + "subtype": "integer", + "value": "5" + }, + "startIndex": 91, + "type": "builtin.number" + }, + { + "endIndex": 102, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 102, + "type": "builtin.number" + }, + { + "endIndex": 114, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 114, + "type": "builtin.number" + }, + { + "endIndex": 156, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 156, + "type": "builtin.number" + }, + { + "endIndex": 166, + "entity": "4.25", + "resolution": { + "subtype": "decimal", + "value": "4.25" + }, + "startIndex": 163, + "type": "builtin.number" + }, + { + "endIndex": 178, + "entity": "32", + "resolution": { + "subtype": "integer", + "value": "32" + }, + "startIndex": 177, + "type": "builtin.number" + }, + { + "endIndex": 188, + "entity": "210.4", + "resolution": { + "subtype": "decimal", + "value": "210.4" + }, + "startIndex": 184, + "type": "builtin.number" + }, + { + "endIndex": 205, + "entity": "10", + "resolution": { + "subtype": "integer", + "value": "10" + }, + "startIndex": 204, + "type": "builtin.number" + }, + { + "endIndex": 215, + "entity": "10.5", + "resolution": { + "subtype": "decimal", + "value": "10.5" + }, + "startIndex": 212, + "type": "builtin.number" + }, + { + "endIndex": 224, + "entity": "425", + "resolution": { + "subtype": "integer", + "value": "425" + }, + "startIndex": 222, + "type": "builtin.number" + }, + { + "endIndex": 228, + "entity": "555", + "resolution": { + "subtype": "integer", + "value": "555" + }, + "startIndex": 226, + "type": "builtin.number" + }, + { + "endIndex": 233, + "entity": "1234", + "resolution": { + "subtype": "integer", + "value": "1234" + }, + "startIndex": 230, + "type": "builtin.number" + }, + { + "endIndex": 239, + "entity": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "startIndex": 239, + "type": "builtin.number" + }, + { + "endIndex": 257, + "entity": "-27.5", + "resolution": { + "subtype": "decimal", + "value": "-27.5" + }, + "startIndex": 253, + "type": "builtin.number" + }, + { + "endIndex": 284, + "entity": "one", + "resolution": { + "subtype": "integer", + "value": "1" + }, + "startIndex": 282, + "type": "builtin.number" + }, + { + "endIndex": 305, + "entity": "one", + "resolution": { + "subtype": "integer", + "value": "1" + }, + "startIndex": 303, + "type": "builtin.number" + }, + { + "endIndex": 11, + "entity": "12 years old", + "resolution": { + "unit": "Year", + "value": "12" + }, + "role": "begin", + "startIndex": 0, + "type": "builtin.age" + }, + { + "endIndex": 26, + "entity": "3 days old", + "resolution": { + "unit": "Day", + "value": "3" + }, + "role": "end", + "startIndex": 17, + "type": "builtin.age" + }, + { + "endIndex": 7, + "entity": "12 years", + "resolution": { + "values": [ + { + "timex": "P12Y", + "type": "duration", + "value": "378432000" + } + ] + }, + "startIndex": 0, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 22, + "entity": "3 days", + "resolution": { + "values": [ + { + "timex": "P3D", + "type": "duration", + "value": "259200" + } + ] + }, + "startIndex": 17, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 52, + "entity": "monday july 3rd, 2019", + "resolution": { + "values": [ + { + "timex": "2019-07-03", + "type": "date", + "value": "2019-07-03" + } + ] + }, + "startIndex": 32, + "type": "builtin.datetimeV2.date" + }, + { + "endIndex": 69, + "entity": "every monday", + "resolution": { + "values": [ + { + "timex": "XXXX-WXX-1", + "type": "set", + "value": "not resolved" + } + ] + }, + "startIndex": 58, + "type": "builtin.datetimeV2.set" + }, + { + "endIndex": 96, + "entity": "between 3am and 5:30am", + "resolution": { + "values": [ + { + "end": "05:30:00", + "start": "03:00:00", + "timex": "(T03,T05:30,PT2H30M)", + "type": "timerange" + } + ] + }, + "startIndex": 75, + "type": "builtin.datetimeV2.timerange" + }, + { + "endIndex": 108, + "entity": "4 acres", + "resolution": { + "unit": "Acre", + "value": "4" + }, + "startIndex": 102, + "type": "builtin.dimension" + }, + { + "endIndex": 126, + "entity": "4 pico meters", + "resolution": { + "unit": "Picometer", + "value": "4" + }, + "startIndex": 114, + "type": "builtin.dimension" + }, + { + "endIndex": 149, + "entity": "chrimc@hotmail.com", + "resolution": { + "value": "chrimc@hotmail.com" + }, + "startIndex": 132, + "type": "builtin.email" + }, + { + "endIndex": 156, + "entity": "$4", + "resolution": { + "unit": "Dollar", + "value": "4" + }, + "startIndex": 155, + "type": "builtin.currency" + }, + { + "endIndex": 166, + "entity": "$4.25", + "resolution": { + "unit": "Dollar", + "value": "4.25" + }, + "role": "max", + "startIndex": 162, + "type": "builtin.currency" + }, + { + "endIndex": 46, + "entity": "3rd", + "resolution": { + "offset": "3", + "relativeTo": "start" + }, + "role": "endpos", + "startIndex": 44, + "type": "builtin.ordinalV2" + }, + { + "endIndex": 198, + "entity": "first", + "resolution": { + "offset": "1", + "relativeTo": "start" + }, + "startIndex": 194, + "type": "builtin.ordinalV2" + }, + { + "endIndex": 284, + "entity": "next one", + "resolution": { + "offset": "1", + "relativeTo": "current" + }, + "startIndex": 277, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 305, + "entity": "previous one", + "resolution": { + "offset": "-1", + "relativeTo": "current" + }, + "startIndex": 294, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 206, + "entity": "10%", + "resolution": { + "value": "10%" + }, + "startIndex": 204, + "type": "builtin.percentage" + }, + { + "endIndex": 216, + "entity": "10.5%", + "resolution": { + "value": "10.5%" + }, + "startIndex": 212, + "type": "builtin.percentage" + }, + { + "endIndex": 233, + "entity": "425-555-1234", + "resolution": { + "score": "0.9", + "value": "425-555-1234" + }, + "startIndex": 222, + "type": "builtin.phonenumber" + }, + { + "endIndex": 247, + "entity": "3 degrees", + "resolution": { + "unit": "Degree", + "value": "3" + }, + "startIndex": 239, + "type": "builtin.temperature" + }, + { + "endIndex": 267, + "entity": "-27.5 degrees c", + "resolution": { + "unit": "C", + "value": "-27.5" + }, + "startIndex": 253, + "type": "builtin.temperature" + } + ], + "intents": [ + { + "intent": "EntityTests", + "score": 0.958614767 + }, + { + "intent": "Roles", + "score": 0.191202149 + }, + { + "intent": "Weather.GetForecast", + "score": 0.0141556319 + }, + { + "intent": "None", + "score": 0.0101076821 + }, + { + "intent": "search", + "score": 0.00475360872 + }, + { + "intent": "Travel", + "score": 0.00232480234 + }, + { + "intent": "Delivery", + "score": 0.000280677923 + }, + { + "intent": "SpecifyName", + "score": 7.367716E-05 + }, + { + "intent": "Help", + "score": 4.74059061E-06 + }, + { + "intent": "Cancel", + "score": 1.54311692E-06 + }, + { + "intent": "Greeting", + "score": 8.076372E-07 + } + ], + "query": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "EntityTests", + "score": 0.958614767 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "begin": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "begin", + "startIndex": 0, + "text": "12 years old", + "type": "builtin.age" + } + ], + "Composite1": [ + { + "length": 306, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "type": "Composite1" + } + ], + "end": [ + { + "length": 10, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "end", + "startIndex": 17, + "text": "3 days old", + "type": "builtin.age" + } + ], + "endpos": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "endpos", + "startIndex": 44, + "text": "3rd", + "type": "builtin.ordinalV2" + } + ], + "max": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "max", + "startIndex": 162, + "text": "$4.25", + "type": "builtin.currency" + } + ], + "ordinalV2": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 194, + "text": "first", + "type": "builtin.ordinalV2" + }, + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 277, + "text": "next one", + "type": "builtin.ordinalV2.relative" + }, + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 294, + "text": "previous one", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "begin": [ + { + "number": 12, + "units": "Year" + } + ], + "Composite1": [ + { + "$instance": { + "datetimeV2": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 6, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 21, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 32, + "text": "monday july 3rd, 2019", + "type": "builtin.datetimeV2.date" + }, + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 58, + "text": "every monday", + "type": "builtin.datetimeV2.set" + }, + { + "length": 22, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 75, + "text": "between 3am and 5:30am", + "type": "builtin.datetimeV2.timerange" + } + ], + "dimension": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4 acres", + "type": "builtin.dimension" + }, + { + "length": 13, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4 pico meters", + "type": "builtin.dimension" + } + ], + "email": [ + { + "length": 18, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 132, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "money": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 155, + "text": "$4", + "type": "builtin.currency" + } + ], + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "2019", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 91, + "text": "5", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "4", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 163, + "text": "4.25", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 177, + "text": "32", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 184, + "text": "210.4", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 222, + "text": "425", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "555", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 230, + "text": "1234", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 282, + "text": "one", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 303, + "text": "one", + "type": "builtin.number" + } + ], + "percentage": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10%", + "type": "builtin.percentage" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5%", + "type": "builtin.percentage" + } + ], + "phonenumber": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 222, + "text": "425-555-1234", + "type": "builtin.phonenumber" + } + ], + "temperature": [ + { + "length": 9, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3 degrees", + "type": "builtin.temperature" + }, + { + "length": 15, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5 degrees c", + "type": "builtin.temperature" + } + ] + }, + "datetimeV2": [ + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "378432000" + } + ], + "timex": "P12Y" + } + ] + }, + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "259200" + } + ], + "timex": "P3D" + } + ] + }, + { + "type": "date", + "values": [ + { + "resolution": [ + { + "value": "2019-07-03" + } + ], + "timex": "2019-07-03" + } + ] + }, + { + "type": "set", + "values": [ + { + "resolution": [ + { + "value": "not resolved" + } + ], + "timex": "XXXX-WXX-1" + } + ] + }, + { + "type": "timerange", + "values": [ + { + "resolution": [ + { + "end": "05:30:00", + "start": "03:00:00" + } + ], + "timex": "(T03,T05:30,PT2H30M)" + } + ] + } + ], + "dimension": [ + { + "number": 4, + "units": "Acre" + }, + { + "number": 4, + "units": "Picometer" + } + ], + "email": [ + "chrimc@hotmail.com" + ], + "money": [ + { + "number": 4, + "units": "Dollar" + } + ], + "number": [ + 12, + 3, + 2019, + 5, + 4, + 4, + 4, + 4.25, + 32, + 210.4, + 10, + 10.5, + 425, + 555, + 1234, + 3, + -27.5, + 1, + 1 + ], + "percentage": [ + 10, + 10.5 + ], + "phonenumber": [ + "425-555-1234" + ], + "temperature": [ + { + "number": 3, + "units": "Degree" + }, + { + "number": -27.5, + "units": "C" + } + ] + } + ], + "end": [ + { + "number": 3, + "units": "Day" + } + ], + "endpos": [ + { + "offset": 3, + "relativeTo": "start" + } + ], + "max": [ + { + "number": 4.25, + "units": "Dollar" + } + ], + "ordinalV2": [ + { + "offset": 1, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "current" + }, + { + "offset": -1, + "relativeTo": "current" + } + ] + }, + "intents": { + "Cancel": { + "score": 1.54311692E-06 + }, + "Delivery": { + "score": 0.000280677923 + }, + "EntityTests": { + "score": 0.958614767 + }, + "Greeting": { + "score": 8.076372E-07 + }, + "Help": { + "score": 4.74059061E-06 + }, + "None": { + "score": 0.0101076821 + }, + "Roles": { + "score": 0.191202149 + }, + "search": { + "score": 0.00475360872 + }, + "SpecifyName": { + "score": 7.367716E-05 + }, + "Travel": { + "score": 0.00232480234 + }, + "Weather.GetForecast": { + "score": 0.0141556319 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite2.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite2.json new file mode 100644 index 000000000..5d79c500f --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite2.json @@ -0,0 +1,435 @@ +{ + "entities": { + "$instance": { + "Composite2": [ + { + "endIndex": 69, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can fly from seattle to dallas via denver", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "endIndex": 48, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "endIndex": 14, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "City": [ + { + "endIndex": 69, + "modelType": "Hierarchical Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 63, + "text": "denver", + "type": "City" + } + ], + "From": [ + { + "endIndex": 48, + "modelType": "Hierarchical Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "seattle", + "type": "City::From" + } + ], + "To": [ + { + "endIndex": 58, + "modelType": "Hierarchical Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 52, + "text": "dallas", + "type": "City::To" + } + ] + }, + "City": [ + "denver" + ], + "From": [ + "seattle" + ], + "To": [ + "dallas" + ] + } + ], + "geographyV2": [ + { + "location": "seattle", + "type": "city" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.000219483933 + }, + "Delivery": { + "score": 0.00125586381 + }, + "EntityTests": { + "score": 0.956510365 + }, + "Greeting": { + "score": 0.00014909108 + }, + "Help": { + "score": 0.0005319686 + }, + "None": { + "score": 0.003814332 + }, + "Roles": { + "score": 0.02785043 + }, + "search": { + "score": 0.00132194813 + }, + "SpecifyName": { + "score": 0.000922683743 + }, + "Travel": { + "score": 0.01013992 + }, + "Weather_GetForecast": { + "score": 0.0228957664 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "http://foo.com is where you can fly from seattle to dallas via denver", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "City", + "value": "denver" + }, + { + "type": "City::From", + "value": "seattle" + }, + { + "type": "City::To", + "value": "dallas" + } + ], + "parentType": "Composite2", + "value": "http : / / foo . com is where you can fly from seattle to dallas via denver" + } + ], + "entities": [ + { + "endIndex": 47, + "entity": "seattle", + "score": 0.997107, + "startIndex": 41, + "type": "City::From" + }, + { + "endIndex": 57, + "entity": "dallas", + "score": 0.998217642, + "startIndex": 52, + "type": "City::To" + }, + { + "endIndex": 68, + "entity": "denver", + "score": 0.991177261, + "startIndex": 63, + "type": "City" + }, + { + "endIndex": 68, + "entity": "http : / / foo . com is where you can fly from seattle to dallas via denver", + "score": 0.9807907, + "startIndex": 0, + "type": "Composite2" + }, + { + "endIndex": 47, + "entity": "seattle", + "startIndex": 41, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 13, + "entity": "http://foo.com", + "resolution": { + "value": "http://foo.com" + }, + "role": "oldURL", + "startIndex": 0, + "type": "builtin.url" + } + ], + "intents": [ + { + "intent": "EntityTests", + "score": 0.956510365 + }, + { + "intent": "Roles", + "score": 0.02785043 + }, + { + "intent": "Weather.GetForecast", + "score": 0.0228957664 + }, + { + "intent": "Travel", + "score": 0.01013992 + }, + { + "intent": "None", + "score": 0.003814332 + }, + { + "intent": "search", + "score": 0.00132194813 + }, + { + "intent": "Delivery", + "score": 0.00125586381 + }, + { + "intent": "SpecifyName", + "score": 0.000922683743 + }, + { + "intent": "Help", + "score": 0.0005319686 + }, + { + "intent": "Cancel", + "score": 0.000219483933 + }, + { + "intent": "Greeting", + "score": 0.00014909108 + } + ], + "query": "http://foo.com is where you can fly from seattle to dallas via denver", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "EntityTests", + "score": 0.956510365 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Composite2": [ + { + "length": 69, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can fly from seattle to dallas via denver", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "length": 14, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "oldURL", + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "City": [ + { + "length": 6, + "modelType": "Hierarchical Entity Extractor", + "modelTypeId": 3, + "recognitionSources": [ + "model" + ], + "startIndex": 63, + "text": "denver", + "type": "City" + } + ], + "City::From": [ + { + "length": 7, + "modelType": "Hierarchical Entity Extractor", + "modelTypeId": 3, + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "seattle", + "type": "City::From" + } + ], + "City::To": [ + { + "length": 6, + "modelType": "Hierarchical Entity Extractor", + "modelTypeId": 3, + "recognitionSources": [ + "model" + ], + "startIndex": 52, + "text": "dallas", + "type": "City::To" + } + ] + }, + "City": [ + "denver" + ], + "City::From": [ + "seattle" + ], + "City::To": [ + "dallas" + ] + } + ], + "geographyV2": [ + { + "type": "city", + "value": "seattle" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.000219483933 + }, + "Delivery": { + "score": 0.00125586381 + }, + "EntityTests": { + "score": 0.956510365 + }, + "Greeting": { + "score": 0.00014909108 + }, + "Help": { + "score": 0.0005319686 + }, + "None": { + "score": 0.003814332 + }, + "Roles": { + "score": 0.02785043 + }, + "search": { + "score": 0.00132194813 + }, + "SpecifyName": { + "score": 0.000922683743 + }, + "Travel": { + "score": 0.01013992 + }, + "Weather.GetForecast": { + "score": 0.0228957664 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "http://foo.com is where you can fly from seattle to dallas via denver" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite3.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite3.json new file mode 100644 index 000000000..863187133 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Composite3.json @@ -0,0 +1,461 @@ +{ + "entities": { + "$instance": { + "Destination": [ + { + "endIndex": 33, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9818366, + "startIndex": 25, + "text": "12346 WA", + "type": "Address" + } + ], + "Source": [ + { + "endIndex": 21, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9345161, + "startIndex": 13, + "text": "12345 VA", + "type": "Address" + } + ] + }, + "Destination": [ + { + "$instance": { + "number": [ + { + "endIndex": 30, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 25, + "text": "12346", + "type": "builtin.number" + } + ], + "State": [ + { + "endIndex": 33, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9893861, + "startIndex": 31, + "text": "WA", + "type": "State" + } + ] + }, + "number": [ + 12346 + ], + "State": [ + "WA" + ] + } + ], + "Source": [ + { + "$instance": { + "number": [ + { + "endIndex": 18, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 13, + "text": "12345", + "type": "builtin.number" + } + ], + "State": [ + { + "endIndex": 21, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.941649556, + "startIndex": 19, + "text": "VA", + "type": "State" + } + ] + }, + "number": [ + 12345 + ], + "State": [ + "VA" + ] + } + ] + }, + "intents": { + "Cancel": { + "score": 1.01764708E-09 + }, + "Delivery": { + "score": 0.00238572317 + }, + "EntityTests": { + "score": 4.757576E-10 + }, + "Greeting": { + "score": 1.0875E-09 + }, + "Help": { + "score": 1.01764708E-09 + }, + "None": { + "score": 1.17844979E-06 + }, + "Roles": { + "score": 0.999911964 + }, + "search": { + "score": 9.494859E-06 + }, + "SpecifyName": { + "score": 3.0666667E-09 + }, + "Travel": { + "score": 3.09763345E-06 + }, + "Weather_GetForecast": { + "score": 1.02792524E-06 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "Deliver from 12345 VA to 12346 WA", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "builtin.number", + "value": "12345" + }, + { + "type": "State", + "value": "va" + } + ], + "parentType": "Address", + "value": "12345 va" + }, + { + "children": [ + { + "type": "builtin.number", + "value": "12346" + }, + { + "type": "State", + "value": "wa" + } + ], + "parentType": "Address", + "value": "12346 wa" + } + ], + "entities": [ + { + "endIndex": 20, + "entity": "va", + "score": 0.9684971, + "startIndex": 19, + "type": "State" + }, + { + "endIndex": 32, + "entity": "wa", + "score": 0.988121331, + "startIndex": 31, + "type": "State" + }, + { + "endIndex": 20, + "entity": "12345 va", + "role": "Source", + "score": 0.9659546, + "startIndex": 13, + "type": "Address" + }, + { + "endIndex": 32, + "entity": "12346 wa", + "role": "Destination", + "score": 0.987832844, + "startIndex": 25, + "type": "Address" + }, + { + "endIndex": 17, + "entity": "12345", + "resolution": { + "subtype": "integer", + "value": "12345" + }, + "startIndex": 13, + "type": "builtin.number" + }, + { + "endIndex": 29, + "entity": "12346", + "resolution": { + "subtype": "integer", + "value": "12346" + }, + "startIndex": 25, + "type": "builtin.number" + } + ], + "intents": [ + { + "intent": "Roles", + "score": 0.99991256 + }, + { + "intent": "Delivery", + "score": 0.00239894539 + }, + { + "intent": "None", + "score": 1.18518381E-06 + }, + { + "intent": "Weather.GetForecast", + "score": 1.03386708E-06 + }, + { + "intent": "search", + "score": 9.45E-09 + }, + { + "intent": "SpecifyName", + "score": 3.08333337E-09 + }, + { + "intent": "Travel", + "score": 3.08333337E-09 + }, + { + "intent": "Greeting", + "score": 1.09375E-09 + }, + { + "intent": "Cancel", + "score": 1.02352937E-09 + }, + { + "intent": "Help", + "score": 1.02352937E-09 + }, + { + "intent": "EntityTests", + "score": 4.617647E-10 + } + ], + "query": "Deliver from 12345 VA to 12346 WA", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Roles", + "score": 0.99991256 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production", + "Version": "GeoPeople" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Destination": [ + { + "length": 8, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "role": "Destination", + "score": 0.9818366, + "startIndex": 25, + "text": "12346 WA", + "type": "Address" + } + ], + "Source": [ + { + "length": 8, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "role": "Source", + "score": 0.9345161, + "startIndex": 13, + "text": "12345 VA", + "type": "Address" + } + ] + }, + "Destination": [ + { + "$instance": { + "number": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 25, + "text": "12346", + "type": "builtin.number" + } + ], + "State": [ + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "score": 0.9893861, + "startIndex": 31, + "text": "WA", + "type": "State" + } + ] + }, + "number": [ + 12346 + ], + "State": [ + "WA" + ] + } + ], + "Source": [ + { + "$instance": { + "number": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 13, + "text": "12345", + "type": "builtin.number" + } + ], + "State": [ + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "score": 0.941649556, + "startIndex": 19, + "text": "VA", + "type": "State" + } + ] + }, + "number": [ + 12345 + ], + "State": [ + "VA" + ] + } + ] + }, + "intents": { + "Cancel": { + "score": 1.01764708E-09 + }, + "Delivery": { + "score": 0.00238572317 + }, + "EntityTests": { + "score": 4.757576E-10 + }, + "Greeting": { + "score": 1.0875E-09 + }, + "Help": { + "score": 1.01764708E-09 + }, + "None": { + "score": 1.17844979E-06 + }, + "Roles": { + "score": 0.999911964 + }, + "search": { + "score": 9.494859E-06 + }, + "SpecifyName": { + "score": 3.0666667E-09 + }, + "Travel": { + "score": 3.09763345E-06 + }, + "Weather.GetForecast": { + "score": 1.02792524E-06 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Roles" + }, + "query": "Deliver from 12345 VA to 12346 WA" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Contoso App.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Contoso App.json new file mode 100644 index 000000000..330e757bd --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Contoso App.json @@ -0,0 +1,2541 @@ +{ + "luis_schema_version": "7.0.0", + "intents": [ + { + "name": "Cancel", + "features": [] + }, + { + "name": "Delivery", + "features": [] + }, + { + "name": "EntityTests", + "features": [] + }, + { + "name": "Greeting", + "features": [] + }, + { + "name": "Help", + "features": [] + }, + { + "name": "None", + "features": [] + }, + { + "name": "Roles", + "features": [] + }, + { + "name": "search", + "features": [] + }, + { + "name": "SpecifyName", + "features": [] + }, + { + "name": "Travel", + "features": [] + }, + { + "name": "Weather.GetForecast", + "features": [] + } + ], + "entities": [ + { + "name": "Name", + "children": [], + "roles": [ + "liker", + "likee" + ], + "features": [] + }, + { + "name": "State", + "children": [], + "roles": [], + "features": [] + }, + { + "name": "Weather.Location", + "children": [], + "roles": [ + "source", + "destination" + ], + "features": [] + } + ], + "hierarchicals": [ + { + "name": "City", + "children": [ + { + "name": "To" + }, + { + "name": "From" + } + ], + "roles": [], + "features": [] + } + ], + "composites": [ + { + "name": "Address", + "children": [ + { + "name": "number" + }, + { + "name": "State" + } + ], + "roles": [ + "Source", + "Destination" + ], + "features": [] + }, + { + "name": "Composite1", + "children": [ + { + "name": "age" + }, + { + "name": "datetimeV2" + }, + { + "name": "dimension" + }, + { + "name": "email" + }, + { + "name": "money" + }, + { + "name": "number" + }, + { + "name": "percentage" + }, + { + "name": "phonenumber" + }, + { + "name": "temperature" + } + ], + "roles": [], + "features": [] + }, + { + "name": "Composite2", + "children": [ + { + "name": "Airline" + }, + { + "name": "City" + }, + { + "name": "url" + }, + { + "name": "City::From" + }, + { + "name": "City::To" + }, + { + "name": "Weather.Location" + } + ], + "roles": [], + "features": [] + } + ], + "closedLists": [ + { + "name": "Airline", + "subLists": [ + { + "canonicalForm": "Delta", + "list": [ + "DL" + ] + }, + { + "canonicalForm": "Alaska", + "list": [] + }, + { + "canonicalForm": "United", + "list": [] + }, + { + "canonicalForm": "Virgin", + "list": [ + "DL" + ] + } + ], + "roles": [ + "Buyer", + "Seller" + ] + } + ], + "prebuiltEntities": [ + { + "name": "age", + "roles": [ + "end", + "begin" + ] + }, + { + "name": "datetimeV2", + "roles": [ + "leave", + "arrive" + ] + }, + { + "name": "dimension", + "roles": [ + "length", + "width" + ] + }, + { + "name": "email", + "roles": [ + "sender", + "receiver" + ] + }, + { + "name": "geographyV2", + "roles": [ + "endloc", + "startloc" + ] + }, + { + "name": "money", + "roles": [ + "max", + "min" + ] + }, + { + "name": "number", + "roles": [] + }, + { + "name": "ordinalV2", + "roles": [ + "endpos", + "startpos" + ] + }, + { + "name": "percentage", + "roles": [ + "maximum", + "minimum" + ] + }, + { + "name": "personName", + "roles": [ + "child", + "parent" + ] + }, + { + "name": "phonenumber", + "roles": [ + "old", + "newPhone" + ] + }, + { + "name": "temperature", + "roles": [ + "a", + "b" + ] + }, + { + "name": "url", + "roles": [ + "oldURL" + ] + } + ], + "utterances": [ + { + "text": "\" i need to know the temperature at bangor , sme \"", + "intent": "Weather.GetForecast", + "entities": [] + }, + { + "text": "\" tell me perth weather , sclimate & temperature at australia \"", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 10, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "$2 and $4.25", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "$4 and $4.25", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "$4 and $99", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "$4.25 and $4", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "$99 and $4.50", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "10 years old and 4 years old", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "12 years old and 3 days old", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "12 years old and 3 days old and monday july 3rd and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite1", + "startPos": 0, + "endPos": 299, + "children": [] + } + ] + }, + { + "text": "12% and 8%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "12% to 8%", + "intent": "Roles", + "entities": [ + { + "entity": "percentage", + "role": "minimum", + "startPos": 0, + "endPos": 2, + "children": [] + }, + { + "entity": "percentage", + "role": "maximum", + "startPos": 7, + "endPos": 8, + "children": [] + } + ] + }, + { + "text": "3 degrees and -27.5 degrees c", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "3 inches long by 2 inches wide", + "intent": "Roles", + "entities": [ + { + "entity": "dimension", + "role": "length", + "startPos": 0, + "endPos": 7, + "children": [] + }, + { + "entity": "dimension", + "role": "width", + "startPos": 17, + "endPos": 24, + "children": [] + } + ] + }, + { + "text": "3 inches long by 2 inches wide and 5% to 10% and are you between 6 years old and 8 years old and can i trade kb457 for kb922 and change 425-777-1212 to 206-666-4123 and did delta buy virgin and did the rain from hawaii get to redmond and http://foo.com changed to http://blah.com and i like between 68 degrees and 72 degrees and john likes mary and leave 3pm and arrive 5pm and pay between $400 and $500 and send chrimc@hotmail.com from emad@gmail.com", + "intent": "Roles", + "entities": [ + { + "entity": "dimension", + "role": "length", + "startPos": 0, + "endPos": 7, + "children": [] + }, + { + "entity": "dimension", + "role": "width", + "startPos": 17, + "endPos": 24, + "children": [] + }, + { + "entity": "percentage", + "role": "minimum", + "startPos": 35, + "endPos": 36, + "children": [] + }, + { + "entity": "percentage", + "role": "maximum", + "startPos": 41, + "endPos": 43, + "children": [] + }, + { + "entity": "age", + "role": "begin", + "startPos": 65, + "endPos": 75, + "children": [] + }, + { + "entity": "age", + "role": "end", + "startPos": 81, + "endPos": 91, + "children": [] + }, + { + "entity": "Part", + "role": "buy", + "startPos": 119, + "endPos": 123, + "children": [] + }, + { + "entity": "Airline", + "role": "Buyer", + "startPos": 173, + "endPos": 177, + "children": [] + }, + { + "entity": "Airline", + "role": "Seller", + "startPos": 183, + "endPos": 188, + "children": [] + }, + { + "entity": "Weather.Location", + "role": "source", + "startPos": 212, + "endPos": 217, + "children": [] + }, + { + "entity": "Weather.Location", + "role": "destination", + "startPos": 226, + "endPos": 232, + "children": [] + }, + { + "entity": "temperature", + "role": "b", + "startPos": 314, + "endPos": 323, + "children": [] + }, + { + "entity": "Name", + "role": "liker", + "startPos": 329, + "endPos": 332, + "children": [] + }, + { + "entity": "Name", + "role": "likee", + "startPos": 340, + "endPos": 343, + "children": [] + }, + { + "entity": "datetimeV2", + "role": "leave", + "startPos": 355, + "endPos": 357, + "children": [] + }, + { + "entity": "datetimeV2", + "role": "arrive", + "startPos": 370, + "endPos": 372, + "children": [] + }, + { + "entity": "money", + "role": "min", + "startPos": 390, + "endPos": 393, + "children": [] + }, + { + "entity": "money", + "role": "max", + "startPos": 399, + "endPos": 402, + "children": [] + }, + { + "entity": "email", + "role": "receiver", + "startPos": 413, + "endPos": 430, + "children": [] + } + ] + }, + { + "text": "4% and 5%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "4% to 12%", + "intent": "Roles", + "entities": [ + { + "entity": "percentage", + "role": "minimum", + "startPos": 0, + "endPos": 1, + "children": [] + }, + { + "entity": "percentage", + "role": "maximum", + "startPos": 6, + "endPos": 8, + "children": [] + } + ] + }, + { + "text": "425-555-1212", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "425-555-1234", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "5% to 10%", + "intent": "Roles", + "entities": [ + { + "entity": "percentage", + "role": "minimum", + "startPos": 0, + "endPos": 1, + "children": [] + }, + { + "entity": "percentage", + "role": "maximum", + "startPos": 6, + "endPos": 8, + "children": [] + } + ] + }, + { + "text": "8% and 12%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "8% and 14%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "8% and 9%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "8% to 12%", + "intent": "Roles", + "entities": [ + { + "entity": "percentage", + "role": "minimum", + "startPos": 0, + "endPos": 1, + "children": [] + }, + { + "entity": "percentage", + "role": "maximum", + "startPos": 6, + "endPos": 8, + "children": [] + } + ] + }, + { + "text": "9 feet long by 4 feet wide", + "intent": "Roles", + "entities": [ + { + "entity": "dimension", + "role": "length", + "startPos": 0, + "endPos": 5, + "children": [] + }, + { + "entity": "dimension", + "role": "width", + "startPos": 15, + "endPos": 20, + "children": [] + } + ] + }, + { + "text": "9.2% and 10.3%", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "abort", + "intent": "Cancel", + "entities": [] + }, + { + "text": "and $10 and $20", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "and $4 and $4.25", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "and 4$", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "and 425-765-5555", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "and can i trade kb457 for kb922", + "intent": "Roles", + "entities": [ + { + "entity": "Part", + "role": "sell", + "startPos": 16, + "endPos": 20, + "children": [] + }, + { + "entity": "Part", + "role": "buy", + "startPos": 26, + "endPos": 30, + "children": [] + } + ] + }, + { + "text": "are you between 13 years old and 16 years old", + "intent": "Roles", + "entities": [ + { + "entity": "age", + "role": "begin", + "startPos": 16, + "endPos": 27, + "children": [] + }, + { + "entity": "age", + "role": "end", + "startPos": 33, + "endPos": 44, + "children": [] + } + ] + }, + { + "text": "are you between 4 years old and 7 years old", + "intent": "Roles", + "entities": [ + { + "entity": "age", + "role": "begin", + "startPos": 16, + "endPos": 26, + "children": [] + }, + { + "entity": "age", + "role": "end", + "startPos": 32, + "endPos": 42, + "children": [] + } + ] + }, + { + "text": "are you between 6 years old and 10 years old", + "intent": "Roles", + "entities": [ + { + "entity": "age", + "role": "begin", + "startPos": 16, + "endPos": 26, + "children": [] + }, + { + "entity": "age", + "role": "end", + "startPos": 32, + "endPos": 43, + "children": [] + } + ] + }, + { + "text": "are you between 6 years old and 8 years old", + "intent": "Roles", + "entities": [ + { + "entity": "age", + "role": "end", + "startPos": 32, + "endPos": 42, + "children": [] + } + ] + }, + { + "text": "assist", + "intent": "Help", + "entities": [] + }, + { + "text": "bart simpson", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "bart simpson helps homer simpson", + "intent": "Roles", + "entities": [ + { + "entity": "personName", + "role": "child", + "startPos": 0, + "endPos": 11, + "children": [] + }, + { + "entity": "personName", + "role": "parent", + "startPos": 19, + "endPos": 31, + "children": [] + } + ] + }, + { + "text": "bart simpson is parent of lisa simpson to move calcutta to london", + "intent": "Roles", + "entities": [ + { + "entity": "personName", + "role": "parent", + "startPos": 0, + "endPos": 11, + "children": [] + }, + { + "entity": "personName", + "role": "child", + "startPos": 26, + "endPos": 37, + "children": [] + }, + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 47, + "endPos": 54, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 59, + "endPos": 64, + "children": [] + } + ] + }, + { + "text": "calcutta", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "can i trade kb457 for kb922", + "intent": "Roles", + "entities": [ + { + "entity": "Part", + "role": "sell", + "startPos": 12, + "endPos": 16, + "children": [] + }, + { + "entity": "Part", + "role": "buy", + "startPos": 22, + "endPos": 26, + "children": [] + } + ] + }, + { + "text": "can i trade kb922 for kb457", + "intent": "Roles", + "entities": [ + { + "entity": "Part", + "role": "sell", + "startPos": 12, + "endPos": 16, + "children": [] + }, + { + "entity": "Part", + "role": "buy", + "startPos": 22, + "endPos": 26, + "children": [] + } + ] + }, + { + "text": "cancel", + "intent": "Delivery", + "entities": [] + }, + { + "text": "change 425-765-1111 to 425-888-4444", + "intent": "Roles", + "entities": [ + { + "entity": "phonenumber", + "role": "old", + "startPos": 7, + "endPos": 18, + "children": [] + }, + { + "entity": "phonenumber", + "role": "newPhone", + "startPos": 23, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "change 425-777-1212 to 206-666-4123", + "intent": "Roles", + "entities": [ + { + "entity": "phonenumber", + "role": "old", + "startPos": 7, + "endPos": 18, + "children": [] + }, + { + "entity": "phonenumber", + "role": "newPhone", + "startPos": 23, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "deliver 12345 va to 12346 wa", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 8, + "endPos": 15, + "children": [] + }, + { + "entity": "State", + "startPos": 14, + "endPos": 15, + "children": [] + }, + { + "entity": "Address", + "startPos": 20, + "endPos": 27, + "children": [] + }, + { + "entity": "State", + "startPos": 26, + "endPos": 27, + "children": [] + } + ] + }, + { + "text": "delivery address is in 45654 ga", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 23, + "endPos": 30, + "children": [] + }, + { + "entity": "State", + "startPos": 29, + "endPos": 30, + "children": [] + } + ] + }, + { + "text": "did delta buy virgin", + "intent": "Roles", + "entities": [] + }, + { + "text": "did delta buy virgin?", + "intent": "Roles", + "entities": [ + { + "entity": "Airline", + "role": "Buyer", + "startPos": 4, + "endPos": 8, + "children": [] + }, + { + "entity": "Airline", + "role": "Seller", + "startPos": 14, + "endPos": 19, + "children": [] + } + ] + }, + { + "text": "did the rain from hawaii get to redmond", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "role": "source", + "startPos": 18, + "endPos": 23, + "children": [] + }, + { + "entity": "Weather.Location", + "role": "destination", + "startPos": 32, + "endPos": 38, + "children": [] + } + ] + }, + { + "text": "did virgin buy delta", + "intent": "Roles", + "entities": [ + { + "entity": "Airline", + "role": "Buyer", + "startPos": 4, + "endPos": 9, + "children": [] + }, + { + "entity": "Airline", + "role": "Seller", + "startPos": 15, + "endPos": 19, + "children": [] + } + ] + }, + { + "text": "disregard", + "intent": "Cancel", + "entities": [] + }, + { + "text": "do not do it", + "intent": "Cancel", + "entities": [] + }, + { + "text": "do not do that", + "intent": "Cancel", + "entities": [] + }, + { + "text": "don't", + "intent": "Cancel", + "entities": [] + }, + { + "text": "don't do it", + "intent": "Cancel", + "entities": [] + }, + { + "text": "don't do that", + "intent": "Cancel", + "entities": [] + }, + { + "text": "email about dogs from chris and also cats", + "intent": "search", + "entities": [] + }, + { + "text": "fly from paris, texas to chicago via stork", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 41, + "children": [] + } + ] + }, + { + "text": "forecast in celcius", + "intent": "Weather.GetForecast", + "entities": [] + }, + { + "text": "get florence temperature in september", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 4, + "endPos": 11, + "children": [] + } + ] + }, + { + "text": "get for me the weather conditions in sonoma county", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 37, + "endPos": 49, + "children": [] + } + ] + }, + { + "text": "get the daily temperature greenwood indiana", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 26, + "endPos": 42, + "children": [] + } + ] + }, + { + "text": "get the forcast for me", + "intent": "Weather.GetForecast", + "entities": [] + }, + { + "text": "get the weather at saint george utah", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 19, + "endPos": 35, + "children": [] + } + ] + }, + { + "text": "go from 3rd to 5th", + "intent": "Roles", + "entities": [ + { + "entity": "ordinalV2", + "role": "startpos", + "startPos": 8, + "endPos": 10, + "children": [] + }, + { + "entity": "ordinalV2", + "role": "endpos", + "startPos": 15, + "endPos": 17, + "children": [] + } + ] + }, + { + "text": "go from first to last", + "intent": "Roles", + "entities": [ + { + "entity": "ordinalV2", + "role": "startpos", + "startPos": 8, + "endPos": 12, + "children": [] + }, + { + "entity": "ordinalV2", + "role": "endpos", + "startPos": 17, + "endPos": 20, + "children": [] + } + ] + }, + { + "text": "go from next to last to last move london to jakarta and homer simpson is the parent of lisa simpson", + "intent": "Roles", + "entities": [ + { + "entity": "ordinalV2", + "role": "startpos", + "startPos": 8, + "endPos": 19, + "children": [] + }, + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 34, + "endPos": 39, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 44, + "endPos": 50, + "children": [] + }, + { + "entity": "personName", + "role": "parent", + "startPos": 56, + "endPos": 68, + "children": [] + }, + { + "entity": "personName", + "role": "child", + "startPos": 87, + "endPos": 98, + "children": [] + } + ] + }, + { + "text": "good afternoon", + "intent": "Greeting", + "entities": [] + }, + { + "text": "good evening", + "intent": "Greeting", + "entities": [] + }, + { + "text": "good morning", + "intent": "Greeting", + "entities": [] + }, + { + "text": "good night", + "intent": "Greeting", + "entities": [] + }, + { + "text": "he is yousef", + "intent": "SpecifyName", + "entities": [ + { + "entity": "Name", + "startPos": 6, + "endPos": 11, + "children": [] + } + ] + }, + { + "text": "hello", + "intent": "Cancel", + "entities": [] + }, + { + "text": "hello bot", + "intent": "Greeting", + "entities": [] + }, + { + "text": "help", + "intent": "Help", + "entities": [] + }, + { + "text": "help me", + "intent": "Help", + "entities": [] + }, + { + "text": "help me please", + "intent": "Help", + "entities": [] + }, + { + "text": "help please", + "intent": "Help", + "entities": [] + }, + { + "text": "hi", + "intent": "Greeting", + "entities": [] + }, + { + "text": "hi bot", + "intent": "Greeting", + "entities": [] + }, + { + "text": "hi emad", + "intent": "Greeting", + "entities": [] + }, + { + "text": "his name is tom", + "intent": "SpecifyName", + "entities": [ + { + "entity": "Name", + "startPos": 12, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "hiya", + "intent": "Greeting", + "entities": [] + }, + { + "text": "homer simpson is parent of bart simpson", + "intent": "Roles", + "entities": [] + }, + { + "text": "homer simpson is parent of bart simpson to move jakarta to calcutta", + "intent": "Roles", + "entities": [ + { + "entity": "personName", + "role": "child", + "startPos": 27, + "endPos": 38, + "children": [] + }, + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 48, + "endPos": 54, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 59, + "endPos": 66, + "children": [] + } + ] + }, + { + "text": "homer simpson is parent of lisa simpson", + "intent": "Roles", + "entities": [ + { + "entity": "personName", + "role": "parent", + "startPos": 0, + "endPos": 12, + "children": [] + }, + { + "entity": "personName", + "role": "child", + "startPos": 27, + "endPos": 38, + "children": [] + } + ] + }, + { + "text": "how are you", + "intent": "Greeting", + "entities": [] + }, + { + "text": "how are you doing today?", + "intent": "Greeting", + "entities": [] + }, + { + "text": "how are you doing?", + "intent": "Greeting", + "entities": [] + }, + { + "text": "how are you today?", + "intent": "Greeting", + "entities": [] + }, + { + "text": "how much rain does chambersburg get a year", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 19, + "endPos": 30, + "children": [] + } + ] + }, + { + "text": "howdy", + "intent": "Greeting", + "entities": [] + }, + { + "text": "how's it goig", + "intent": "Greeting", + "entities": [] + }, + { + "text": "http://blah.com changed to http://foo.com", + "intent": "Roles", + "entities": [ + { + "entity": "url", + "role": "oldURL", + "startPos": 0, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "http://blah.com is where you can fly from dallas to seattle via denver", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 69, + "children": [] + }, + { + "entity": "City::From", + "startPos": 42, + "endPos": 47, + "children": [] + }, + { + "entity": "City::To", + "startPos": 52, + "endPos": 58, + "children": [] + }, + { + "entity": "City", + "startPos": 64, + "endPos": 69, + "children": [] + } + ] + }, + { + "text": "http://foo.com", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "http://foo.com changed to http://blah.com", + "intent": "Roles", + "entities": [ + { + "entity": "url", + "role": "oldURL", + "startPos": 0, + "endPos": 13, + "children": [] + } + ] + }, + { + "text": "http://foo.com is ok", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "http://foo.com is where you can fly from seattle to dallas via denver", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 68, + "children": [] + }, + { + "entity": "City::From", + "startPos": 41, + "endPos": 47, + "children": [] + }, + { + "entity": "City::To", + "startPos": 52, + "endPos": 57, + "children": [] + }, + { + "entity": "City", + "startPos": 63, + "endPos": 68, + "children": [] + } + ] + }, + { + "text": "http://woof.com", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "http://woof.com is where you can fly from seattle to dallas via chicago", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 70, + "children": [] + }, + { + "entity": "City::From", + "startPos": 42, + "endPos": 48, + "children": [] + }, + { + "entity": "City::To", + "startPos": 53, + "endPos": 58, + "children": [] + }, + { + "entity": "City", + "startPos": 64, + "endPos": 70, + "children": [] + } + ] + }, + { + "text": "http://woof.com is where you can fly from seattle to dallas via chicago on delta", + "intent": "EntityTests", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 79, + "children": [] + }, + { + "entity": "City::From", + "startPos": 42, + "endPos": 48, + "children": [] + }, + { + "entity": "City::To", + "startPos": 53, + "endPos": 58, + "children": [] + }, + { + "entity": "City", + "startPos": 64, + "endPos": 70, + "children": [] + } + ] + }, + { + "text": "https://foo.com is where you can get weather for seattle", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Composite2", + "startPos": 0, + "endPos": 55, + "children": [] + }, + { + "entity": "Weather.Location", + "startPos": 49, + "endPos": 55, + "children": [] + } + ] + }, + { + "text": "i am lili", + "intent": "SpecifyName", + "entities": [ + { + "entity": "Name", + "startPos": 5, + "endPos": 8, + "children": [] + } + ] + }, + { + "text": "i am stuck", + "intent": "Help", + "entities": [] + }, + { + "text": "i like between 68 degrees and 72 degrees", + "intent": "Roles", + "entities": [ + { + "entity": "temperature", + "role": "a", + "startPos": 15, + "endPos": 24, + "children": [] + } + ] + }, + { + "text": "i like between 72 degrees and 80 degrees", + "intent": "Roles", + "entities": [ + { + "entity": "temperature", + "role": "a", + "startPos": 15, + "endPos": 24, + "children": [] + }, + { + "entity": "temperature", + "role": "b", + "startPos": 30, + "endPos": 39, + "children": [] + } + ] + }, + { + "text": "i want this in 98052 wa", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 15, + "endPos": 22, + "children": [] + }, + { + "entity": "State", + "startPos": 21, + "endPos": 22, + "children": [] + } + ] + }, + { + "text": "i want to arrive at newyork", + "intent": "Travel", + "entities": [ + { + "entity": "City::To", + "startPos": 20, + "endPos": 26, + "children": [] + } + ] + }, + { + "text": "i want to fly out of seattle", + "intent": "Travel", + "entities": [ + { + "entity": "City::From", + "startPos": 21, + "endPos": 27, + "children": [] + } + ] + }, + { + "text": "i want to know the temperature at death valley", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 34, + "endPos": 45, + "children": [] + } + ] + }, + { + "text": "i want to travel", + "intent": "Travel", + "entities": [] + }, + { + "text": "i want to travel from seattle to dallas", + "intent": "Travel", + "entities": [ + { + "entity": "City::From", + "startPos": 22, + "endPos": 28, + "children": [] + }, + { + "entity": "City::To", + "startPos": 33, + "endPos": 38, + "children": [] + } + ] + }, + { + "text": "i would like to cancel", + "intent": "Cancel", + "entities": [] + }, + { + "text": "i'll be leaving from cairo to paris", + "intent": "Travel", + "entities": [ + { + "entity": "City::From", + "startPos": 21, + "endPos": 25, + "children": [] + }, + { + "entity": "City::To", + "startPos": 30, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "i'm stuck", + "intent": "Help", + "entities": [] + }, + { + "text": "joeseph@hotmail.com", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "john likes mary", + "intent": "Roles", + "entities": [ + { + "entity": "Name", + "role": "liker", + "startPos": 0, + "endPos": 3, + "children": [] + }, + { + "entity": "Name", + "role": "likee", + "startPos": 11, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "kb409 is cool", + "intent": "EntityTests", + "entities": [] + }, + { + "text": "leave 3pm and arrive 5pm", + "intent": "Roles", + "entities": [ + { + "entity": "datetimeV2", + "role": "leave", + "startPos": 6, + "endPos": 8, + "children": [] + }, + { + "entity": "datetimeV2", + "role": "arrive", + "startPos": 21, + "endPos": 23, + "children": [] + } + ] + }, + { + "text": "mayday", + "intent": "Help", + "entities": [] + }, + { + "text": "move calcutta to mumbai", + "intent": "Roles", + "entities": [ + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 5, + "endPos": 12, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 17, + "endPos": 22, + "children": [] + } + ] + }, + { + "text": "move jakarta to london", + "intent": "Roles", + "entities": [ + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 5, + "endPos": 11, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 16, + "endPos": 21, + "children": [] + } + ] + }, + { + "text": "move london to calcutta", + "intent": "Roles", + "entities": [ + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 5, + "endPos": 10, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 15, + "endPos": 22, + "children": [] + } + ] + }, + { + "text": "move london to jakarta", + "intent": "Roles", + "entities": [ + { + "entity": "geographyV2", + "role": "startloc", + "startPos": 5, + "endPos": 10, + "children": [] + }, + { + "entity": "geographyV2", + "role": "endloc", + "startPos": 15, + "endPos": 21, + "children": [] + } + ] + }, + { + "text": "my name is emad", + "intent": "SpecifyName", + "entities": [ + { + "entity": "Name", + "startPos": 11, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "my water bottle is green.", + "intent": "None", + "entities": [] + }, + { + "text": "never mind", + "intent": "Cancel", + "entities": [] + }, + { + "text": "no thanks", + "intent": "Cancel", + "entities": [] + }, + { + "text": "nope", + "intent": "Cancel", + "entities": [] + }, + { + "text": "pay between $4 and $4.25", + "intent": "Roles", + "entities": [ + { + "entity": "money", + "role": "min", + "startPos": 12, + "endPos": 13, + "children": [] + }, + { + "entity": "money", + "role": "max", + "startPos": 19, + "endPos": 23, + "children": [] + } + ] + }, + { + "text": "pay between $4 and $40", + "intent": "Roles", + "entities": [ + { + "entity": "money", + "role": "min", + "startPos": 12, + "endPos": 13, + "children": [] + }, + { + "entity": "money", + "role": "max", + "startPos": 19, + "endPos": 21, + "children": [] + } + ] + }, + { + "text": "pay between $400 and $500", + "intent": "Roles", + "entities": [ + { + "entity": "money", + "role": "min", + "startPos": 12, + "endPos": 15, + "children": [] + }, + { + "entity": "money", + "role": "max", + "startPos": 21, + "endPos": 24, + "children": [] + } + ] + }, + { + "text": "please cancel", + "intent": "Cancel", + "entities": [] + }, + { + "text": "please deliver february 2nd 2001", + "intent": "Delivery", + "entities": [] + }, + { + "text": "please deliver to 98033 wa", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 18, + "endPos": 25, + "children": [] + }, + { + "entity": "State", + "startPos": 24, + "endPos": 25, + "children": [] + } + ] + }, + { + "text": "please delivery it to 98033 wa", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 22, + "endPos": 29, + "children": [] + }, + { + "entity": "State", + "startPos": 28, + "endPos": 29, + "children": [] + } + ] + }, + { + "text": "please disregard", + "intent": "Cancel", + "entities": [] + }, + { + "text": "please help me", + "intent": "Help", + "entities": [] + }, + { + "text": "please stop", + "intent": "Cancel", + "entities": [] + }, + { + "text": "provide me by toronto weather please", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 14, + "endPos": 20, + "children": [] + } + ] + }, + { + "text": "send bob@foo.com from chris@ark.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 15, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 22, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "send bob@hob.com from main@gmail.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 15, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 22, + "endPos": 35, + "children": [] + } + ] + }, + { + "text": "send cancel@foo.com from help@h.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 18, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 25, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "send chrimc@hotmail.com from emad@gmail.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 22, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 29, + "endPos": 42, + "children": [] + } + ] + }, + { + "text": "send chris@ark.com from bob@foo.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 17, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 24, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "send ham@ham.com from chr@live.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 15, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 22, + "endPos": 33, + "children": [] + } + ] + }, + { + "text": "send help@h.com from cancel@foo.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 14, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 21, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "send tom@foo.com from john@hotmail.com", + "intent": "Roles", + "entities": [ + { + "entity": "email", + "role": "receiver", + "startPos": 5, + "endPos": 15, + "children": [] + }, + { + "entity": "email", + "role": "sender", + "startPos": 22, + "endPos": 37, + "children": [] + } + ] + }, + { + "text": "show average rainfall for boise", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 26, + "endPos": 30, + "children": [] + } + ] + }, + { + "text": "show me the forecast at alabama", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 24, + "endPos": 30, + "children": [] + } + ] + }, + { + "text": "soliciting today ' s weather", + "intent": "Weather.GetForecast", + "entities": [] + }, + { + "text": "sos", + "intent": "Help", + "entities": [] + }, + { + "text": "start with the first one", + "intent": "Roles", + "entities": [] + }, + { + "text": "stop", + "intent": "Cancel", + "entities": [] + }, + { + "text": "temperature of delhi in celsius please", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 15, + "endPos": 19, + "children": [] + } + ] + }, + { + "text": "the address is 66666 fl", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 15, + "endPos": 22, + "children": [] + }, + { + "entity": "State", + "startPos": 21, + "endPos": 22, + "children": [] + } + ] + }, + { + "text": "there is a large deep dish pizza in your future.", + "intent": "None", + "entities": [] + }, + { + "text": "this is chris", + "intent": "SpecifyName", + "entities": [ + { + "entity": "Name", + "startPos": 8, + "endPos": 12, + "children": [] + } + ] + }, + { + "text": "this is requested in 55555 ny", + "intent": "Delivery", + "entities": [ + { + "entity": "Address", + "startPos": 21, + "endPos": 28, + "children": [] + }, + { + "entity": "State", + "startPos": 27, + "endPos": 28, + "children": [] + } + ] + }, + { + "text": "tom likes susan", + "intent": "Roles", + "entities": [ + { + "entity": "Name", + "role": "liker", + "startPos": 0, + "endPos": 2, + "children": [] + }, + { + "entity": "Name", + "role": "likee", + "startPos": 10, + "endPos": 14, + "children": [] + } + ] + }, + { + "text": "was last year about this time as wet as it is now in the south ?", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 57, + "endPos": 61, + "children": [] + } + ] + }, + { + "text": "what ' s the weather going to be like in hawaii ?", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 41, + "endPos": 46, + "children": [] + } + ] + }, + { + "text": "what ' s the weather like in minneapolis", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 29, + "endPos": 39, + "children": [] + } + ] + }, + { + "text": "what can i say", + "intent": "Help", + "entities": [] + }, + { + "text": "what can you do", + "intent": "Help", + "entities": [] + }, + { + "text": "what can you help me with", + "intent": "Help", + "entities": [] + }, + { + "text": "what do i do now?", + "intent": "Help", + "entities": [] + }, + { + "text": "what do i do?", + "intent": "Help", + "entities": [] + }, + { + "text": "what is the rain volume in sonoma county ?", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 27, + "endPos": 39, + "children": [] + } + ] + }, + { + "text": "what is the weather in redmond ?", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 23, + "endPos": 29, + "children": [] + } + ] + }, + { + "text": "what is the weather today at 10 day durham ?", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 36, + "endPos": 41, + "children": [] + } + ] + }, + { + "text": "what to wear in march in california", + "intent": "None", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 25, + "endPos": 34, + "children": [] + } + ] + }, + { + "text": "what will the weather be tomorrow in accord new york ?", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 37, + "endPos": 51, + "children": [] + } + ] + }, + { + "text": "why doesn't this work ?", + "intent": "Help", + "entities": [] + }, + { + "text": "will it be raining in ranchi", + "intent": "Weather.GetForecast", + "entities": [ + { + "entity": "Weather.Location", + "startPos": 22, + "endPos": 27, + "children": [] + } + ] + }, + { + "text": "will it rain this weekend", + "intent": "Weather.GetForecast", + "entities": [] + }, + { + "text": "will it snow today", + "intent": "Weather.GetForecast", + "entities": [] + } + ], + "versionId": "0.2", + "name": "Contoso App V3", + "desc": "Default Intents for Azure Bot Service V2", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "patternAnyEntities": [ + { + "name": "person", + "roles": [ + "from", + "to" + ], + "explicitList": [] + }, + { + "name": "subject", + "roles": [ + "extra" + ], + "explicitList": [] + } + ], + "regex_entities": [ + { + "name": "Part", + "regexPattern": "kb[0-9]+", + "roles": [ + "sell", + "buy" + ] + } + ], + "phraselists": [], + "regex_features": [], + "patterns": [ + { + "pattern": "deliver from {Address:Source} to {Address:Destination}", + "intent": "Roles" + }, + { + "pattern": "email from {person:from} to {person:to}", + "intent": "search" + }, + { + "pattern": "email about {subject} [from {person}] [and also {subject:extra}]", + "intent": "search" + } + ], + "settings": [ + { + "name": "UseAllTrainingData", + "value": "true" + } + ] +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DateTimeReference.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DateTimeReference.json new file mode 100644 index 000000000..d7c01e6bc --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DateTimeReference.json @@ -0,0 +1,75 @@ +{ + "entities": { + "Airline": [ + [ + "Delta" + ] + ], + "datetime": [ + { + "timex": [ + "2019-05-06" + ], + "type": "date" + } + ] + }, + "intents": { + "EntityTests": { + "score": 0.190871954 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "fly on delta tomorrow", + "v3": { + "options": { + "DateTimeReference": "05/05/2019 12:00:00", + "IncludeAllIntents": false, + "IncludeAPIResults": true, + "IncludeInstanceData": false, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "Airline": [ + [ + "Delta" + ] + ], + "datetimeV2": [ + { + "type": "date", + "values": [ + { + "resolution": [ + { + "value": "2019-05-06" + } + ], + "timex": "2019-05-06" + } + ] + } + ] + }, + "intents": { + "EntityTests": { + "score": 0.190871954 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "fly on delta tomorrow" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DynamicListsAndList.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DynamicListsAndList.json new file mode 100644 index 000000000..e973384fa --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/DynamicListsAndList.json @@ -0,0 +1,221 @@ +{ + "entities": { + "$instance": { + "Airline": [ + { + "endIndex": 21, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 4, + "text": "zimbabwe airlines", + "type": "Airline" + }, + { + "endIndex": 33, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 25, + "text": "deltaair", + "type": "Airline" + } + ], + "endloc": [ + { + "endIndex": 12, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 4, + "text": "zimbabwe", + "type": "builtin.geographyV2.countryRegion" + } + ] + }, + "Airline": [ + [ + "ZimAir" + ], + [ + "DeltaAir" + ] + ], + "endloc": [ + { + "location": "zimbabwe", + "type": "countryRegion" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.006457624 + }, + "Delivery": { + "score": 0.00599454 + }, + "EntityTests": { + "score": 0.084535785 + }, + "Greeting": { + "score": 0.005392684 + }, + "Help": { + "score": 0.005619145 + }, + "None": { + "score": 0.03300121 + }, + "Roles": { + "score": 0.0213384479 + }, + "search": { + "score": 0.000823451439 + }, + "SpecifyName": { + "score": 0.0037871073 + }, + "Travel": { + "score": 0.008902215 + }, + "Weather_GetForecast": { + "score": 0.006632081 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "fly zimbabwe airlines or deltaair", + "v3": { + "options": { + "DynamicLists": [ + { + "listEntityName": "Airline", + "requestLists": [ + { + "canonicalForm": "ZimAir", + "synonyms": [ + "zimbabwe airlines" + ] + }, + { + "canonicalForm": "DeltaAir" + } + ] + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Airline": [ + { + "length": 17, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "model" + ], + "startIndex": 4, + "text": "zimbabwe airlines", + "type": "Airline" + }, + { + "length": 8, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "model" + ], + "startIndex": 25, + "text": "deltaair", + "type": "Airline" + } + ], + "endloc": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "endloc", + "startIndex": 4, + "text": "zimbabwe", + "type": "builtin.geographyV2.countryRegion" + } + ] + }, + "Airline": [ + [ + "ZimAir" + ], + [ + "DeltaAir" + ] + ], + "endloc": [ + { + "type": "countryRegion", + "value": "zimbabwe" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.006457624 + }, + "Delivery": { + "score": 0.00599454 + }, + "EntityTests": { + "score": 0.084535785 + }, + "Greeting": { + "score": 0.005392684 + }, + "Help": { + "score": 0.005619145 + }, + "None": { + "score": 0.03300121 + }, + "Roles": { + "score": 0.0213384479 + }, + "search": { + "score": 0.000823451439 + }, + "SpecifyName": { + "score": 0.0037871073 + }, + "Travel": { + "score": 0.008902215 + }, + "Weather.GetForecast": { + "score": 0.006632081 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "fly zimbabwe airlines or deltaair" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndBuiltin.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndBuiltin.json new file mode 100644 index 000000000..6ab4b476a --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndBuiltin.json @@ -0,0 +1,167 @@ +{ + "entities": { + "$instance": { + "number": [ + { + "endIndex": 7, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 4, + "text": "hul", + "type": "builtin.number" + }, + { + "endIndex": 13, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 12, + "text": "2", + "type": "builtin.number" + } + ] + }, + "number": [ + 8, + 2 + ] + }, + "intents": { + "Cancel": { + "score": 0.006839604 + }, + "Delivery": { + "score": 0.005634159 + }, + "EntityTests": { + "score": 0.1439953 + }, + "Greeting": { + "score": 0.004496467 + }, + "Help": { + "score": 0.005810102 + }, + "None": { + "score": 0.0134399384 + }, + "Roles": { + "score": 0.0453764722 + }, + "search": { + "score": 0.00117916975 + }, + "SpecifyName": { + "score": 0.00377203338 + }, + "Travel": { + "score": 0.00252068671 + }, + "Weather_GetForecast": { + "score": 0.009093848 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "buy hul and 2 items", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 3, + "entityName": "number", + "resolution": 8, + "startIndex": 4 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "number": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 4, + "text": "hul", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 12, + "text": "2", + "type": "builtin.number" + } + ] + }, + "number": [ + 8, + 2 + ] + }, + "intents": { + "Cancel": { + "score": 0.006839604 + }, + "Delivery": { + "score": 0.005634159 + }, + "EntityTests": { + "score": 0.1439953 + }, + "Greeting": { + "score": 0.004496467 + }, + "Help": { + "score": 0.005810102 + }, + "None": { + "score": 0.0134399384 + }, + "Roles": { + "score": 0.0453764722 + }, + "search": { + "score": 0.00117916975 + }, + "SpecifyName": { + "score": 0.00377203338 + }, + "Travel": { + "score": 0.00252068671 + }, + "Weather.GetForecast": { + "score": 0.009093848 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "buy hul and 2 items" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndComposite.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndComposite.json new file mode 100644 index 000000000..0b8a7b5c8 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndComposite.json @@ -0,0 +1,196 @@ +{ + "entities": { + "$instance": { + "Address": [ + { + "endIndex": 33, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 17, + "text": "repent harelquin", + "type": "Address" + } + ], + "number": [ + { + "endIndex": 10, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "35", + "type": "builtin.number" + } + ] + }, + "Address": [ + { + "number": [ + 3 + ], + "State": [ + "France" + ] + } + ], + "number": [ + 35 + ] + }, + "intents": { + "Cancel": { + "score": 0.00324298278 + }, + "Delivery": { + "score": 0.480763137 + }, + "EntityTests": { + "score": 0.004284346 + }, + "Greeting": { + "score": 0.00282274443 + }, + "Help": { + "score": 0.00290596974 + }, + "None": { + "score": 0.0205373727 + }, + "Roles": { + "score": 0.06780239 + }, + "search": { + "score": 0.000895992853 + }, + "SpecifyName": { + "score": 0.002745299 + }, + "Travel": { + "score": 0.00337635027 + }, + "Weather_GetForecast": { + "score": 0.00949979 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "deliver 35 WA to repent harelquin", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 16, + "entityName": "Address", + "resolution": { + "number": [ + 3 + ], + "State": [ + "France" + ] + }, + "startIndex": 17 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Address": [ + { + "length": 16, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 17, + "text": "repent harelquin", + "type": "Address" + } + ], + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "35", + "type": "builtin.number" + } + ] + }, + "Address": [ + { + "number": [ + 3 + ], + "State": [ + "France" + ] + } + ], + "number": [ + 35 + ] + }, + "intents": { + "Cancel": { + "score": 0.00324298278 + }, + "Delivery": { + "score": 0.480763137 + }, + "EntityTests": { + "score": 0.004284346 + }, + "Greeting": { + "score": 0.00282274443 + }, + "Help": { + "score": 0.00290596974 + }, + "None": { + "score": 0.0205373727 + }, + "Roles": { + "score": 0.06780239 + }, + "search": { + "score": 0.000895992853 + }, + "SpecifyName": { + "score": 0.002745299 + }, + "Travel": { + "score": 0.00337635027 + }, + "Weather.GetForecast": { + "score": 0.00949979 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Delivery" + }, + "query": "deliver 35 WA to repent harelquin" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndList.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndList.json new file mode 100644 index 000000000..c4e88f12b --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndList.json @@ -0,0 +1,177 @@ +{ + "entities": { + "$instance": { + "Airline": [ + { + "endIndex": 23, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 7, + "text": "humberg airlines", + "type": "Airline" + }, + { + "endIndex": 32, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 27, + "text": "Delta", + "type": "Airline" + } + ] + }, + "Airline": [ + [ + "HumAir" + ], + [ + "Delta" + ] + ] + }, + "intents": { + "Cancel": { + "score": 0.00322572654 + }, + "Delivery": { + "score": 0.00437863264 + }, + "EntityTests": { + "score": 0.07901226 + }, + "Greeting": { + "score": 0.00273721316 + }, + "Help": { + "score": 0.00294000562 + }, + "None": { + "score": 0.0279089436 + }, + "Roles": { + "score": 0.120735578 + }, + "search": { + "score": 0.000854549464 + }, + "SpecifyName": { + "score": 0.002717544 + }, + "Travel": { + "score": 0.00884681847 + }, + "Weather_GetForecast": { + "score": 0.00485026464 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "fly on humberg airlines or Delta", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 16, + "entityName": "Airline", + "resolution": [ + "HumAir" + ], + "startIndex": 7 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Airline": [ + { + "length": 16, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 7, + "text": "humberg airlines", + "type": "Airline" + }, + { + "length": 5, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "model" + ], + "startIndex": 27, + "text": "Delta", + "type": "Airline" + } + ] + }, + "Airline": [ + [ + "HumAir" + ], + [ + "Delta" + ] + ] + }, + "intents": { + "Cancel": { + "score": 0.00322572654 + }, + "Delivery": { + "score": 0.00437863264 + }, + "EntityTests": { + "score": 0.07901226 + }, + "Greeting": { + "score": 0.00273721316 + }, + "Help": { + "score": 0.00294000562 + }, + "None": { + "score": 0.0279089436 + }, + "Roles": { + "score": 0.120735578 + }, + "search": { + "score": 0.000854549464 + }, + "SpecifyName": { + "score": 0.002717544 + }, + "Travel": { + "score": 0.00884681847 + }, + "Weather.GetForecast": { + "score": 0.00485026464 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Roles" + }, + "query": "fly on humberg airlines or Delta" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndRegex.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndRegex.json new file mode 100644 index 000000000..33b9fef97 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndRegex.json @@ -0,0 +1,166 @@ +{ + "entities": { + "$instance": { + "Part": [ + { + "endIndex": 5, + "modelType": "Regex Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 0, + "text": "42ski", + "type": "Part" + }, + { + "endIndex": 26, + "modelType": "Regex Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 21, + "text": "kb423", + "type": "Part" + } + ] + }, + "Part": [ + "42ski", + "kb423" + ] + }, + "intents": { + "Cancel": { + "score": 0.0128021352 + }, + "Delivery": { + "score": 0.004558195 + }, + "EntityTests": { + "score": 0.009367461 + }, + "Greeting": { + "score": 0.0025622393 + }, + "Help": { + "score": 0.0021368505 + }, + "None": { + "score": 0.2778768 + }, + "Roles": { + "score": 0.0273504611 + }, + "search": { + "score": 0.000832980848 + }, + "SpecifyName": { + "score": 0.00770643726 + }, + "Travel": { + "score": 0.00204822514 + }, + "Weather_GetForecast": { + "score": 0.009585343 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "42ski is a part like kb423", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 5, + "entityName": "Part", + "startIndex": 0 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Part": [ + { + "length": 5, + "modelType": "Regex Entity Extractor", + "modelTypeId": 8, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 0, + "text": "42ski", + "type": "Part" + }, + { + "length": 5, + "modelType": "Regex Entity Extractor", + "modelTypeId": 8, + "recognitionSources": [ + "model" + ], + "startIndex": 21, + "text": "kb423", + "type": "Part" + } + ] + }, + "Part": [ + "42ski", + "kb423" + ] + }, + "intents": { + "Cancel": { + "score": 0.0128021352 + }, + "Delivery": { + "score": 0.004558195 + }, + "EntityTests": { + "score": 0.009367461 + }, + "Greeting": { + "score": 0.0025622393 + }, + "Help": { + "score": 0.0021368505 + }, + "None": { + "score": 0.2778768 + }, + "Roles": { + "score": 0.0273504611 + }, + "search": { + "score": 0.000832980848 + }, + "SpecifyName": { + "score": 0.00770643726 + }, + "Travel": { + "score": 0.00204822514 + }, + "Weather.GetForecast": { + "score": 0.009585343 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "None" + }, + "query": "42ski is a part like kb423" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimple.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimple.json new file mode 100644 index 000000000..7fc6b25c0 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimple.json @@ -0,0 +1,232 @@ +{ + "entities": { + "$instance": { + "number": [ + { + "endIndex": 10, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "37", + "type": "builtin.number" + }, + { + "endIndex": 19, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "82", + "type": "builtin.number" + } + ], + "State": [ + { + "endIndex": 13, + "modelType": "Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 11, + "text": "wa", + "type": "State" + }, + { + "endIndex": 22, + "modelType": "Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 20, + "text": "co", + "type": "State" + } + ] + }, + "number": [ + 37, + 82 + ], + "State": [ + "wa", + { + "state": "Colorado" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.004019331 + }, + "Delivery": { + "score": 0.509761333 + }, + "EntityTests": { + "score": 0.004867602 + }, + "Greeting": { + "score": 0.002855288 + }, + "Help": { + "score": 0.00350000733 + }, + "None": { + "score": 0.0121234478 + }, + "Roles": { + "score": 0.08292572 + }, + "search": { + "score": 0.0009203224 + }, + "SpecifyName": { + "score": 0.00308484654 + }, + "Travel": { + "score": 0.00362442387 + }, + "Weather_GetForecast": { + "score": 0.01115346 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "deliver 37 wa to 82 co", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 2, + "entityName": "State", + "startIndex": 11 + }, + { + "entityLength": 2, + "entityName": "State", + "resolution": { + "state": "Colorado" + }, + "startIndex": 20 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "37", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "82", + "type": "builtin.number" + } + ], + "State": [ + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 11, + "text": "wa", + "type": "State" + }, + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 20, + "text": "co", + "type": "State" + } + ] + }, + "number": [ + 37, + 82 + ], + "State": [ + "wa", + { + "state": "Colorado" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.004019331 + }, + "Delivery": { + "score": 0.509761333 + }, + "EntityTests": { + "score": 0.004867602 + }, + "Greeting": { + "score": 0.002855288 + }, + "Help": { + "score": 0.00350000733 + }, + "None": { + "score": 0.0121234478 + }, + "Roles": { + "score": 0.08292572 + }, + "search": { + "score": 0.0009203224 + }, + "SpecifyName": { + "score": 0.00308484654 + }, + "Travel": { + "score": 0.00362442387 + }, + "Weather.GetForecast": { + "score": 0.01115346 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Delivery" + }, + "query": "deliver 37 wa to 82 co" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimpleOverride.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimpleOverride.json new file mode 100644 index 000000000..5f2080c8d --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalEntitiesAndSimpleOverride.json @@ -0,0 +1,239 @@ +{ + "entities": { + "$instance": { + "number": [ + { + "endIndex": 10, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "37", + "type": "builtin.number" + }, + { + "endIndex": 19, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "82", + "type": "builtin.number" + } + ], + "State": [ + { + "endIndex": 13, + "modelType": "Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 11, + "text": "wa", + "type": "State" + }, + { + "endIndex": 22, + "modelType": "Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 20, + "text": "co", + "type": "State" + } + ] + }, + "number": [ + 37, + 82 + ], + "State": [ + { + "state": "Washington" + }, + { + "state": "Colorado" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.004019331 + }, + "Delivery": { + "score": 0.509761333 + }, + "EntityTests": { + "score": 0.004867602 + }, + "Greeting": { + "score": 0.002855288 + }, + "Help": { + "score": 0.00350000733 + }, + "None": { + "score": 0.0121234478 + }, + "Roles": { + "score": 0.08292572 + }, + "search": { + "score": 0.0009203224 + }, + "SpecifyName": { + "score": 0.00308484654 + }, + "Travel": { + "score": 0.00362442387 + }, + "Weather_GetForecast": { + "score": 0.01115346 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "deliver 37 wa to 82 co", + "v3": { + "options": { + "ExternalEntities": [ + { + "entityLength": 2, + "entityName": "State", + "resolution": { + "state": "Washington" + }, + "startIndex": 11 + }, + { + "entityLength": 2, + "entityName": "State", + "resolution": { + "state": "Colorado" + }, + "startIndex": 20 + } + ], + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "37", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "82", + "type": "builtin.number" + } + ], + "State": [ + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 11, + "text": "wa", + "type": "State" + }, + { + "length": 2, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 20, + "text": "co", + "type": "State" + } + ] + }, + "number": [ + 37, + 82 + ], + "State": [ + { + "state": "Washington" + }, + { + "state": "Colorado" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.004019331 + }, + "Delivery": { + "score": 0.509761333 + }, + "EntityTests": { + "score": 0.004867602 + }, + "Greeting": { + "score": 0.002855288 + }, + "Help": { + "score": 0.00350000733 + }, + "None": { + "score": 0.0121234478 + }, + "Roles": { + "score": 0.08292572 + }, + "search": { + "score": 0.0009203224 + }, + "SpecifyName": { + "score": 0.00308484654 + }, + "Travel": { + "score": 0.00362442387 + }, + "Weather.GetForecast": { + "score": 0.01115346 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Delivery" + }, + "query": "deliver 37 wa to 82 co" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalRecognizer.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalRecognizer.json new file mode 100644 index 000000000..05cca17b7 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/ExternalRecognizer.json @@ -0,0 +1,201 @@ +{ + "entities": { + "$instance": { + "Address": [ + { + "endIndex": 33, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 17, + "text": "repent harelquin", + "type": "Address" + } + ], + "number": [ + { + "endIndex": 10, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "35", + "type": "builtin.number" + } + ] + }, + "Address": [ + { + "number": [ + 3 + ], + "State": [ + "France" + ] + } + ], + "number": [ + 35 + ] + }, + "intents": { + "Cancel": { + "score": 0.00324298278 + }, + "Delivery": { + "score": 0.480763137 + }, + "EntityTests": { + "score": 0.004284346 + }, + "Greeting": { + "score": 0.00282274443 + }, + "Help": { + "score": 0.00290596974 + }, + "None": { + "score": 0.0205373727 + }, + "Roles": { + "score": 0.06780239 + }, + "search": { + "score": 0.000895992853 + }, + "SpecifyName": { + "score": 0.002745299 + }, + "Travel": { + "score": 0.00337635027 + }, + "Weather_GetForecast": { + "score": 0.00949979 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "deliver 35 WA to repent harelquin", + "v3": { + "options": { + "ExternalRecognizerResult": { + "$instance":{ + "Address":[ + { + "endIndex":33, + "modelType":"Composite Entity Extractor", + "resolution": { + "number": [ + 3 + ], + "State": [ + "France" + ]}, + "startIndex":17, + "text":"repent harelquin", + "type":"Address" + } + ] + } + }, + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Address": [ + { + "length": 16, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "externalEntities" + ], + "startIndex": 17, + "text": "repent harelquin", + "type": "Address" + } + ], + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "35", + "type": "builtin.number" + } + ] + }, + "Address": [ + { + "number": [ + 3 + ], + "State": [ + "France" + ] + } + ], + "number": [ + 35 + ] + }, + "intents": { + "Cancel": { + "score": 0.00324298278 + }, + "Delivery": { + "score": 0.480763137 + }, + "EntityTests": { + "score": 0.004284346 + }, + "Greeting": { + "score": 0.00282274443 + }, + "Help": { + "score": 0.00290596974 + }, + "None": { + "score": 0.0205373727 + }, + "Roles": { + "score": 0.06780239 + }, + "search": { + "score": 0.000895992853 + }, + "SpecifyName": { + "score": 0.002745299 + }, + "Travel": { + "score": 0.00337635027 + }, + "Weather.GetForecast": { + "score": 0.00949979 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Delivery" + }, + "query": "deliver 35 WA to repent harelquin" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/GeoPeopleOrdinal.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/GeoPeopleOrdinal.json new file mode 100644 index 000000000..758c2efb5 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/GeoPeopleOrdinal.json @@ -0,0 +1,436 @@ +{ + "entities": { + "$instance": { + "child": [ + { + "endIndex": 99, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 87, + "text": "lisa simpson", + "type": "builtin.personName" + } + ], + "endloc": [ + { + "endIndex": 51, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 44, + "text": "jakarta", + "type": "builtin.geographyV2.city" + } + ], + "ordinalV2": [ + { + "endIndex": 28, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 24, + "text": "last", + "type": "builtin.ordinalV2.relative" + } + ], + "parent": [ + { + "endIndex": 69, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 56, + "text": "homer simpson", + "type": "builtin.personName" + } + ], + "startloc": [ + { + "endIndex": 40, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 34, + "text": "london", + "type": "builtin.geographyV2.city" + } + ], + "startpos": [ + { + "endIndex": 20, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 8, + "text": "next to last", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "child": [ + "lisa simpson" + ], + "endloc": [ + { + "location": "jakarta", + "type": "city" + } + ], + "ordinalV2": [ + { + "offset": 0, + "relativeTo": "end" + } + ], + "parent": [ + "homer simpson" + ], + "startloc": [ + { + "location": "london", + "type": "city" + } + ], + "startpos": [ + { + "offset": -1, + "relativeTo": "end" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.000106304564 + }, + "Delivery": { + "score": 0.00121616619 + }, + "EntityTests": { + "score": 0.00107762846 + }, + "Greeting": { + "score": 5.23392373E-05 + }, + "Help": { + "score": 0.000134394242 + }, + "None": { + "score": 0.0108486973 + }, + "Roles": { + "score": 0.9991838 + }, + "search": { + "score": 0.002142746 + }, + "SpecifyName": { + "score": 0.0006965211 + }, + "Travel": { + "score": 0.0046763327 + }, + "Weather_GetForecast": { + "score": 0.0105504664 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "go from next to last to last move london to jakarta and homer simpson is the parent of lisa simpson", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 39, + "entity": "london", + "role": "startloc", + "startIndex": 34, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 50, + "entity": "jakarta", + "role": "endloc", + "startIndex": 44, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 19, + "entity": "next to last", + "resolution": { + "offset": "-1", + "relativeTo": "end" + }, + "role": "startpos", + "startIndex": 8, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 27, + "entity": "last", + "resolution": { + "offset": "0", + "relativeTo": "end" + }, + "startIndex": 24, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 68, + "entity": "homer simpson", + "role": "parent", + "startIndex": 56, + "type": "builtin.personName" + }, + { + "endIndex": 98, + "entity": "lisa simpson", + "role": "child", + "startIndex": 87, + "type": "builtin.personName" + } + ], + "intents": [ + { + "intent": "Roles", + "score": 0.9991838 + }, + { + "intent": "None", + "score": 0.0108486973 + }, + { + "intent": "Weather.GetForecast", + "score": 0.0105504664 + }, + { + "intent": "Travel", + "score": 0.0046763327 + }, + { + "intent": "search", + "score": 0.002142746 + }, + { + "intent": "Delivery", + "score": 0.00121616619 + }, + { + "intent": "EntityTests", + "score": 0.00107762846 + }, + { + "intent": "SpecifyName", + "score": 0.0006965211 + }, + { + "intent": "Help", + "score": 0.000134394242 + }, + { + "intent": "Cancel", + "score": 0.000106304564 + }, + { + "intent": "Greeting", + "score": 5.23392373E-05 + } + ], + "query": "go from next to last to last move london to jakarta and homer simpson is the parent of lisa simpson", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Roles", + "score": 0.9991838 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "child": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "child", + "startIndex": 87, + "text": "lisa simpson", + "type": "builtin.personName" + } + ], + "endloc": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "endloc", + "startIndex": 44, + "text": "jakarta", + "type": "builtin.geographyV2.city" + } + ], + "ordinalV2": [ + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 24, + "text": "last", + "type": "builtin.ordinalV2.relative" + } + ], + "parent": [ + { + "length": 13, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "parent", + "startIndex": 56, + "text": "homer simpson", + "type": "builtin.personName" + } + ], + "startloc": [ + { + "length": 6, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "startloc", + "startIndex": 34, + "text": "london", + "type": "builtin.geographyV2.city" + } + ], + "startpos": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "startpos", + "startIndex": 8, + "text": "next to last", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "child": [ + "lisa simpson" + ], + "endloc": [ + { + "type": "city", + "value": "jakarta" + } + ], + "ordinalV2": [ + { + "offset": 0, + "relativeTo": "end" + } + ], + "parent": [ + "homer simpson" + ], + "startloc": [ + { + "type": "city", + "value": "london" + } + ], + "startpos": [ + { + "offset": -1, + "relativeTo": "end" + } + ] + }, + "intents": { + "Cancel": { + "score": 0.000106304564 + }, + "Delivery": { + "score": 0.00121616619 + }, + "EntityTests": { + "score": 0.00107762846 + }, + "Greeting": { + "score": 5.23392373E-05 + }, + "Help": { + "score": 0.000134394242 + }, + "None": { + "score": 0.0108486973 + }, + "Roles": { + "score": 0.9991838 + }, + "search": { + "score": 0.002142746 + }, + "SpecifyName": { + "score": 0.0006965211 + }, + "Travel": { + "score": 0.0046763327 + }, + "Weather.GetForecast": { + "score": 0.0105504664 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Roles" + }, + "query": "go from next to last to last move london to jakarta and homer simpson is the parent of lisa simpson" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Minimal.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Minimal.json new file mode 100644 index 000000000..62a0bb45e --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Minimal.json @@ -0,0 +1,143 @@ +{ + "entities": { + "Airline": [ + [ + "Delta" + ] + ], + "datetime": [ + { + "timex": [ + "T15" + ], + "type": "time" + } + ], + "dimension": [ + { + "number": 3, + "units": "Picometer" + } + ] + }, + "intents": { + "Roles": { + "score": 0.42429316 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "fly on delta at 3pm", + "v2": { + "options": { + "IncludeAllIntents": false, + "IncludeInstanceData": false, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 18, + "entity": "3pm", + "resolution": { + "values": [ + { + "timex": "T15", + "type": "time", + "value": "15:00:00" + } + ] + }, + "startIndex": 16, + "type": "builtin.datetimeV2.time" + }, + { + "endIndex": 18, + "entity": "3pm", + "resolution": { + "unit": "Picometer", + "value": "3" + }, + "startIndex": 16, + "type": "builtin.dimension" + }, + { + "endIndex": 11, + "entity": "delta", + "resolution": { + "values": [ + "Delta" + ] + }, + "startIndex": 7, + "type": "Airline" + } + ], + "query": "fly on delta at 3pm", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Roles", + "score": 0.42429316 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": false, + "IncludeAPIResults": true, + "IncludeInstanceData": false, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "Airline": [ + [ + "Delta" + ] + ], + "datetimeV2": [ + { + "type": "time", + "values": [ + { + "resolution": [ + { + "value": "15:00:00" + } + ], + "timex": "T15" + } + ] + } + ], + "dimension": [ + { + "number": 3, + "units": "Picometer" + } + ] + }, + "intents": { + "Roles": { + "score": 0.42429316 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Roles" + }, + "query": "fly on delta at 3pm" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/MinimalWithGeo.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/MinimalWithGeo.json new file mode 100644 index 000000000..bd02086e1 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/MinimalWithGeo.json @@ -0,0 +1,672 @@ +{ + "entities": { + "geographyV2": [ + { + "location": "dallas", + "type": "city" + }, + { + "location": "texas", + "type": "state" + } + ] + }, + "intents": { + "EntityTests": { + "score": 0.466911465 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "fly to dallas, texas", + "v2": { + "options": { + "IncludeAllIntents": false, + "IncludeInstanceData": false, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 12, + "entity": "dallas", + "startIndex": 7, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 19, + "entity": "texas", + "startIndex": 15, + "type": "builtin.geographyV2.state" + } + ], + "query": "fly to dallas, texas", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "EntityTests", + "score": 0.466911465 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": false, + "IncludeInstanceData": false, + "LogPersonalInformation": false, + "Slot": "production", + "Timeout": 100000.0 + }, + "response": { + "prediction": { + "entities": { + "Composite1": [ + { + "$instance": { + "age": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old", + "type": "builtin.age" + }, + { + "length": 10, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days old", + "type": "builtin.age" + } + ], + "datetimeV2": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 6, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 21, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 32, + "text": "monday july 3rd, 2019", + "type": "builtin.datetimeV2.date" + }, + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 58, + "text": "every monday", + "type": "builtin.datetimeV2.set" + }, + { + "length": 22, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 75, + "text": "between 3am and 5:30am", + "type": "builtin.datetimeV2.timerange" + } + ], + "dimension": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4 acres", + "type": "builtin.dimension" + }, + { + "length": 13, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4 pico meters", + "type": "builtin.dimension" + } + ], + "email": [ + { + "length": 18, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 132, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "money": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 155, + "text": "$4", + "type": "builtin.currency" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 162, + "text": "$4.25", + "type": "builtin.currency" + } + ], + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "2019", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 91, + "text": "5", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "4", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 163, + "text": "4.25", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 177, + "text": "32", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 184, + "text": "210.4", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 222, + "text": "425", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "555", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 230, + "text": "1234", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 282, + "text": "one", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 303, + "text": "one", + "type": "builtin.number" + } + ], + "percentage": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10%", + "type": "builtin.percentage" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5%", + "type": "builtin.percentage" + } + ], + "phonenumber": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 222, + "text": "425-555-1234", + "type": "builtin.phonenumber" + } + ], + "temperature": [ + { + "length": 9, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3 degrees", + "type": "builtin.temperature" + }, + { + "length": 15, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5 degrees c", + "type": "builtin.temperature" + } + ] + }, + "age": [ + { + "number": 12, + "unit": "Year" + }, + { + "number": 3, + "unit": "Day" + } + ], + "datetimeV2": [ + { + "type": "duration", + "values": [ + { + "timex": "P12Y", + "value": "378432000" + } + ] + }, + { + "type": "duration", + "values": [ + { + "timex": "P3D", + "value": "259200" + } + ] + }, + { + "type": "date", + "values": [ + { + "timex": "2019-07-03", + "value": "2019-07-03" + } + ] + }, + { + "type": "set", + "values": [ + { + "timex": "XXXX-WXX-1", + "value": "not resolved" + } + ] + }, + { + "type": "timerange", + "values": [ + { + "end": "05:30:00", + "start": "03:00:00", + "timex": "(T03,T05:30,PT2H30M)" + } + ] + } + ], + "dimension": [ + { + "number": 4, + "unit": "Acre" + }, + { + "number": 4, + "unit": "Picometer" + } + ], + "email": [ + "chrimc@hotmail.com" + ], + "money": [ + { + "number": 4, + "unit": "Dollar" + }, + { + "number": 4.25, + "unit": "Dollar" + } + ], + "number": [ + 12, + 3, + 2019, + 5, + 4, + 4, + 4, + 4.25, + 32, + 210.4, + 10, + 10.5, + 425, + 555, + 1234, + 3, + -27.5, + 1, + 1 + ], + "percentage": [ + 10, + 10.5 + ], + "phonenumber": [ + "425-555-1234" + ], + "temperature": [ + { + "number": 3, + "unit": "Degree" + }, + { + "number": -27.5, + "unit": "C" + } + ] + } + ], + "ordinalV2": [ + { + "offset": 3, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "current" + }, + { + "offset": -1, + "relativeTo": "current" + } + ] + }, + "intents": { + "Cancel": { + "score": 1.56337478E-06 + }, + "Delivery": { + "score": 0.0002846266 + }, + "EntityTests": { + "score": 0.953405857 + }, + "Greeting": { + "score": 8.20979437E-07 + }, + "Help": { + "score": 4.81870757E-06 + }, + "None": { + "score": 0.01040122 + }, + "Roles": { + "score": 0.197366714 + }, + "search": { + "score": 0.14049834 + }, + "SpecifyName": { + "score": 0.000137732946 + }, + "Travel": { + "score": 0.0100996653 + }, + "Weather.GetForecast": { + "score": 0.0143940123 + } + }, + "normalizedQuery": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/NoEntitiesInstanceTrue.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/NoEntitiesInstanceTrue.json new file mode 100644 index 000000000..32313e0e7 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/NoEntitiesInstanceTrue.json @@ -0,0 +1,41 @@ +{ + "entities": { + "$instance": {} + }, + "intents": { + "Greeting": { + "score": 0.959510267 + } + }, + "sentiment": { + "label": "positive", + "score": 0.9431402 + }, + "text": "Hi", + "v3": { + "options": { + "IncludeAllIntents": false, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": {}, + "intents": { + "Greeting": { + "score": 0.959510267 + } + }, + "sentiment": { + "label": "positive", + "score": 0.9431402 + }, + "topIntent": "Greeting" + }, + "query": "Hi" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Patterns.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Patterns.json new file mode 100644 index 000000000..865967c22 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Patterns.json @@ -0,0 +1,363 @@ +{ + "entities": { + "$instance": { + "extra": [ + { + "endIndex": 76, + "modelType": "Pattern.Any Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 71, + "text": "kb435", + "type": "subject" + } + ], + "parent": [ + { + "endIndex": 61, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "bart simpson", + "type": "builtin.personName" + } + ], + "Part": [ + { + "endIndex": 76, + "modelType": "Regex Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 71, + "text": "kb435", + "type": "Part" + } + ], + "person": [ + { + "endIndex": 61, + "modelType": "Pattern.Any Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "bart simpson", + "type": "person" + } + ], + "subject": [ + { + "endIndex": 43, + "modelType": "Pattern.Any Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 12, + "text": "something wicked this way comes", + "type": "subject" + } + ] + }, + "extra": [ + "kb435" + ], + "parent": [ + "bart simpson" + ], + "Part": [ + "kb435" + ], + "person": [ + "bart simpson" + ], + "subject": [ + "something wicked this way comes" + ] + }, + "intents": { + "Cancel": { + "score": 1.02352937E-09 + }, + "Delivery": { + "score": 1.81E-09 + }, + "EntityTests": { + "score": 1.15439843E-05 + }, + "Greeting": { + "score": 1.09375E-09 + }, + "Help": { + "score": 1.02352937E-09 + }, + "None": { + "score": 2.394552E-06 + }, + "Roles": { + "score": 5.6224585E-06 + }, + "search": { + "score": 0.9999948 + }, + "SpecifyName": { + "score": 3.08333337E-09 + }, + "Travel": { + "score": 3.08333337E-09 + }, + "Weather_GetForecast": { + "score": 1.03386708E-06 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "email about something wicked this way comes from bart simpson and also kb435", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 60, + "entity": "bart simpson", + "role": "parent", + "startIndex": 49, + "type": "builtin.personName" + }, + { + "endIndex": 75, + "entity": "kb435", + "startIndex": 71, + "type": "Part" + }, + { + "endIndex": 42, + "entity": "something wicked this way comes", + "role": "", + "startIndex": 12, + "type": "subject" + }, + { + "endIndex": 60, + "entity": "bart simpson", + "role": "", + "startIndex": 49, + "type": "person" + }, + { + "endIndex": 75, + "entity": "kb435", + "role": "extra", + "startIndex": 71, + "type": "subject" + } + ], + "intents": [ + { + "intent": "search", + "score": 0.9999948 + }, + { + "intent": "EntityTests", + "score": 1.15439843E-05 + }, + { + "intent": "Roles", + "score": 5.6224585E-06 + }, + { + "intent": "None", + "score": 2.394552E-06 + }, + { + "intent": "Weather.GetForecast", + "score": 1.03386708E-06 + }, + { + "intent": "SpecifyName", + "score": 3.08333337E-09 + }, + { + "intent": "Travel", + "score": 3.08333337E-09 + }, + { + "intent": "Delivery", + "score": 1.81E-09 + }, + { + "intent": "Greeting", + "score": 1.09375E-09 + }, + { + "intent": "Cancel", + "score": 1.02352937E-09 + }, + { + "intent": "Help", + "score": 1.02352937E-09 + } + ], + "query": "email about something wicked this way comes from bart simpson and also kb435", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "search", + "score": 0.9999948 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "extra": [ + { + "length": 5, + "modelType": "Pattern.Any Entity Extractor", + "modelTypeId": 7, + "recognitionSources": [ + "model" + ], + "role": "extra", + "startIndex": 71, + "text": "kb435", + "type": "subject" + } + ], + "parent": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "parent", + "startIndex": 49, + "text": "bart simpson", + "type": "builtin.personName" + } + ], + "Part": [ + { + "length": 5, + "modelType": "Regex Entity Extractor", + "modelTypeId": 8, + "recognitionSources": [ + "model" + ], + "startIndex": 71, + "text": "kb435", + "type": "Part" + } + ], + "person": [ + { + "length": 12, + "modelType": "Pattern.Any Entity Extractor", + "modelTypeId": 7, + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "bart simpson", + "type": "person" + } + ], + "subject": [ + { + "length": 31, + "modelType": "Pattern.Any Entity Extractor", + "modelTypeId": 7, + "recognitionSources": [ + "model" + ], + "startIndex": 12, + "text": "something wicked this way comes", + "type": "subject" + } + ] + }, + "extra": [ + "kb435" + ], + "parent": [ + "bart simpson" + ], + "Part": [ + "kb435" + ], + "person": [ + "bart simpson" + ], + "subject": [ + "something wicked this way comes" + ] + }, + "intents": { + "Cancel": { + "score": 1.02352937E-09 + }, + "Delivery": { + "score": 1.81E-09 + }, + "EntityTests": { + "score": 1.15439843E-05 + }, + "Greeting": { + "score": 1.09375E-09 + }, + "Help": { + "score": 1.02352937E-09 + }, + "None": { + "score": 2.394552E-06 + }, + "Roles": { + "score": 5.6224585E-06 + }, + "search": { + "score": 0.9999948 + }, + "SpecifyName": { + "score": 3.08333337E-09 + }, + "Travel": { + "score": 3.08333337E-09 + }, + "Weather.GetForecast": { + "score": 1.03386708E-06 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "search" + }, + "query": "email about something wicked this way comes from bart simpson and also kb435" + } + } +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Prebuilt.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Prebuilt.json new file mode 100644 index 000000000..1ccad7581 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Prebuilt.json @@ -0,0 +1,351 @@ +{ + "entities": { + "$instance": { + "Composite2": [ + { + "endIndex": 66, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "endIndex": 66, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "endIndex": 14, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "Weather_Location": [ + { + "endIndex": 66, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "Weather.Location" + } + ] + }, + "Weather_Location": [ + "seattle" + ] + } + ], + "geographyV2": [ + { + "location": "seattle", + "type": "city" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.00017013021 + }, + "Delivery": { + "score": 0.00114031672 + }, + "EntityTests": { + "score": 0.286522 + }, + "Greeting": { + "score": 0.000150978623 + }, + "Help": { + "score": 0.000547617 + }, + "None": { + "score": 0.01798658 + }, + "Roles": { + "score": 0.0459664278 + }, + "search": { + "score": 0.0009428267 + }, + "SpecifyName": { + "score": 0.0009960134 + }, + "Travel": { + "score": 0.00235179346 + }, + "Weather_GetForecast": { + "score": 0.6732952 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "Weather.Location", + "value": "seattle" + } + ], + "parentType": "Composite2", + "value": "http : / / foo . com is where you can get a weather forecast for seattle" + } + ], + "entities": [ + { + "endIndex": 65, + "entity": "seattle", + "score": 0.8245291, + "startIndex": 59, + "type": "Weather.Location" + }, + { + "endIndex": 65, + "entity": "http : / / foo . com is where you can get a weather forecast for seattle", + "score": 0.6503277, + "startIndex": 0, + "type": "Composite2" + }, + { + "endIndex": 65, + "entity": "seattle", + "startIndex": 59, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 13, + "entity": "http://foo.com", + "resolution": { + "value": "http://foo.com" + }, + "role": "oldURL", + "startIndex": 0, + "type": "builtin.url" + } + ], + "intents": [ + { + "intent": "Weather.GetForecast", + "score": 0.6732952 + }, + { + "intent": "EntityTests", + "score": 0.286522 + }, + { + "intent": "Roles", + "score": 0.0459664278 + }, + { + "intent": "None", + "score": 0.01798658 + }, + { + "intent": "Travel", + "score": 0.00235179346 + }, + { + "intent": "Delivery", + "score": 0.00114031672 + }, + { + "intent": "SpecifyName", + "score": 0.0009960134 + }, + { + "intent": "search", + "score": 0.0009428267 + }, + { + "intent": "Help", + "score": 0.000547617 + }, + { + "intent": "Cancel", + "score": 0.00017013021 + }, + { + "intent": "Greeting", + "score": 0.000150978623 + } + ], + "query": "http://foo.com is where you can get a weather forecast for seattle", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Weather.GetForecast", + "score": 0.6732952 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Composite2": [ + { + "length": 66, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "length": 14, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "oldURL", + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "Weather.Location": [ + { + "length": 7, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "Weather.Location" + } + ] + }, + "Weather.Location": [ + "seattle" + ] + } + ], + "geographyV2": [ + { + "type": "city", + "value": "seattle" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.00017013021 + }, + "Delivery": { + "score": 0.00114031672 + }, + "EntityTests": { + "score": 0.286522 + }, + "Greeting": { + "score": 0.000150978623 + }, + "Help": { + "score": 0.000547617 + }, + "None": { + "score": 0.01798658 + }, + "Roles": { + "score": 0.0459664278 + }, + "search": { + "score": 0.0009428267 + }, + "SpecifyName": { + "score": 0.0009960134 + }, + "Travel": { + "score": 0.00235179346 + }, + "Weather.GetForecast": { + "score": 0.6732952 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Weather.GetForecast" + }, + "query": "http://foo.com is where you can get a weather forecast for seattle" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TestRecognizerResultConvert.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TestRecognizerResultConvert.java new file mode 100644 index 000000000..8e6f6b4bc --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TestRecognizerResultConvert.java @@ -0,0 +1,15 @@ +package com.microsoft.bot.ai.luis.testdata; + +import com.microsoft.bot.builder.RecognizerConvert; +import com.microsoft.bot.builder.RecognizerResult; + +public class TestRecognizerResultConvert implements RecognizerConvert { + + public String recognizerResultText; + + @Override + public void convert(Object result) { + RecognizerResult castedObject = ((RecognizerResult) result); + recognizerResultText = castedObject.getText(); + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TraceActivity.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TraceActivity.json new file mode 100644 index 000000000..da661fa29 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TraceActivity.json @@ -0,0 +1,247 @@ +{ + "entities": { + "$instance": { + "Name": [ + { + "endIndex": 15, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 11, + "text": "Emad", + "type": "Name" + } + ], + "personName": [ + { + "endIndex": 15, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 11, + "text": "Emad", + "type": "builtin.personName" + } + ] + }, + "Name": [ + "Emad" + ], + "personName": [ + "Emad" + ] + }, + "intents": { + "Cancel": { + "score": 0.00555860251 + }, + "Delivery": { + "score": 0.005572036 + }, + "EntityTests": { + "score": 0.006504555 + }, + "Greeting": { + "score": 0.07429792 + }, + "Help": { + "score": 0.004867298 + }, + "None": { + "score": 0.0109896818 + }, + "Roles": { + "score": 0.0382854156 + }, + "search": { + "score": 0.0006921758 + }, + "SpecifyName": { + "score": 0.794012964 + }, + "Travel": { + "score": 0.00203858572 + }, + "Weather_GetForecast": { + "score": 0.006886828 + } + }, + "sentiment": { + "label": "positive", + "score": 0.7930478 + }, + "text": "My name is Emad", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 14, + "entity": "emad", + "score": 0.980508447, + "startIndex": 11, + "type": "Name" + }, + { + "endIndex": 14, + "entity": "emad", + "startIndex": 11, + "type": "builtin.personName" + } + ], + "intents": [ + { + "intent": "SpecifyName", + "score": 0.794012964 + }, + { + "intent": "Greeting", + "score": 0.07429792 + }, + { + "intent": "Roles", + "score": 0.0382854156 + }, + { + "intent": "None", + "score": 0.0109896818 + }, + { + "intent": "Weather.GetForecast", + "score": 0.006886828 + }, + { + "intent": "EntityTests", + "score": 0.006504555 + }, + { + "intent": "Delivery", + "score": 0.005572036 + }, + { + "intent": "Cancel", + "score": 0.00555860251 + }, + { + "intent": "Help", + "score": 0.004867298 + }, + { + "intent": "Travel", + "score": 0.00203858572 + }, + { + "intent": "search", + "score": 0.0006921758 + } + ], + "query": "My name is Emad", + "sentimentAnalysis": { + "label": "positive", + "score": 0.7930478 + }, + "topScoringIntent": { + "intent": "SpecifyName", + "score": 0.794012964 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Name": [ + { + "length": 4, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "startIndex": 11, + "text": "Emad", + "type": "Name" + } + ], + "personName": [ + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 11, + "text": "Emad", + "type": "builtin.personName" + } + ] + }, + "Name": [ + "Emad" + ], + "personName": [ + "Emad" + ] + }, + "intents": { + "Cancel": { + "score": 0.00555860251 + }, + "Delivery": { + "score": 0.005572036 + }, + "EntityTests": { + "score": 0.006504555 + }, + "Greeting": { + "score": 0.07429792 + }, + "Help": { + "score": 0.004867298 + }, + "None": { + "score": 0.0109896818 + }, + "Roles": { + "score": 0.0382854156 + }, + "search": { + "score": 0.0006921758 + }, + "SpecifyName": { + "score": 0.794012964 + }, + "Travel": { + "score": 0.00203858572 + }, + "Weather.GetForecast": { + "score": 0.006886828 + } + }, + "sentiment": { + "label": "positive", + "score": 0.7930478 + }, + "topIntent": "SpecifyName" + }, + "query": "My name is Emad" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Typed.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Typed.json new file mode 100644 index 000000000..88b2a1002 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/Typed.json @@ -0,0 +1,1971 @@ +{ + "entities": { + "$instance": { + "begin": [ + { + "endIndex": 12, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old", + "type": "builtin.age" + } + ], + "Composite1": [ + { + "endIndex": 306, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "type": "Composite1" + } + ], + "end": [ + { + "endIndex": 27, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days old", + "type": "builtin.age" + } + ], + "endpos": [ + { + "endIndex": 47, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 44, + "text": "3rd", + "type": "builtin.ordinalV2" + } + ], + "max": [ + { + "endIndex": 167, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 162, + "text": "$4.25", + "type": "builtin.currency" + } + ], + "ordinalV2": [ + { + "endIndex": 199, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 194, + "text": "first", + "type": "builtin.ordinalV2" + }, + { + "endIndex": 285, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 277, + "text": "next one", + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 306, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 294, + "text": "previous one", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "begin": [ + { + "number": 12, + "units": "Year" + } + ], + "Composite1": [ + { + "$instance": { + "datetime": [ + { + "endIndex": 8, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years", + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 23, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days", + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 53, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 32, + "text": "monday july 3rd, 2019", + "type": "builtin.datetimeV2.date" + }, + { + "endIndex": 70, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 58, + "text": "every monday", + "type": "builtin.datetimeV2.set" + }, + { + "endIndex": 97, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 75, + "text": "between 3am and 5:30am", + "type": "builtin.datetimeV2.timerange" + } + ], + "dimension": [ + { + "endIndex": 109, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4 acres", + "type": "builtin.dimension" + }, + { + "endIndex": 127, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4 pico meters", + "type": "builtin.dimension" + } + ], + "email": [ + { + "endIndex": 150, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 132, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "money": [ + { + "endIndex": 157, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 155, + "text": "$4", + "type": "builtin.currency" + } + ], + "number": [ + { + "endIndex": 2, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12", + "type": "builtin.number" + }, + { + "endIndex": 18, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3", + "type": "builtin.number" + }, + { + "endIndex": 53, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "2019", + "type": "builtin.number" + }, + { + "endIndex": 92, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 91, + "text": "5", + "type": "builtin.number" + }, + { + "endIndex": 103, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 115, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 157, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "4", + "type": "builtin.number" + }, + { + "endIndex": 167, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 163, + "text": "4.25", + "type": "builtin.number" + }, + { + "endIndex": 179, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 177, + "text": "32", + "type": "builtin.number" + }, + { + "endIndex": 189, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 184, + "text": "210.4", + "type": "builtin.number" + }, + { + "endIndex": 206, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10", + "type": "builtin.number" + }, + { + "endIndex": 216, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5", + "type": "builtin.number" + }, + { + "endIndex": 225, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 222, + "text": "425", + "type": "builtin.number" + }, + { + "endIndex": 229, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "555", + "type": "builtin.number" + }, + { + "endIndex": 234, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 230, + "text": "1234", + "type": "builtin.number" + }, + { + "endIndex": 240, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3", + "type": "builtin.number" + }, + { + "endIndex": 258, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5", + "type": "builtin.number" + }, + { + "endIndex": 285, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 282, + "text": "one", + "type": "builtin.number" + }, + { + "endIndex": 306, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 303, + "text": "one", + "type": "builtin.number" + } + ], + "percentage": [ + { + "endIndex": 207, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10%", + "type": "builtin.percentage" + }, + { + "endIndex": 217, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5%", + "type": "builtin.percentage" + } + ], + "phonenumber": [ + { + "endIndex": 234, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 222, + "text": "425-555-1234", + "type": "builtin.phonenumber" + } + ], + "temperature": [ + { + "endIndex": 248, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3 degrees", + "type": "builtin.temperature" + }, + { + "endIndex": 268, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5 degrees c", + "type": "builtin.temperature" + } + ] + }, + "datetime": [ + { + "timex": [ + "P12Y" + ], + "type": "duration" + }, + { + "timex": [ + "P3D" + ], + "type": "duration" + }, + { + "timex": [ + "2019-07-03" + ], + "type": "date" + }, + { + "timex": [ + "XXXX-WXX-1" + ], + "type": "set" + }, + { + "timex": [ + "(T03,T05:30,PT2H30M)" + ], + "type": "timerange" + } + ], + "dimension": [ + { + "number": 4, + "units": "Acre" + }, + { + "number": 4, + "units": "Picometer" + } + ], + "email": [ + "chrimc@hotmail.com" + ], + "money": [ + { + "number": 4, + "units": "Dollar" + } + ], + "number": [ + 12, + 3, + 2019, + 5, + 4, + 4, + 4, + 4.25, + 32, + 210.4, + 10, + 10.5, + 425, + 555, + 1234, + 3, + -27.5, + 1, + 1 + ], + "percentage": [ + 10, + 10.5 + ], + "phonenumber": [ + "425-555-1234" + ], + "temperature": [ + { + "number": 3, + "units": "Degree" + }, + { + "number": -27.5, + "units": "C" + } + ] + } + ], + "end": [ + { + "number": 3, + "units": "Day" + } + ], + "endpos": [ + { + "offset": 3, + "relativeTo": "start" + } + ], + "max": [ + { + "number": 4.25, + "units": "Dollar" + } + ], + "ordinalV2": [ + { + "offset": 1, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "current" + }, + { + "offset": -1, + "relativeTo": "current" + } + ] + }, + "intents": { + "Cancel": { + "score": 1.54311692E-06 + }, + "Delivery": { + "score": 0.000280677923 + }, + "EntityTests": { + "score": 0.958614767 + }, + "Greeting": { + "score": 8.076372E-07 + }, + "Help": { + "score": 4.74059061E-06 + }, + "None": { + "score": 0.0101076821 + }, + "Roles": { + "score": 0.191202149 + }, + "search": { + "score": 0.00475360872 + }, + "SpecifyName": { + "score": 7.367716E-05 + }, + "Travel": { + "score": 0.00232480234 + }, + "Weather_GetForecast": { + "score": 0.0141556319 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "builtin.datetimeV2.duration", + "value": "12 years" + }, + { + "type": "builtin.datetimeV2.duration", + "value": "3 days" + }, + { + "type": "builtin.datetimeV2.date", + "value": "monday july 3rd, 2019" + }, + { + "type": "builtin.datetimeV2.set", + "value": "every monday" + }, + { + "type": "builtin.datetimeV2.timerange", + "value": "between 3am and 5:30am" + }, + { + "type": "builtin.dimension", + "value": "4 acres" + }, + { + "type": "builtin.dimension", + "value": "4 pico meters" + }, + { + "type": "builtin.email", + "value": "chrimc@hotmail.com" + }, + { + "type": "builtin.currency", + "value": "$4" + }, + { + "type": "builtin.number", + "value": "12" + }, + { + "type": "builtin.number", + "value": "3" + }, + { + "type": "builtin.number", + "value": "2019" + }, + { + "type": "builtin.number", + "value": "5" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4" + }, + { + "type": "builtin.number", + "value": "4.25" + }, + { + "type": "builtin.number", + "value": "32" + }, + { + "type": "builtin.number", + "value": "210.4" + }, + { + "type": "builtin.number", + "value": "10" + }, + { + "type": "builtin.number", + "value": "10.5" + }, + { + "type": "builtin.number", + "value": "425" + }, + { + "type": "builtin.number", + "value": "555" + }, + { + "type": "builtin.number", + "value": "1234" + }, + { + "type": "builtin.number", + "value": "3" + }, + { + "type": "builtin.number", + "value": "-27.5" + }, + { + "type": "builtin.number", + "value": "one" + }, + { + "type": "builtin.number", + "value": "one" + }, + { + "type": "builtin.percentage", + "value": "10%" + }, + { + "type": "builtin.percentage", + "value": "10.5%" + }, + { + "type": "builtin.phonenumber", + "value": "425-555-1234" + }, + { + "type": "builtin.temperature", + "value": "3 degrees" + }, + { + "type": "builtin.temperature", + "value": "-27.5 degrees c" + } + ], + "parentType": "Composite1", + "value": "12 years old and 3 days old and monday july 3rd , 2019 and every monday and between 3am and 5 : 30am and 4 acres and 4 pico meters and chrimc @ hotmail . com and $ 4 and $ 4 . 25 and also 32 and 210 . 4 and first and 10 % and 10 . 5 % and 425 - 555 - 1234 and 3 degrees and - 27 . 5 degrees c and the next one and the previous one" + } + ], + "entities": [ + { + "endIndex": 305, + "entity": "12 years old and 3 days old and monday july 3rd , 2019 and every monday and between 3am and 5 : 30am and 4 acres and 4 pico meters and chrimc @ hotmail . com and $ 4 and $ 4 . 25 and also 32 and 210 . 4 and first and 10 % and 10 . 5 % and 425 - 555 - 1234 and 3 degrees and - 27 . 5 degrees c and the next one and the previous one", + "score": 0.9074669, + "startIndex": 0, + "type": "Composite1" + }, + { + "endIndex": 1, + "entity": "12", + "resolution": { + "subtype": "integer", + "value": "12" + }, + "startIndex": 0, + "type": "builtin.number" + }, + { + "endIndex": 17, + "entity": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "startIndex": 17, + "type": "builtin.number" + }, + { + "endIndex": 52, + "entity": "2019", + "resolution": { + "subtype": "integer", + "value": "2019" + }, + "startIndex": 49, + "type": "builtin.number" + }, + { + "endIndex": 91, + "entity": "5", + "resolution": { + "subtype": "integer", + "value": "5" + }, + "startIndex": 91, + "type": "builtin.number" + }, + { + "endIndex": 102, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 102, + "type": "builtin.number" + }, + { + "endIndex": 114, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 114, + "type": "builtin.number" + }, + { + "endIndex": 156, + "entity": "4", + "resolution": { + "subtype": "integer", + "value": "4" + }, + "startIndex": 156, + "type": "builtin.number" + }, + { + "endIndex": 166, + "entity": "4.25", + "resolution": { + "subtype": "decimal", + "value": "4.25" + }, + "startIndex": 163, + "type": "builtin.number" + }, + { + "endIndex": 178, + "entity": "32", + "resolution": { + "subtype": "integer", + "value": "32" + }, + "startIndex": 177, + "type": "builtin.number" + }, + { + "endIndex": 188, + "entity": "210.4", + "resolution": { + "subtype": "decimal", + "value": "210.4" + }, + "startIndex": 184, + "type": "builtin.number" + }, + { + "endIndex": 205, + "entity": "10", + "resolution": { + "subtype": "integer", + "value": "10" + }, + "startIndex": 204, + "type": "builtin.number" + }, + { + "endIndex": 215, + "entity": "10.5", + "resolution": { + "subtype": "decimal", + "value": "10.5" + }, + "startIndex": 212, + "type": "builtin.number" + }, + { + "endIndex": 224, + "entity": "425", + "resolution": { + "subtype": "integer", + "value": "425" + }, + "startIndex": 222, + "type": "builtin.number" + }, + { + "endIndex": 228, + "entity": "555", + "resolution": { + "subtype": "integer", + "value": "555" + }, + "startIndex": 226, + "type": "builtin.number" + }, + { + "endIndex": 233, + "entity": "1234", + "resolution": { + "subtype": "integer", + "value": "1234" + }, + "startIndex": 230, + "type": "builtin.number" + }, + { + "endIndex": 239, + "entity": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "startIndex": 239, + "type": "builtin.number" + }, + { + "endIndex": 257, + "entity": "-27.5", + "resolution": { + "subtype": "decimal", + "value": "-27.5" + }, + "startIndex": 253, + "type": "builtin.number" + }, + { + "endIndex": 284, + "entity": "one", + "resolution": { + "subtype": "integer", + "value": "1" + }, + "startIndex": 282, + "type": "builtin.number" + }, + { + "endIndex": 305, + "entity": "one", + "resolution": { + "subtype": "integer", + "value": "1" + }, + "startIndex": 303, + "type": "builtin.number" + }, + { + "endIndex": 11, + "entity": "12 years old", + "resolution": { + "unit": "Year", + "value": "12" + }, + "role": "begin", + "startIndex": 0, + "type": "builtin.age" + }, + { + "endIndex": 26, + "entity": "3 days old", + "resolution": { + "unit": "Day", + "value": "3" + }, + "role": "end", + "startIndex": 17, + "type": "builtin.age" + }, + { + "endIndex": 7, + "entity": "12 years", + "resolution": { + "values": [ + { + "timex": "P12Y", + "type": "duration", + "value": "378432000" + } + ] + }, + "startIndex": 0, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 22, + "entity": "3 days", + "resolution": { + "values": [ + { + "timex": "P3D", + "type": "duration", + "value": "259200" + } + ] + }, + "startIndex": 17, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 52, + "entity": "monday july 3rd, 2019", + "resolution": { + "values": [ + { + "timex": "2019-07-03", + "type": "date", + "value": "2019-07-03" + } + ] + }, + "startIndex": 32, + "type": "builtin.datetimeV2.date" + }, + { + "endIndex": 69, + "entity": "every monday", + "resolution": { + "values": [ + { + "timex": "XXXX-WXX-1", + "type": "set", + "value": "not resolved" + } + ] + }, + "startIndex": 58, + "type": "builtin.datetimeV2.set" + }, + { + "endIndex": 96, + "entity": "between 3am and 5:30am", + "resolution": { + "values": [ + { + "end": "05:30:00", + "start": "03:00:00", + "timex": "(T03,T05:30,PT2H30M)", + "type": "timerange" + } + ] + }, + "startIndex": 75, + "type": "builtin.datetimeV2.timerange" + }, + { + "endIndex": 108, + "entity": "4 acres", + "resolution": { + "unit": "Acre", + "value": "4" + }, + "startIndex": 102, + "type": "builtin.dimension" + }, + { + "endIndex": 126, + "entity": "4 pico meters", + "resolution": { + "unit": "Picometer", + "value": "4" + }, + "startIndex": 114, + "type": "builtin.dimension" + }, + { + "endIndex": 149, + "entity": "chrimc@hotmail.com", + "resolution": { + "value": "chrimc@hotmail.com" + }, + "startIndex": 132, + "type": "builtin.email" + }, + { + "endIndex": 156, + "entity": "$4", + "resolution": { + "unit": "Dollar", + "value": "4" + }, + "startIndex": 155, + "type": "builtin.currency" + }, + { + "endIndex": 166, + "entity": "$4.25", + "resolution": { + "unit": "Dollar", + "value": "4.25" + }, + "role": "max", + "startIndex": 162, + "type": "builtin.currency" + }, + { + "endIndex": 46, + "entity": "3rd", + "resolution": { + "offset": "3", + "relativeTo": "start" + }, + "role": "endpos", + "startIndex": 44, + "type": "builtin.ordinalV2" + }, + { + "endIndex": 198, + "entity": "first", + "resolution": { + "offset": "1", + "relativeTo": "start" + }, + "startIndex": 194, + "type": "builtin.ordinalV2" + }, + { + "endIndex": 284, + "entity": "next one", + "resolution": { + "offset": "1", + "relativeTo": "current" + }, + "startIndex": 277, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 305, + "entity": "previous one", + "resolution": { + "offset": "-1", + "relativeTo": "current" + }, + "startIndex": 294, + "type": "builtin.ordinalV2.relative" + }, + { + "endIndex": 206, + "entity": "10%", + "resolution": { + "value": "10%" + }, + "startIndex": 204, + "type": "builtin.percentage" + }, + { + "endIndex": 216, + "entity": "10.5%", + "resolution": { + "value": "10.5%" + }, + "startIndex": 212, + "type": "builtin.percentage" + }, + { + "endIndex": 233, + "entity": "425-555-1234", + "resolution": { + "score": "0.9", + "value": "425-555-1234" + }, + "startIndex": 222, + "type": "builtin.phonenumber" + }, + { + "endIndex": 247, + "entity": "3 degrees", + "resolution": { + "unit": "Degree", + "value": "3" + }, + "startIndex": 239, + "type": "builtin.temperature" + }, + { + "endIndex": 267, + "entity": "-27.5 degrees c", + "resolution": { + "unit": "C", + "value": "-27.5" + }, + "startIndex": 253, + "type": "builtin.temperature" + } + ], + "intents": [ + { + "intent": "EntityTests", + "score": 0.958614767 + }, + { + "intent": "Roles", + "score": 0.191202149 + }, + { + "intent": "Weather.GetForecast", + "score": 0.0141556319 + }, + { + "intent": "None", + "score": 0.0101076821 + }, + { + "intent": "search", + "score": 0.00475360872 + }, + { + "intent": "Travel", + "score": 0.00232480234 + }, + { + "intent": "Delivery", + "score": 0.000280677923 + }, + { + "intent": "SpecifyName", + "score": 7.367716E-05 + }, + { + "intent": "Help", + "score": 4.74059061E-06 + }, + { + "intent": "Cancel", + "score": 1.54311692E-06 + }, + { + "intent": "Greeting", + "score": 8.076372E-07 + } + ], + "query": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "EntityTests", + "score": 0.958614767 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "begin": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "begin", + "startIndex": 0, + "text": "12 years old", + "type": "builtin.age" + } + ], + "Composite1": [ + { + "length": 306, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one", + "type": "Composite1" + } + ], + "end": [ + { + "length": 10, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "end", + "startIndex": 17, + "text": "3 days old", + "type": "builtin.age" + } + ], + "endpos": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "endpos", + "startIndex": 44, + "text": "3rd", + "type": "builtin.ordinalV2" + } + ], + "max": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "max", + "startIndex": 162, + "text": "$4.25", + "type": "builtin.currency" + } + ], + "ordinalV2": [ + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 194, + "text": "first", + "type": "builtin.ordinalV2" + }, + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 277, + "text": "next one", + "type": "builtin.ordinalV2.relative" + }, + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 294, + "text": "previous one", + "type": "builtin.ordinalV2.relative" + } + ] + }, + "begin": [ + { + "number": 12, + "units": "Year" + } + ], + "Composite1": [ + { + "$instance": { + "datetimeV2": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12 years", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 6, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3 days", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 21, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 32, + "text": "monday july 3rd, 2019", + "type": "builtin.datetimeV2.date" + }, + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 58, + "text": "every monday", + "type": "builtin.datetimeV2.set" + }, + { + "length": 22, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 75, + "text": "between 3am and 5:30am", + "type": "builtin.datetimeV2.timerange" + } + ], + "dimension": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4 acres", + "type": "builtin.dimension" + }, + { + "length": 13, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4 pico meters", + "type": "builtin.dimension" + } + ], + "email": [ + { + "length": 18, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 132, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "money": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 155, + "text": "$4", + "type": "builtin.currency" + } + ], + "number": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "12", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "3", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 49, + "text": "2019", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 91, + "text": "5", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 102, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 114, + "text": "4", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "4", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 163, + "text": "4.25", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 177, + "text": "32", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 184, + "text": "210.4", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 222, + "text": "425", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "555", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 230, + "text": "1234", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3", + "type": "builtin.number" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 282, + "text": "one", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 303, + "text": "one", + "type": "builtin.number" + } + ], + "percentage": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 204, + "text": "10%", + "type": "builtin.percentage" + }, + { + "length": 5, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "10.5%", + "type": "builtin.percentage" + } + ], + "phonenumber": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 222, + "text": "425-555-1234", + "type": "builtin.phonenumber" + } + ], + "temperature": [ + { + "length": 9, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 239, + "text": "3 degrees", + "type": "builtin.temperature" + }, + { + "length": 15, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 253, + "text": "-27.5 degrees c", + "type": "builtin.temperature" + } + ] + }, + "datetimeV2": [ + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "378432000" + } + ], + "timex": "P12Y" + } + ] + }, + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "259200" + } + ], + "timex": "P3D" + } + ] + }, + { + "type": "date", + "values": [ + { + "resolution": [ + { + "value": "2019-07-03" + } + ], + "timex": "2019-07-03" + } + ] + }, + { + "type": "set", + "values": [ + { + "resolution": [ + { + "value": "not resolved" + } + ], + "timex": "XXXX-WXX-1" + } + ] + }, + { + "type": "timerange", + "values": [ + { + "resolution": [ + { + "end": "05:30:00", + "start": "03:00:00" + } + ], + "timex": "(T03,T05:30,PT2H30M)" + } + ] + } + ], + "dimension": [ + { + "number": 4, + "units": "Acre" + }, + { + "number": 4, + "units": "Picometer" + } + ], + "email": [ + "chrimc@hotmail.com" + ], + "money": [ + { + "number": 4, + "units": "Dollar" + } + ], + "number": [ + 12, + 3, + 2019, + 5, + 4, + 4, + 4, + 4.25, + 32, + 210.4, + 10, + 10.5, + 425, + 555, + 1234, + 3, + -27.5, + 1, + 1 + ], + "percentage": [ + 10, + 10.5 + ], + "phonenumber": [ + "425-555-1234" + ], + "temperature": [ + { + "number": 3, + "units": "Degree" + }, + { + "number": -27.5, + "units": "C" + } + ] + } + ], + "end": [ + { + "number": 3, + "units": "Day" + } + ], + "endpos": [ + { + "offset": 3, + "relativeTo": "start" + } + ], + "max": [ + { + "number": 4.25, + "units": "Dollar" + } + ], + "ordinalV2": [ + { + "offset": 1, + "relativeTo": "start" + }, + { + "offset": 1, + "relativeTo": "current" + }, + { + "offset": -1, + "relativeTo": "current" + } + ] + }, + "intents": { + "Cancel": { + "score": 1.54311692E-06 + }, + "Delivery": { + "score": 0.000280677923 + }, + "EntityTests": { + "score": 0.958614767 + }, + "Greeting": { + "score": 8.076372E-07 + }, + "Help": { + "score": 4.74059061E-06 + }, + "None": { + "score": 0.0101076821 + }, + "Roles": { + "score": 0.191202149 + }, + "search": { + "score": 0.00475360872 + }, + "SpecifyName": { + "score": 7.367716E-05 + }, + "Travel": { + "score": 0.00232480234 + }, + "Weather.GetForecast": { + "score": 0.0141556319 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "EntityTests" + }, + "query": "12 years old and 3 days old and monday july 3rd, 2019 and every monday and between 3am and 5:30am and 4 acres and 4 pico meters and chrimc@hotmail.com and $4 and $4.25 and also 32 and 210.4 and first and 10% and 10.5% and 425-555-1234 and 3 degrees and -27.5 degrees c and the next one and the previous one" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TypedPrebuilt.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TypedPrebuilt.json new file mode 100644 index 000000000..1ccad7581 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/TypedPrebuilt.json @@ -0,0 +1,351 @@ +{ + "entities": { + "$instance": { + "Composite2": [ + { + "endIndex": 66, + "modelType": "Composite Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "endIndex": 66, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "endIndex": 14, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "Weather_Location": [ + { + "endIndex": 66, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "Weather.Location" + } + ] + }, + "Weather_Location": [ + "seattle" + ] + } + ], + "geographyV2": [ + { + "location": "seattle", + "type": "city" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.00017013021 + }, + "Delivery": { + "score": 0.00114031672 + }, + "EntityTests": { + "score": 0.286522 + }, + "Greeting": { + "score": 0.000150978623 + }, + "Help": { + "score": 0.000547617 + }, + "None": { + "score": 0.01798658 + }, + "Roles": { + "score": 0.0459664278 + }, + "search": { + "score": 0.0009428267 + }, + "SpecifyName": { + "score": 0.0009960134 + }, + "Travel": { + "score": 0.00235179346 + }, + "Weather_GetForecast": { + "score": 0.6732952 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "compositeEntities": [ + { + "children": [ + { + "type": "Weather.Location", + "value": "seattle" + } + ], + "parentType": "Composite2", + "value": "http : / / foo . com is where you can get a weather forecast for seattle" + } + ], + "entities": [ + { + "endIndex": 65, + "entity": "seattle", + "score": 0.8245291, + "startIndex": 59, + "type": "Weather.Location" + }, + { + "endIndex": 65, + "entity": "http : / / foo . com is where you can get a weather forecast for seattle", + "score": 0.6503277, + "startIndex": 0, + "type": "Composite2" + }, + { + "endIndex": 65, + "entity": "seattle", + "startIndex": 59, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 13, + "entity": "http://foo.com", + "resolution": { + "value": "http://foo.com" + }, + "role": "oldURL", + "startIndex": 0, + "type": "builtin.url" + } + ], + "intents": [ + { + "intent": "Weather.GetForecast", + "score": 0.6732952 + }, + { + "intent": "EntityTests", + "score": 0.286522 + }, + { + "intent": "Roles", + "score": 0.0459664278 + }, + { + "intent": "None", + "score": 0.01798658 + }, + { + "intent": "Travel", + "score": 0.00235179346 + }, + { + "intent": "Delivery", + "score": 0.00114031672 + }, + { + "intent": "SpecifyName", + "score": 0.0009960134 + }, + { + "intent": "search", + "score": 0.0009428267 + }, + { + "intent": "Help", + "score": 0.000547617 + }, + { + "intent": "Cancel", + "score": 0.00017013021 + }, + { + "intent": "Greeting", + "score": 0.000150978623 + } + ], + "query": "http://foo.com is where you can get a weather forecast for seattle", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Weather.GetForecast", + "score": 0.6732952 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "Composite2": [ + { + "length": 66, + "modelType": "Composite Entity Extractor", + "modelTypeId": 4, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "http://foo.com is where you can get a weather forecast for seattle", + "type": "Composite2" + } + ], + "geographyV2": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "builtin.geographyV2.city" + } + ], + "oldURL": [ + { + "length": 14, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "oldURL", + "startIndex": 0, + "text": "http://foo.com", + "type": "builtin.url" + } + ] + }, + "Composite2": [ + { + "$instance": { + "Weather.Location": [ + { + "length": 7, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "startIndex": 59, + "text": "seattle", + "type": "Weather.Location" + } + ] + }, + "Weather.Location": [ + "seattle" + ] + } + ], + "geographyV2": [ + { + "type": "city", + "value": "seattle" + } + ], + "oldURL": [ + "http://foo.com" + ] + }, + "intents": { + "Cancel": { + "score": 0.00017013021 + }, + "Delivery": { + "score": 0.00114031672 + }, + "EntityTests": { + "score": 0.286522 + }, + "Greeting": { + "score": 0.000150978623 + }, + "Help": { + "score": 0.000547617 + }, + "None": { + "score": 0.01798658 + }, + "Roles": { + "score": 0.0459664278 + }, + "search": { + "score": 0.0009428267 + }, + "SpecifyName": { + "score": 0.0009960134 + }, + "Travel": { + "score": 0.00235179346 + }, + "Weather.GetForecast": { + "score": 0.6732952 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Weather.GetForecast" + }, + "query": "http://foo.com is where you can get a weather forecast for seattle" + } + } +} diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/V1DatetimeResolution.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/V1DatetimeResolution.json new file mode 100644 index 000000000..eb662c3ff --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/V1DatetimeResolution.json @@ -0,0 +1,19 @@ +{ + "query": "4", + "topScoringIntent": { + "intent": "None", + "score": 0.8575135 + }, + "entities": [ + { + "entity": "4", + "type": "builtin.datetime.time", + "startIndex": 0, + "endIndex": 0, + "resolution": { + "comment": "ampm", + "time": "T04" + } + } + ] +} \ No newline at end of file diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/roles.json b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/roles.json new file mode 100644 index 000000000..adb7c1fa6 --- /dev/null +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/testdata/roles.json @@ -0,0 +1,2252 @@ +{ + "entities": { + "$instance": { + "a": [ + { + "endIndex": 309, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 299, + "text": "68 degrees", + "type": "builtin.temperature" + } + ], + "arrive": [ + { + "endIndex": 373, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 370, + "text": "5pm", + "type": "builtin.datetimeV2.time" + } + ], + "b": [ + { + "endIndex": 324, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 314, + "text": "72 degrees", + "type": "builtin.temperature" + } + ], + "begin": [ + { + "endIndex": 76, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 65, + "text": "6 years old", + "type": "builtin.age" + } + ], + "buy": [ + { + "endIndex": 124, + "modelType": "Regex Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 119, + "text": "kb922", + "type": "Part" + } + ], + "Buyer": [ + { + "endIndex": 178, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 173, + "text": "delta", + "type": "Airline" + } + ], + "datetime": [ + { + "endIndex": 72, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 65, + "text": "6 years", + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 88, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 81, + "text": "8 years", + "type": "builtin.datetimeV2.duration" + } + ], + "destination": [ + { + "endIndex": 233, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "redmond", + "type": "Weather.Location" + } + ], + "dimension": [ + { + "endIndex": 358, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 355, + "text": "3pm", + "type": "builtin.dimension" + }, + { + "endIndex": 373, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 370, + "text": "5pm", + "type": "builtin.dimension" + } + ], + "end": [ + { + "endIndex": 92, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 81, + "text": "8 years old", + "type": "builtin.age" + } + ], + "geographyV2": [ + { + "endIndex": 218, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "hawaii", + "type": "builtin.geographyV2.state" + }, + { + "endIndex": 233, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "redmond", + "type": "builtin.geographyV2.city" + } + ], + "leave": [ + { + "endIndex": 358, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 355, + "text": "3pm", + "type": "builtin.datetimeV2.time" + } + ], + "length": [ + { + "endIndex": 8, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "3 inches", + "type": "builtin.dimension" + } + ], + "likee": [ + { + "endIndex": 344, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 340, + "text": "mary", + "type": "Name" + } + ], + "liker": [ + { + "endIndex": 333, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 329, + "text": "john", + "type": "Name" + } + ], + "max": [ + { + "endIndex": 403, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 399, + "text": "$500", + "type": "builtin.currency" + } + ], + "maximum": [ + { + "endIndex": 44, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "10%", + "type": "builtin.percentage" + } + ], + "min": [ + { + "endIndex": 394, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 390, + "text": "$400", + "type": "builtin.currency" + } + ], + "minimum": [ + { + "endIndex": 37, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 35, + "text": "5%", + "type": "builtin.percentage" + } + ], + "newPhone": [ + { + "endIndex": 164, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 152, + "text": "206-666-4123", + "type": "builtin.phonenumber" + } + ], + "number": [ + { + "endIndex": 1, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "3", + "type": "builtin.number" + }, + { + "endIndex": 18, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "2", + "type": "builtin.number" + }, + { + "endIndex": 36, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 35, + "text": "5", + "type": "builtin.number" + }, + { + "endIndex": 43, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "10", + "type": "builtin.number" + }, + { + "endIndex": 66, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 65, + "text": "6", + "type": "builtin.number" + }, + { + "endIndex": 82, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 81, + "text": "8", + "type": "builtin.number" + }, + { + "endIndex": 139, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 136, + "text": "425", + "type": "builtin.number" + }, + { + "endIndex": 143, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 140, + "text": "777", + "type": "builtin.number" + }, + { + "endIndex": 148, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 144, + "text": "1212", + "type": "builtin.number" + }, + { + "endIndex": 155, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 152, + "text": "206", + "type": "builtin.number" + }, + { + "endIndex": 159, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "666", + "type": "builtin.number" + }, + { + "endIndex": 164, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 160, + "text": "4123", + "type": "builtin.number" + }, + { + "endIndex": 301, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 299, + "text": "68", + "type": "builtin.number" + }, + { + "endIndex": 316, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 314, + "text": "72", + "type": "builtin.number" + }, + { + "endIndex": 394, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 391, + "text": "400", + "type": "builtin.number" + }, + { + "endIndex": 403, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 400, + "text": "500", + "type": "builtin.number" + } + ], + "old": [ + { + "endIndex": 148, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "score": 0.9, + "startIndex": 136, + "text": "425-777-1212", + "type": "builtin.phonenumber" + } + ], + "oldURL": [ + { + "endIndex": 252, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 238, + "text": "http://foo.com", + "type": "builtin.url" + } + ], + "personName": [ + { + "endIndex": 333, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 329, + "text": "john", + "type": "builtin.personName" + }, + { + "endIndex": 344, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 340, + "text": "mary", + "type": "builtin.personName" + } + ], + "receiver": [ + { + "endIndex": 431, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 413, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "sell": [ + { + "endIndex": 114, + "modelType": "Regex Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 109, + "text": "kb457", + "type": "Part" + } + ], + "Seller": [ + { + "endIndex": 189, + "modelType": "List Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 183, + "text": "virgin", + "type": "Airline" + } + ], + "sender": [ + { + "endIndex": 451, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 437, + "text": "emad@gmail.com", + "type": "builtin.email" + } + ], + "source": [ + { + "endIndex": 218, + "modelType": "Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "hawaii", + "type": "Weather.Location" + } + ], + "url": [ + { + "endIndex": 279, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 264, + "text": "http://blah.com", + "type": "builtin.url" + } + ], + "width": [ + { + "endIndex": 25, + "modelType": "Prebuilt Entity Extractor", + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "2 inches", + "type": "builtin.dimension" + } + ] + }, + "a": [ + { + "number": 68, + "units": "Degree" + } + ], + "arrive": [ + { + "timex": [ + "T17" + ], + "type": "time" + } + ], + "b": [ + { + "number": 72, + "units": "Degree" + } + ], + "begin": [ + { + "number": 6, + "units": "Year" + } + ], + "buy": [ + "kb922" + ], + "Buyer": [ + [ + "Delta" + ] + ], + "datetime": [ + { + "timex": [ + "P6Y" + ], + "type": "duration" + }, + { + "timex": [ + "P8Y" + ], + "type": "duration" + } + ], + "destination": [ + "redmond" + ], + "dimension": [ + { + "number": 3, + "units": "Picometer" + }, + { + "number": 5, + "units": "Picometer" + } + ], + "end": [ + { + "number": 8, + "units": "Year" + } + ], + "geographyV2": [ + { + "location": "hawaii", + "type": "state" + }, + { + "location": "redmond", + "type": "city" + } + ], + "leave": [ + { + "timex": [ + "T15" + ], + "type": "time" + } + ], + "length": [ + { + "number": 3, + "units": "Inch" + } + ], + "likee": [ + "mary" + ], + "liker": [ + "john" + ], + "max": [ + { + "number": 500, + "units": "Dollar" + } + ], + "maximum": [ + 10 + ], + "min": [ + { + "number": 400, + "units": "Dollar" + } + ], + "minimum": [ + 5 + ], + "newPhone": [ + "206-666-4123" + ], + "number": [ + 3, + 2, + 5, + 10, + 6, + 8, + 425, + 777, + 1212, + 206, + 666, + 4123, + 68, + 72, + 400, + 500 + ], + "old": [ + "425-777-1212" + ], + "oldURL": [ + "http://foo.com" + ], + "personName": [ + "john", + "mary" + ], + "receiver": [ + "chrimc@hotmail.com" + ], + "sell": [ + "kb457" + ], + "Seller": [ + [ + "Virgin" + ] + ], + "sender": [ + "emad@gmail.com" + ], + "source": [ + "hawaii" + ], + "url": [ + "http://blah.com" + ], + "width": [ + { + "number": 2, + "units": "Inch" + } + ] + }, + "intents": { + "Cancel": { + "score": 4.48137826E-07 + }, + "Delivery": { + "score": 7.920229E-05 + }, + "EntityTests": { + "score": 0.00420103827 + }, + "Greeting": { + "score": 4.6571725E-07 + }, + "Help": { + "score": 7.5179554E-07 + }, + "None": { + "score": 0.0009303715 + }, + "Roles": { + "score": 1.0 + }, + "search": { + "score": 0.00245359377 + }, + "SpecifyName": { + "score": 5.62756977E-05 + }, + "Travel": { + "score": 0.002153493 + }, + "Weather_GetForecast": { + "score": 0.0100878729 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "text": "3 inches long by 2 inches wide and 5% to 10% and are you between 6 years old and 8 years old and can i trade kb457 for kb922 and change 425-777-1212 to 206-666-4123 and did delta buy virgin and did the rain from hawaii get to redmond and http://foo.com changed to http://blah.com and i like between 68 degrees and 72 degrees and john likes mary and leave 3pm and arrive 5pm and pay between $400 and $500 and send chrimc@hotmail.com from emad@gmail.com", + "v2": { + "options": { + "IncludeAllIntents": true, + "IncludeInstanceData": true, + "LogPersonalInformation": false, + "Timeout": 100000.0 + }, + "response": { + "entities": [ + { + "endIndex": 332, + "entity": "john", + "role": "liker", + "score": 0.991758049, + "startIndex": 329, + "type": "Name" + }, + { + "endIndex": 343, + "entity": "mary", + "role": "likee", + "score": 0.995282352, + "startIndex": 340, + "type": "Name" + }, + { + "endIndex": 217, + "entity": "hawaii", + "role": "source", + "score": 0.985036731, + "startIndex": 212, + "type": "Weather.Location" + }, + { + "endIndex": 232, + "entity": "redmond", + "role": "destination", + "score": 0.989005446, + "startIndex": 226, + "type": "Weather.Location" + }, + { + "endIndex": 0, + "entity": "3", + "resolution": { + "subtype": "integer", + "value": "3" + }, + "startIndex": 0, + "type": "builtin.number" + }, + { + "endIndex": 17, + "entity": "2", + "resolution": { + "subtype": "integer", + "value": "2" + }, + "startIndex": 17, + "type": "builtin.number" + }, + { + "endIndex": 35, + "entity": "5", + "resolution": { + "subtype": "integer", + "value": "5" + }, + "startIndex": 35, + "type": "builtin.number" + }, + { + "endIndex": 42, + "entity": "10", + "resolution": { + "subtype": "integer", + "value": "10" + }, + "startIndex": 41, + "type": "builtin.number" + }, + { + "endIndex": 65, + "entity": "6", + "resolution": { + "subtype": "integer", + "value": "6" + }, + "startIndex": 65, + "type": "builtin.number" + }, + { + "endIndex": 81, + "entity": "8", + "resolution": { + "subtype": "integer", + "value": "8" + }, + "startIndex": 81, + "type": "builtin.number" + }, + { + "endIndex": 138, + "entity": "425", + "resolution": { + "subtype": "integer", + "value": "425" + }, + "startIndex": 136, + "type": "builtin.number" + }, + { + "endIndex": 142, + "entity": "777", + "resolution": { + "subtype": "integer", + "value": "777" + }, + "startIndex": 140, + "type": "builtin.number" + }, + { + "endIndex": 147, + "entity": "1212", + "resolution": { + "subtype": "integer", + "value": "1212" + }, + "startIndex": 144, + "type": "builtin.number" + }, + { + "endIndex": 154, + "entity": "206", + "resolution": { + "subtype": "integer", + "value": "206" + }, + "startIndex": 152, + "type": "builtin.number" + }, + { + "endIndex": 158, + "entity": "666", + "resolution": { + "subtype": "integer", + "value": "666" + }, + "startIndex": 156, + "type": "builtin.number" + }, + { + "endIndex": 163, + "entity": "4123", + "resolution": { + "subtype": "integer", + "value": "4123" + }, + "startIndex": 160, + "type": "builtin.number" + }, + { + "endIndex": 300, + "entity": "68", + "resolution": { + "subtype": "integer", + "value": "68" + }, + "startIndex": 299, + "type": "builtin.number" + }, + { + "endIndex": 315, + "entity": "72", + "resolution": { + "subtype": "integer", + "value": "72" + }, + "startIndex": 314, + "type": "builtin.number" + }, + { + "endIndex": 393, + "entity": "400", + "resolution": { + "subtype": "integer", + "value": "400" + }, + "startIndex": 391, + "type": "builtin.number" + }, + { + "endIndex": 402, + "entity": "500", + "resolution": { + "subtype": "integer", + "value": "500" + }, + "startIndex": 400, + "type": "builtin.number" + }, + { + "endIndex": 75, + "entity": "6 years old", + "resolution": { + "unit": "Year", + "value": "6" + }, + "role": "begin", + "startIndex": 65, + "type": "builtin.age" + }, + { + "endIndex": 91, + "entity": "8 years old", + "resolution": { + "unit": "Year", + "value": "8" + }, + "role": "end", + "startIndex": 81, + "type": "builtin.age" + }, + { + "endIndex": 71, + "entity": "6 years", + "resolution": { + "values": [ + { + "timex": "P6Y", + "type": "duration", + "value": "189216000" + } + ] + }, + "startIndex": 65, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 87, + "entity": "8 years", + "resolution": { + "values": [ + { + "timex": "P8Y", + "type": "duration", + "value": "252288000" + } + ] + }, + "startIndex": 81, + "type": "builtin.datetimeV2.duration" + }, + { + "endIndex": 357, + "entity": "3pm", + "resolution": { + "values": [ + { + "timex": "T15", + "type": "time", + "value": "15:00:00" + } + ] + }, + "role": "leave", + "startIndex": 355, + "type": "builtin.datetimeV2.time" + }, + { + "endIndex": 372, + "entity": "5pm", + "resolution": { + "values": [ + { + "timex": "T17", + "type": "time", + "value": "17:00:00" + } + ] + }, + "role": "arrive", + "startIndex": 370, + "type": "builtin.datetimeV2.time" + }, + { + "endIndex": 7, + "entity": "3 inches", + "resolution": { + "unit": "Inch", + "value": "3" + }, + "role": "length", + "startIndex": 0, + "type": "builtin.dimension" + }, + { + "endIndex": 24, + "entity": "2 inches", + "resolution": { + "unit": "Inch", + "value": "2" + }, + "role": "width", + "startIndex": 17, + "type": "builtin.dimension" + }, + { + "endIndex": 357, + "entity": "3pm", + "resolution": { + "unit": "Picometer", + "value": "3" + }, + "startIndex": 355, + "type": "builtin.dimension" + }, + { + "endIndex": 372, + "entity": "5pm", + "resolution": { + "unit": "Picometer", + "value": "5" + }, + "startIndex": 370, + "type": "builtin.dimension" + }, + { + "endIndex": 430, + "entity": "chrimc@hotmail.com", + "resolution": { + "value": "chrimc@hotmail.com" + }, + "role": "receiver", + "startIndex": 413, + "type": "builtin.email" + }, + { + "endIndex": 450, + "entity": "emad@gmail.com", + "resolution": { + "value": "emad@gmail.com" + }, + "role": "sender", + "startIndex": 437, + "type": "builtin.email" + }, + { + "endIndex": 217, + "entity": "hawaii", + "startIndex": 212, + "type": "builtin.geographyV2.state" + }, + { + "endIndex": 232, + "entity": "redmond", + "startIndex": 226, + "type": "builtin.geographyV2.city" + }, + { + "endIndex": 393, + "entity": "$400", + "resolution": { + "unit": "Dollar", + "value": "400" + }, + "role": "min", + "startIndex": 390, + "type": "builtin.currency" + }, + { + "endIndex": 402, + "entity": "$500", + "resolution": { + "unit": "Dollar", + "value": "500" + }, + "role": "max", + "startIndex": 399, + "type": "builtin.currency" + }, + { + "endIndex": 36, + "entity": "5%", + "resolution": { + "value": "5%" + }, + "role": "minimum", + "startIndex": 35, + "type": "builtin.percentage" + }, + { + "endIndex": 43, + "entity": "10%", + "resolution": { + "value": "10%" + }, + "role": "maximum", + "startIndex": 41, + "type": "builtin.percentage" + }, + { + "endIndex": 332, + "entity": "john", + "startIndex": 329, + "type": "builtin.personName" + }, + { + "endIndex": 343, + "entity": "mary", + "startIndex": 340, + "type": "builtin.personName" + }, + { + "endIndex": 147, + "entity": "425-777-1212", + "resolution": { + "score": "0.9", + "value": "425-777-1212" + }, + "role": "old", + "startIndex": 136, + "type": "builtin.phonenumber" + }, + { + "endIndex": 163, + "entity": "206-666-4123", + "resolution": { + "score": "0.9", + "value": "206-666-4123" + }, + "role": "newPhone", + "startIndex": 152, + "type": "builtin.phonenumber" + }, + { + "endIndex": 308, + "entity": "68 degrees", + "resolution": { + "unit": "Degree", + "value": "68" + }, + "role": "a", + "startIndex": 299, + "type": "builtin.temperature" + }, + { + "endIndex": 323, + "entity": "72 degrees", + "resolution": { + "unit": "Degree", + "value": "72" + }, + "role": "b", + "startIndex": 314, + "type": "builtin.temperature" + }, + { + "endIndex": 251, + "entity": "http://foo.com", + "resolution": { + "value": "http://foo.com" + }, + "role": "oldURL", + "startIndex": 238, + "type": "builtin.url" + }, + { + "endIndex": 278, + "entity": "http://blah.com", + "resolution": { + "value": "http://blah.com" + }, + "startIndex": 264, + "type": "builtin.url" + }, + { + "endIndex": 177, + "entity": "delta", + "resolution": { + "values": [ + "Delta" + ] + }, + "role": "Buyer", + "startIndex": 173, + "type": "Airline" + }, + { + "endIndex": 188, + "entity": "virgin", + "resolution": { + "values": [ + "Virgin" + ] + }, + "role": "Seller", + "startIndex": 183, + "type": "Airline" + }, + { + "endIndex": 113, + "entity": "kb457", + "role": "sell", + "startIndex": 109, + "type": "Part" + }, + { + "endIndex": 123, + "entity": "kb922", + "role": "buy", + "startIndex": 119, + "type": "Part" + } + ], + "intents": [ + { + "intent": "Roles", + "score": 1.0 + }, + { + "intent": "Weather.GetForecast", + "score": 0.0100878729 + }, + { + "intent": "EntityTests", + "score": 0.00420103827 + }, + { + "intent": "search", + "score": 0.00245359377 + }, + { + "intent": "Travel", + "score": 0.002153493 + }, + { + "intent": "None", + "score": 0.0009303715 + }, + { + "intent": "Delivery", + "score": 7.920229E-05 + }, + { + "intent": "SpecifyName", + "score": 5.62756977E-05 + }, + { + "intent": "Help", + "score": 7.5179554E-07 + }, + { + "intent": "Greeting", + "score": 4.6571725E-07 + }, + { + "intent": "Cancel", + "score": 4.48137826E-07 + } + ], + "query": "3 inches long by 2 inches wide and 5% to 10% and are you between 6 years old and 8 years old and can i trade kb457 for kb922 and change 425-777-1212 to 206-666-4123 and did delta buy virgin and did the rain from hawaii get to redmond and http://foo.com changed to http://blah.com and i like between 68 degrees and 72 degrees and john likes mary and leave 3pm and arrive 5pm and pay between $400 and $500 and send chrimc@hotmail.com from emad@gmail.com", + "sentimentAnalysis": { + "label": "neutral", + "score": 0.5 + }, + "topScoringIntent": { + "intent": "Roles", + "score": 1.0 + } + } + }, + "v3": { + "options": { + "IncludeAllIntents": true, + "IncludeAPIResults": true, + "IncludeInstanceData": true, + "Log": true, + "PreferExternalEntities": true, + "Slot": "production" + }, + "response": { + "prediction": { + "entities": { + "$instance": { + "a": [ + { + "length": 10, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "a", + "startIndex": 299, + "text": "68 degrees", + "type": "builtin.temperature" + } + ], + "arrive": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "arrive", + "startIndex": 370, + "text": "5pm", + "type": "builtin.datetimeV2.time" + } + ], + "b": [ + { + "length": 10, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "b", + "startIndex": 314, + "text": "72 degrees", + "type": "builtin.temperature" + } + ], + "begin": [ + { + "length": 11, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "begin", + "startIndex": 65, + "text": "6 years old", + "type": "builtin.age" + } + ], + "buy": [ + { + "length": 5, + "modelType": "Regex Entity Extractor", + "modelTypeId": 8, + "recognitionSources": [ + "model" + ], + "role": "buy", + "startIndex": 119, + "text": "kb922", + "type": "Part" + } + ], + "Buyer": [ + { + "length": 5, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "model" + ], + "role": "Buyer", + "startIndex": 173, + "text": "delta", + "type": "Airline" + } + ], + "datetimeV2": [ + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 65, + "text": "6 years", + "type": "builtin.datetimeV2.duration" + }, + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 81, + "text": "8 years", + "type": "builtin.datetimeV2.duration" + } + ], + "destination": [ + { + "length": 7, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "role": "destination", + "startIndex": 226, + "text": "redmond", + "type": "Weather.Location" + } + ], + "dimension": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 355, + "text": "3pm", + "type": "builtin.dimension" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 370, + "text": "5pm", + "type": "builtin.dimension" + } + ], + "end": [ + { + "length": 11, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "end", + "startIndex": 81, + "text": "8 years old", + "type": "builtin.age" + } + ], + "geographyV2": [ + { + "length": 6, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 212, + "text": "hawaii", + "type": "builtin.geographyV2.state" + }, + { + "length": 7, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 226, + "text": "redmond", + "type": "builtin.geographyV2.city" + } + ], + "leave": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "leave", + "startIndex": 355, + "text": "3pm", + "type": "builtin.datetimeV2.time" + } + ], + "length": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "length", + "startIndex": 0, + "text": "3 inches", + "type": "builtin.dimension" + } + ], + "likee": [ + { + "length": 4, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "role": "likee", + "startIndex": 340, + "text": "mary", + "type": "Name" + } + ], + "liker": [ + { + "length": 4, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "role": "liker", + "startIndex": 329, + "text": "john", + "type": "Name" + } + ], + "max": [ + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "max", + "startIndex": 399, + "text": "$500", + "type": "builtin.currency" + } + ], + "maximum": [ + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "maximum", + "startIndex": 41, + "text": "10%", + "type": "builtin.percentage" + } + ], + "min": [ + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "min", + "startIndex": 390, + "text": "$400", + "type": "builtin.currency" + } + ], + "minimum": [ + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "minimum", + "startIndex": 35, + "text": "5%", + "type": "builtin.percentage" + } + ], + "newPhone": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "newPhone", + "score": 0.9, + "startIndex": 152, + "text": "206-666-4123", + "type": "builtin.phonenumber" + } + ], + "number": [ + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 0, + "text": "3", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 17, + "text": "2", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 35, + "text": "5", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 41, + "text": "10", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 65, + "text": "6", + "type": "builtin.number" + }, + { + "length": 1, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 81, + "text": "8", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 136, + "text": "425", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 140, + "text": "777", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 144, + "text": "1212", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 152, + "text": "206", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 156, + "text": "666", + "type": "builtin.number" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 160, + "text": "4123", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 299, + "text": "68", + "type": "builtin.number" + }, + { + "length": 2, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 314, + "text": "72", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 391, + "text": "400", + "type": "builtin.number" + }, + { + "length": 3, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 400, + "text": "500", + "type": "builtin.number" + } + ], + "old": [ + { + "length": 12, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "old", + "score": 0.9, + "startIndex": 136, + "text": "425-777-1212", + "type": "builtin.phonenumber" + } + ], + "oldURL": [ + { + "length": 14, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "oldURL", + "startIndex": 238, + "text": "http://foo.com", + "type": "builtin.url" + } + ], + "personName": [ + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 329, + "text": "john", + "type": "builtin.personName" + }, + { + "length": 4, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 340, + "text": "mary", + "type": "builtin.personName" + } + ], + "receiver": [ + { + "length": 18, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "receiver", + "startIndex": 413, + "text": "chrimc@hotmail.com", + "type": "builtin.email" + } + ], + "sell": [ + { + "length": 5, + "modelType": "Regex Entity Extractor", + "modelTypeId": 8, + "recognitionSources": [ + "model" + ], + "role": "sell", + "startIndex": 109, + "text": "kb457", + "type": "Part" + } + ], + "Seller": [ + { + "length": 6, + "modelType": "List Entity Extractor", + "modelTypeId": 5, + "recognitionSources": [ + "model" + ], + "role": "Seller", + "startIndex": 183, + "text": "virgin", + "type": "Airline" + } + ], + "sender": [ + { + "length": 14, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "sender", + "startIndex": 437, + "text": "emad@gmail.com", + "type": "builtin.email" + } + ], + "source": [ + { + "length": 6, + "modelType": "Entity Extractor", + "modelTypeId": 1, + "recognitionSources": [ + "model" + ], + "role": "source", + "startIndex": 212, + "text": "hawaii", + "type": "Weather.Location" + } + ], + "url": [ + { + "length": 15, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "startIndex": 264, + "text": "http://blah.com", + "type": "builtin.url" + } + ], + "width": [ + { + "length": 8, + "modelType": "Prebuilt Entity Extractor", + "modelTypeId": 2, + "recognitionSources": [ + "model" + ], + "role": "width", + "startIndex": 17, + "text": "2 inches", + "type": "builtin.dimension" + } + ] + }, + "a": [ + { + "number": 68, + "units": "Degree" + } + ], + "arrive": [ + { + "type": "time", + "values": [ + { + "resolution": [ + { + "value": "17:00:00" + } + ], + "timex": "T17" + } + ] + } + ], + "b": [ + { + "number": 72, + "units": "Degree" + } + ], + "begin": [ + { + "number": 6, + "units": "Year" + } + ], + "buy": [ + "kb922" + ], + "Buyer": [ + [ + "Delta" + ] + ], + "datetimeV2": [ + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "189216000" + } + ], + "timex": "P6Y" + } + ] + }, + { + "type": "duration", + "values": [ + { + "resolution": [ + { + "value": "252288000" + } + ], + "timex": "P8Y" + } + ] + } + ], + "destination": [ + "redmond" + ], + "dimension": [ + { + "number": 3, + "units": "Picometer" + }, + { + "number": 5, + "units": "Picometer" + } + ], + "end": [ + { + "number": 8, + "units": "Year" + } + ], + "geographyV2": [ + { + "type": "state", + "value": "hawaii" + }, + { + "type": "city", + "value": "redmond" + } + ], + "leave": [ + { + "type": "time", + "values": [ + { + "resolution": [ + { + "value": "15:00:00" + } + ], + "timex": "T15" + } + ] + } + ], + "length": [ + { + "number": 3, + "units": "Inch" + } + ], + "likee": [ + "mary" + ], + "liker": [ + "john" + ], + "max": [ + { + "number": 500, + "units": "Dollar" + } + ], + "maximum": [ + 10 + ], + "min": [ + { + "number": 400, + "units": "Dollar" + } + ], + "minimum": [ + 5 + ], + "newPhone": [ + "206-666-4123" + ], + "number": [ + 3, + 2, + 5, + 10, + 6, + 8, + 425, + 777, + 1212, + 206, + 666, + 4123, + 68, + 72, + 400, + 500 + ], + "old": [ + "425-777-1212" + ], + "oldURL": [ + "http://foo.com" + ], + "personName": [ + "john", + "mary" + ], + "receiver": [ + "chrimc@hotmail.com" + ], + "sell": [ + "kb457" + ], + "Seller": [ + [ + "Virgin" + ] + ], + "sender": [ + "emad@gmail.com" + ], + "source": [ + "hawaii" + ], + "url": [ + "http://blah.com" + ], + "width": [ + { + "number": 2, + "units": "Inch" + } + ] + }, + "intents": { + "Cancel": { + "score": 4.48137826E-07 + }, + "Delivery": { + "score": 7.920229E-05 + }, + "EntityTests": { + "score": 0.00420103827 + }, + "Greeting": { + "score": 4.6571725E-07 + }, + "Help": { + "score": 7.5179554E-07 + }, + "None": { + "score": 0.0009303715 + }, + "Roles": { + "score": 1.0 + }, + "search": { + "score": 0.00245359377 + }, + "SpecifyName": { + "score": 5.62756977E-05 + }, + "Travel": { + "score": 0.002153493 + }, + "Weather.GetForecast": { + "score": 0.0100878729 + } + }, + "sentiment": { + "label": "neutral", + "score": 0.5 + }, + "topIntent": "Roles" + }, + "query": "3 inches long by 2 inches wide and 5% to 10% and are you between 6 years old and 8 years old and can i trade kb457 for kb922 and change 425-777-1212 to 206-666-4123 and did delta buy virgin and did the rain from hawaii get to redmond and http://foo.com changed to http://blah.com and i like between 68 degrees and 72 degrees and john likes mary and leave 3pm and arrive 5pm and pay between $400 and $500 and send chrimc@hotmail.com from emad@gmail.com" + } + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java index b0579d519..dbc459f98 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/RecognizerResult.java @@ -33,6 +33,18 @@ public class RecognizerResult implements RecognizerConvert { */ private HashMap properties = new HashMap<>(); + /** + * Holds intent score info. + */ + @SuppressWarnings({ "checkstyle:VisibilityModifier" }) + public static class NamedIntentScore { + /// The intent name + public String intent; + + /// The intent score + public double score; + } + /** * Return the top scoring intent and its score. * @@ -40,16 +52,17 @@ public class RecognizerResult implements RecognizerConvert { * @throws IllegalArgumentException No intents available. */ @JsonIgnore - public IntentScore getTopScoringIntent() throws IllegalArgumentException { + public NamedIntentScore getTopScoringIntent() throws IllegalArgumentException { if (getIntents() == null) { throw new IllegalArgumentException("RecognizerResult.Intents cannot be null"); } - IntentScore topIntent = new IntentScore(); + NamedIntentScore topIntent = new NamedIntentScore(); for (Map.Entry intent : getIntents().entrySet()) { double score = intent.getValue().getScore(); - if (score > topIntent.getScore()) { - topIntent = intent.getValue(); + if (score > topIntent.score) { + topIntent.intent = intent.getKey(); + topIntent.score = intent.getValue().getScore(); } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java new file mode 100644 index 000000000..1861eb224 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java @@ -0,0 +1,272 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.IntentScore; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.RecognizerConvert; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Serialization; +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Recognizer base class. + * + *

Recognizers operate in a DialogContext environment to recognize user input into Intents + * and Entities.

+ * + *

+ * This class models 3 virtual methods around + * * Pure DialogContext (where the recognition happens against current state dialogcontext + * * Activity (where the recognition is from an Activity) + * * Text/Locale (where the recognition is from text/locale) + *

+ * + *

+ * The default implementation of DialogContext method is to use Context.Activity and call the + * activity method. + * The default implementation of Activity method is to filter to Message activities and pull + * out text/locale and call the text/locale method. + *

+ */ +public class Recognizer { + /** + * Intent name that will be produced by this recognizer if the child recognizers do not + * have consensus for intents. + */ + public static final String CHOOSE_INTENT = "ChooseIntent"; + + /** + * Standard none intent that means none of the recognizers recognize the intent. If each + * recognizer returns no intents or None intents, then this recognizer will return None intent. + */ + public static final String NONE_INTENT = "None"; + + @JsonProperty(value = "id") + private String id; + + @JsonIgnore + private BotTelemetryClient telemetryClient = new NullBotTelemetryClient(); + + /** + * Initializes a new Recognizer. + */ + public Recognizer() { + } + + /** + * Runs current DialogContext.TurnContext.Activity through a recognizer and returns a + * generic recognizer result. + * + * @param dialogContext Dialog Context. + * @param activity activity to recognize. + * @return Analysis of utterance. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity + ) { + return recognize(dialogContext, activity, null, null); + } + + /** + * Runs current DialogContext.TurnContext.Activity through a recognizer and returns a + * generic recognizer result. + * + * @param dialogContext Dialog Context. + * @param activity activity to recognize. + * @param telemetryProperties The properties to be included as part of the event tracking. + * @param telemetryMetrics The metrics to be included as part of the event tracking. + * @return Analysis of utterance. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + Map telemetryProperties, + Map telemetryMetrics + ) { + return Async.completeExceptionally(new NotImplementedException("recognize")); + } + + /** + * Runs current DialogContext.TurnContext.Activity through a recognizer and returns a + * strongly-typed recognizer result using RecognizerConvert. + * + * @param dialogContext Dialog Context. + * @param activity activity to recognize. + * @param telemetryProperties The properties to be included as part of the event tracking. + * @param telemetryMetrics The metrics to be included as part of the event tracking. + * @param c Class of type T. + * @param The RecognizerConvert. + * @return Analysis of utterance. + */ + public CompletableFuture recognize( + DialogContext dialogContext, + Activity activity, + Map telemetryProperties, + Map telemetryMetrics, + Class c + ) { + return Async.tryCompletable(() -> { + T result = c.newInstance(); + return recognize(dialogContext, activity, telemetryProperties, telemetryMetrics) + .thenApply(recognizerResult -> { + result.convert(recognizerResult); + return result; + }); + }); + } + + /** + * Returns ChooseIntent between multiple recognizer results. + * + * @param recognizerResults >recognizer Id => recognizer results map. + * @return recognizerResult which is ChooseIntent. + */ + protected static RecognizerResult createChooseIntentResult(Map recognizerResults) { + String text = null; + List candidates = new ArrayList<>(); + + for (Map.Entry recognizerResult : recognizerResults.entrySet()) { + text = recognizerResult.getValue().getText(); + RecognizerResult.NamedIntentScore top = recognizerResult.getValue().getTopScoringIntent(); + if (!StringUtils.equals(top.intent, NONE_INTENT)) { + ObjectNode candidate = Serialization.createObjectNode(); + candidate.put("id", recognizerResult.getKey()); + candidate.put("intent", top.intent); + candidate.put("score", top.score); + candidate.put("result", Serialization.objectToTree(recognizerResult.getValue())); + candidates.add(candidate); + } + } + + RecognizerResult result = new RecognizerResult(); + Map intents = new HashMap<>(); + + if (!candidates.isEmpty()) { + // return ChooseIntent with candidates array + intents.put(CHOOSE_INTENT, new IntentScore() {{ setScore(1.0); }}); + + result.setText(text); + result.setIntents(intents); + result.setProperties("candidates", Serialization.objectToTree(candidates)); + } else { + // just return a none intent + intents.put(NONE_INTENT, new IntentScore() {{ setScore(1.0); }}); + + result.setText(text); + result.setIntents(intents); + } + + return result; + } + + /** + * Gets id of the recognizer. + * @return id of the recognizer + */ + public String getId() { + return id; + } + + /** + * Sets id of the recognizer. + * @param withId id of the recognizer + */ + public void setId(String withId) { + id = withId; + } + + /** + * Gets the currently configured BotTelemetryClient that logs the RecognizerResult event. + * @return BotTelemetryClient + */ + public BotTelemetryClient getTelemetryClient() { + return telemetryClient; + } + + /** + * Sets the currently configured BotTelemetryClient that logs the RecognizerResult event. + * @param withTelemetryClient BotTelemetryClient + */ + public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { + telemetryClient = withTelemetryClient; + } + + /** + * Uses the RecognizerResult to create a list of propeties to be included when tracking the + * result in telemetry. + * + * @param recognizerResult Recognizer Result. + * @param telemetryProperties A list of properties to append or override the properties + * created using the RecognizerResult. + * @param dialogContext >Dialog Context. + * @return A dictionary that can be included when calling the TrackEvent method on the + * TelemetryClient. + */ + protected Map fillRecognizerResultTelemetryProperties( + RecognizerResult recognizerResult, + Map telemetryProperties, + DialogContext dialogContext + ) { + Map properties = new HashMap<>(); + properties.put("Text", recognizerResult.getText()); + properties.put("AlteredText", recognizerResult.getAlteredText()); + properties.put("TopIntent", !recognizerResult.getIntents().isEmpty() + ? recognizerResult.getTopScoringIntent().intent : null); + properties.put("TopIntentScore", !recognizerResult.getIntents().isEmpty() + ? Double.toString(recognizerResult.getTopScoringIntent().score) : null); + properties.put("Intents", !recognizerResult.getIntents().isEmpty() + ? Serialization.toStringSilent(recognizerResult.getIntents()) : null); + properties.put("Entities", recognizerResult.getEntities() != null + ? Serialization.toStringSilent(recognizerResult.getEntities()) : null); + properties.put("AdditionalProperties", !recognizerResult.getProperties().isEmpty() + ? Serialization.toStringSilent(recognizerResult.getProperties()) : null); + + // Additional Properties can override "stock" properties. + if (telemetryProperties != null) { + properties.putAll(telemetryProperties); + } + + return properties; + } + + /** + * Tracks an event with the event name provided using the TelemetryClient attaching the + * properties / metrics. + * + * @param dialogContext Dialog Context. + * @param eventName The name of the event to track. + * @param telemetryProperties The properties to be included as part of the event tracking. + * @param telemetryMetrics The metrics to be included as part of the event tracking. + */ + protected void trackRecognizerResult( + DialogContext dialogContext, + String eventName, + Map telemetryProperties, + Map telemetryMetrics + ) { + if (telemetryClient instanceof NullBotTelemetryClient) { + BotTelemetryClient turnStateTelemetryClient = dialogContext.getContext() + .getTurnState().get(BotTelemetryClient.class); + telemetryClient = turnStateTelemetryClient != null ? turnStateTelemetryClient : telemetryClient; + } + + telemetryClient.trackEvent(eventName, telemetryProperties, telemetryMetrics); + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java index 73f9466ec..53daaf4c8 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; @@ -153,6 +154,21 @@ public static String toString(Object source) throws JsonProcessingException { return objectMapper.writeValueAsString(source); } + /** + * Convert an object to a JSON string. + * + * @param source The object to convert. + * @return The JSON string value. + * @throws JsonProcessingException Error converting to JSON + */ + public static String toStringSilent(Object source) { + try { + return objectMapper.writeValueAsString(source); + } catch (Throwable t) { + return null; + } + } + /** * Parses a JSON document. * @@ -235,5 +251,13 @@ public static JsonNode asNode(boolean b) { public static JsonNode asNode(byte b) { return objectMapper.getNodeFactory().numberNode(b); } + + /** + * Creates an ObjectNode. + * @return ObjectNode. + */ + public static ObjectNode createObjectNode() { + return objectMapper.createObjectNode(); + } } From af273cb6359c6c86a65dd28ef3c58e63c1b92b0e Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 11 Feb 2021 17:46:14 -0600 Subject: [PATCH 069/221] Implemented remaining SkillValidation methods and tests (#972) --- libraries/bot-connector/pom.xml | 4 + .../com/microsoft/bot/connector/Async.java | 18 --- .../AuthenticationConstants.java | 5 + .../authentication/ClaimsIdentity.java | 27 +++- .../authentication/JwtTokenValidation.java | 30 ++++ .../authentication/SkillValidation.java | 103 ++++++++++++- .../microsoft/bot/connector/AsyncTests.java | 38 +++++ .../bot/connector/SkillValidationTests.java | 139 ++++++++++++++++++ 8 files changed, 343 insertions(+), 21 deletions(-) create mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AsyncTests.java create mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/connector/SkillValidationTests.java diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index 70b3c3eb4..60bbdeab3 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -49,6 +49,10 @@ junit junit + + org.mockito + mockito-core + diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java index a343cd9b4..9ec3f5c0f 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Async.java @@ -4,7 +4,6 @@ package com.microsoft.bot.connector; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; /** * Asyc and CompletableFuture helpers methods. @@ -14,23 +13,6 @@ private Async() { } - /** - * Executes a block and throws a completion exception if needed. - * - * @param supplier The block to execute. - * @param The type of the return value. - * @return The return value. - */ - public static T tryThrow(ThrowSupplier supplier) { - try { - return supplier.get(); - } catch (CompletionException ce) { - throw ce; - } catch (Throwable t) { - throw new CompletionException(t); - } - } - /** * Executes a block and returns a CompletableFuture with either the return * value or the exception (completeExceptionally). diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java index 64da23a36..3d9cbfb9b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConstants.java @@ -146,6 +146,11 @@ private AuthenticationConstants() { */ public static final String ANONYMOUS_SKILL_APPID = "AnonymousSkill"; + /** + * Indicates anonymous (no app Id and password were provided). + */ + public static final String ANONYMOUS_AUTH_TYPE = "anonymous"; + /** * The default clock skew in minutes. */ diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsIdentity.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsIdentity.java index d6db4b4bd..00c88e90c 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsIdentity.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsIdentity.java @@ -13,6 +13,7 @@ */ public class ClaimsIdentity { private String issuer; + private String type; private Map claims; private ClaimsIdentity() { @@ -35,8 +36,20 @@ public ClaimsIdentity(String withAuthIssuer) { * @param withClaims A Map of claims. */ public ClaimsIdentity(String withAuthIssuer, Map withClaims) { - this.issuer = withAuthIssuer; - this.claims = withClaims; + this(withAuthIssuer, null, withClaims); + } + + /** + * Manually construct with issuer and claims. + * + * @param withAuthIssuer The auth issuer. + * @param withType The auth type. + * @param withClaims A Map of claims. + */ + public ClaimsIdentity(String withAuthIssuer, String withType, Map withClaims) { + issuer = withAuthIssuer; + type = withType; + claims = withClaims; } /** @@ -50,6 +63,7 @@ public ClaimsIdentity(DecodedJWT jwt) { jwt.getClaims().forEach((k, v) -> claims.put(k, v.asString())); } issuer = jwt.getIssuer(); + type = jwt.getType(); } /** @@ -78,4 +92,13 @@ public Map claims() { public String getIssuer() { return issuer; } + + /** + * The type. + * + * @return The type. + */ + public String getType() { + return type; + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java index 71f52d511..4d6398b78 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java @@ -202,4 +202,34 @@ public static String getAppIdFromClaims(Map claims) throws Illeg return appId; } + + /** + * Internal helper to check if the token has the shape we expect "Bearer [big long string]". + * + * @param authHeader >A string containing the token header. + * @return True if the token is valid, false if not. + */ + public static boolean isValidTokenFormat(String authHeader) { + if (StringUtils.isEmpty(authHeader)) { + // No token, not valid. + return false; + } + + String[] parts = authHeader.split(" "); + if (parts.length != 2) { + // Tokens MUST have exactly 2 parts. If we don't have 2 parts, it's not a valid token + return false; + } + + // We now have an array that should be: + // [0] = "Bearer" + // [1] = "[Big Long String]" + String authScheme = parts[0]; + if (!StringUtils.equals(authScheme, "Bearer")) { + // The scheme MUST be "Bearer" + return false; + } + + return true; + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java index 813fba475..33891fe5a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java @@ -3,9 +3,13 @@ package com.microsoft.bot.connector.authentication; +import com.auth0.jwt.JWT; +import com.microsoft.bot.connector.Async; import java.time.Duration; +import java.util.HashMap; import java.util.Map; import java.util.Optional; +import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -97,7 +101,104 @@ public static Boolean isSkillClaim(Map claims) { // Skill claims must contain and app ID and the AppID must be different than the // audience. - return appId != audience.get().getValue(); + return !StringUtils.equals(appId, audience.get().getValue()); } + /** + * Determines if a given Auth header is from from a skill to bot or bot to skill request. + * + * @param authHeader Bearer Token, in the "Bearer [Long String]" Format. + * @return True, if the token was issued for a skill to bot communication. Otherwise, false. + */ + public static boolean isSkillToken(String authHeader) { + if (!JwtTokenValidation.isValidTokenFormat(authHeader)) { + return false; + } + + // We know is a valid token, split it and work with it: + // [0] = "Bearer" + // [1] = "[Big Long String]" + String bearerToken = authHeader.split(" ")[1]; + + // Parse token + ClaimsIdentity identity = new ClaimsIdentity(JWT.decode(bearerToken)); + + return isSkillClaim(identity.claims()); + } + + /** + * Helper to validate a skills ClaimsIdentity. + * + * @param identity The ClaimsIdentity to validate. + * @param credentials The CredentialProvider. + * @return Nothing if success, otherwise a CompletionException + */ + public static CompletableFuture validateIdentity( + ClaimsIdentity identity, + CredentialProvider credentials + ) { + if (identity == null) { + // No valid identity. Not Authorized. + return Async.completeExceptionally(new AuthenticationException("Invalid Identity")); + } + + if (!identity.isAuthenticated()) { + // The token is in some way invalid. Not Authorized. + return Async.completeExceptionally(new AuthenticationException("Token Not Authenticated")); + } + + Optional> versionClaim = identity.claims().entrySet().stream() + .filter(item -> StringUtils.equals(AuthenticationConstants.VERSION_CLAIM, item.getKey())) + .findFirst(); + if (!versionClaim.isPresent()) { + // No version claim + return Async.completeExceptionally( + new AuthenticationException( + AuthenticationConstants.VERSION_CLAIM + " claim is required on skill Tokens." + ) + ); + } + + // Look for the "aud" claim, but only if issued from the Bot Framework + Optional> audienceClaim = identity.claims().entrySet().stream() + .filter(item -> StringUtils.equals(AuthenticationConstants.AUDIENCE_CLAIM, item.getKey())) + .findFirst(); + if (!audienceClaim.isPresent() || StringUtils.isEmpty(audienceClaim.get().getValue())) { + // Claim is not present or doesn't have a value. Not Authorized. + return Async.completeExceptionally( + new AuthenticationException( + AuthenticationConstants.AUDIENCE_CLAIM + " claim is required on skill Tokens." + ) + ); + } + + String appId = JwtTokenValidation.getAppIdFromClaims(identity.claims()); + if (StringUtils.isEmpty(appId)) { + return Async.completeExceptionally(new AuthenticationException("Invalid appId.")); + } + + return credentials.isValidAppId(audienceClaim.get().getValue()) + .thenApply(isValid -> { + if (!isValid) { + throw new AuthenticationException("Invalid audience."); + } + return null; + }); + } + + /** + * Creates a ClaimsIdentity for an anonymous (unauthenticated) skill. + * + * @return A ClaimsIdentity instance with authentication type set to + * AuthenticationConstants.AnonymousAuthType and a reserved + * AuthenticationConstants.AnonymousSkillAppId claim. + */ + public static ClaimsIdentity createAnonymousSkillClaim() { + Map claims = new HashMap<>(); + claims.put(AuthenticationConstants.APPID_CLAIM, AuthenticationConstants.ANONYMOUS_SKILL_APPID); + return new ClaimsIdentity( + AuthenticationConstants.ANONYMOUS_AUTH_TYPE, + AuthenticationConstants.ANONYMOUS_AUTH_TYPE, + claims); + } } diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AsyncTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AsyncTests.java new file mode 100644 index 000000000..bd6757831 --- /dev/null +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AsyncTests.java @@ -0,0 +1,38 @@ +package com.microsoft.bot.connector; + +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import org.junit.Assert; +import org.junit.Test; + +public class AsyncTests { + @Test() + public void AsyncTryCompletionShouldCompleteExceptionally() { + CompletableFuture result = Async.tryCompletable(() -> { + throw new IllegalArgumentException("test"); + }); + + Assert.assertTrue(result.isCompletedExceptionally()); + } + + @Test + public void AsyncTryCompletionShouldComplete() { + CompletableFuture result = Async.tryCompletable(() -> CompletableFuture.completedFuture(true)); + Assert.assertTrue(result.join()); + } + + @Test + public void AsyncWrapBlockShouldCompleteExceptionally() { + CompletableFuture result = Async.wrapBlock(() -> { + throw new IllegalArgumentException("test"); + }); + + Assert.assertTrue(result.isCompletedExceptionally()); + } + + @Test + public void AsyncWrapBlockShouldComplete() { + CompletableFuture result = Async.wrapBlock(() -> true); + Assert.assertTrue(result.join()); + } +} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/SkillValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/SkillValidationTests.java new file mode 100644 index 000000000..a04d4dad3 --- /dev/null +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/SkillValidationTests.java @@ -0,0 +1,139 @@ +package com.microsoft.bot.connector; + +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import static org.mockito.Mockito.*; + +@RunWith(MockitoJUnitRunner.class) +public class SkillValidationTests { + @Test + public void IsSkillClaimTest() { + Map claims = new HashMap<>(); + String audience = UUID.randomUUID().toString(); + String appId = UUID.randomUUID().toString(); + + // Empty list of claims + Assert.assertFalse(SkillValidation.isSkillClaim(claims)); + + // No Audience claim + claims.put(AuthenticationConstants.VERSION_CLAIM, "1.0"); + Assert.assertFalse(SkillValidation.isSkillClaim(claims)); + + // Emulator Audience claim + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); + Assert.assertFalse(SkillValidation.isSkillClaim(claims)); + + // No AppId claim + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, audience); + Assert.assertFalse(SkillValidation.isSkillClaim(claims)); + + // AppId != Audience + claims.put(AuthenticationConstants.APPID_CLAIM, audience); + Assert.assertFalse(SkillValidation.isSkillClaim(claims)); + + // Anonymous skill app id + claims.put(AuthenticationConstants.APPID_CLAIM, AuthenticationConstants.ANONYMOUS_SKILL_APPID); + Assert.assertTrue(SkillValidation.isSkillClaim(claims)); + + // All checks pass, should be good now + claims.put(AuthenticationConstants.APPID_CLAIM, appId); + Assert.assertTrue(SkillValidation.isSkillClaim(claims)); + } + + @Test + public void IsSkillTokenTest() { + Assert.assertEquals("Null String", false, SkillValidation.isSkillToken(null)); + Assert.assertEquals("Empty String", false, SkillValidation.isSkillToken("")); + Assert.assertEquals("No token Part", false, SkillValidation.isSkillToken("Bearer")); + Assert.assertEquals("No bearer part", false, SkillValidation.isSkillToken("ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImtpZCI6ICJKVzNFWGRudy13WTJFcUxyV1RxUTJyVWtCLWciLA0KICAieDV0IjogIkpXM0VYZG53LXdZMkVxTHJXVHFRMnJVa0ItZyIsDQogICJ0eXAiOiAiSldUIg0KfQ.ew0KICAic2VydmljZXVybCI6ICJodHRwczovL2RpcmVjdGxpbmUuYm90ZnJhbWV3b3JrLmNvbS8iLA0KICAibmJmIjogMTU3MTE5MDM0OCwNCiAgImV4cCI6IDE1NzExOTA5NDgsDQogICJpc3MiOiAiaHR0cHM6Ly9hcGkuYm90ZnJhbWV3b3JrLmNvbSIsDQogICJhdWQiOiAiNGMwMDM5ZTUtNjgxNi00OGU4LWIzMTMtZjc3NjkxZmYxYzVlIg0KfQ.cEVHmQCTjL9HVHGk91sja5CqjgvM7B-nArkOg4bE83m762S_le94--GBb0_7aAy6DCdvkZP0d4yWwbpfOkukEXixCDZQM2kWPcOo6lz_VIuXxHFlZAGrTvJ1QkBsg7vk-6_HR8XSLJQZoWrVhE-E_dPj4GPBKE6s1aNxYytzazbKRAEYa8Cn4iVtuYbuj4XfH8PMDv5aC0APNvfgTGk-BlIiP6AGdo4JYs62lUZVSAYg5VLdBcJYMYcKt-h2n1saeapFDVHx_tdpRuke42M4RpGH_wzICeWC5tTExWEkQWApU85HRA5zzk4OpTv17Ct13JCvQ7cD5x9RK5f7CMnbhQ")); + Assert.assertEquals("Invalid scheme", false, SkillValidation.isSkillToken("Potato ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImtpZCI6ICJKVzNFWGRudy13WTJFcUxyV1RxUTJyVWtCLWciLA0KICAieDV0IjogIkpXM0VYZG53LXdZMkVxTHJXVHFRMnJVa0ItZyIsDQogICJ0eXAiOiAiSldUIg0KfQ.ew0KICAic2VydmljZXVybCI6ICJodHRwczovL2RpcmVjdGxpbmUuYm90ZnJhbWV3b3JrLmNvbS8iLA0KICAibmJmIjogMTU3MTE5MDM0OCwNCiAgImV4cCI6IDE1NzExOTA5NDgsDQogICJpc3MiOiAiaHR0cHM6Ly9hcGkuYm90ZnJhbWV3b3JrLmNvbSIsDQogICJhdWQiOiAiNGMwMDM5ZTUtNjgxNi00OGU4LWIzMTMtZjc3NjkxZmYxYzVlIg0KfQ.cEVHmQCTjL9HVHGk91sja5CqjgvM7B-nArkOg4bE83m762S_le94--GBb0_7aAy6DCdvkZP0d4yWwbpfOkukEXixCDZQM2kWPcOo6lz_VIuXxHFlZAGrTvJ1QkBsg7vk-6_HR8XSLJQZoWrVhE-E_dPj4GPBKE6s1aNxYytzazbKRAEYa8Cn4iVtuYbuj4XfH8PMDv5aC0APNvfgTGk-BlIiP6AGdo4JYs62lUZVSAYg5VLdBcJYMYcKt-h2n1saeapFDVHx_tdpRuke42M4RpGH_wzICeWC5tTExWEkQWApU85HRA5zzk4OpTv17Ct13JCvQ7cD5x9RK5f7CMnbhQ")); + Assert.assertEquals("To bot v2 from webchat", false, SkillValidation.isSkillToken("Bearer ew0KICAiYWxnIjogIlJTMjU2IiwNCiAgImtpZCI6ICJKVzNFWGRudy13WTJFcUxyV1RxUTJyVWtCLWciLA0KICAieDV0IjogIkpXM0VYZG53LXdZMkVxTHJXVHFRMnJVa0ItZyIsDQogICJ0eXAiOiAiSldUIg0KfQ.ew0KICAic2VydmljZXVybCI6ICJodHRwczovL2RpcmVjdGxpbmUuYm90ZnJhbWV3b3JrLmNvbS8iLA0KICAibmJmIjogMTU3MTE5MDM0OCwNCiAgImV4cCI6IDE1NzExOTA5NDgsDQogICJpc3MiOiAiaHR0cHM6Ly9hcGkuYm90ZnJhbWV3b3JrLmNvbSIsDQogICJhdWQiOiAiNGMwMDM5ZTUtNjgxNi00OGU4LWIzMTMtZjc3NjkxZmYxYzVlIg0KfQ.cEVHmQCTjL9HVHGk91sja5CqjgvM7B-nArkOg4bE83m762S_le94--GBb0_7aAy6DCdvkZP0d4yWwbpfOkukEXixCDZQM2kWPcOo6lz_VIuXxHFlZAGrTvJ1QkBsg7vk-6_HR8XSLJQZoWrVhE-E_dPj4GPBKE6s1aNxYytzazbKRAEYa8Cn4iVtuYbuj4XfH8PMDv5aC0APNvfgTGk-BlIiP6AGdo4JYs62lUZVSAYg5VLdBcJYMYcKt-h2n1saeapFDVHx_tdpRuke42M4RpGH_wzICeWC5tTExWEkQWApU85HRA5zzk4OpTv17Ct13JCvQ7cD5x9RK5f7CMnbhQ")); + Assert.assertEquals("To bot v1 token from emulator", false, SkillValidation.isSkillToken("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiI0YzMzYzQyMS1mN2QzLTRiNmMtOTkyYi0zNmU3ZTZkZTg3NjEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwiaWF0IjoxNTcxMTg5ODczLCJuYmYiOjE1NzExODk4NzMsImV4cCI6MTU3MTE5Mzc3MywiYWlvIjoiNDJWZ1lLaWJGUDIyMUxmL0NjL1Yzai8zcGF2RUFBPT0iLCJhcHBpZCI6IjRjMzNjNDIxLWY3ZDMtNGI2Yy05OTJiLTM2ZTdlNmRlODc2MSIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2Q2ZDQ5NDIwLWYzOWItNGRmNy1hMWRjLWQ1OWE5MzU4NzFkYi8iLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJOdXJ3bTVOQnkwR2duT3dKRnFVREFBIiwidmVyIjoiMS4wIn0.GcKs3XZ_4GONVsAoPYI7otqUZPoNN8pULUnlJMxQa-JKXRKV0KtvTAdcMsfYudYxbz7HwcNYerFT1q3RZAimJFtfF4x_sMN23yEVxsQmYQrsf2YPmEsbCfNiEx0YEoWUdS38R1N0Iul2P_P_ZB7XreG4aR5dT6lY5TlXbhputv9pi_yAU7PB1aLuB05phQme5NwJEY22pUfx5pe1wVHogI0JyNLi-6gdoSL63DJ32tbQjr2DNYilPVtLsUkkz7fTky5OKd4p7FmG7P5EbEK4H5j04AGe_nIFs-X6x_FIS_5OSGK4LGA2RPnqa-JYpngzlNWVkUbnuH10AovcAprgdg")); + Assert.assertEquals("To bot v2 token from emulator", false, SkillValidation.isSkillToken("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiI0YzAwMzllNS02ODE2LTQ4ZTgtYjMxMy1mNzc2OTFmZjFjNWUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiL3YyLjAiLCJpYXQiOjE1NzExODkwMTEsIm5iZiI6MTU3MTE4OTAxMSwiZXhwIjoxNTcxMTkyOTExLCJhaW8iOiI0MlZnWUxnYWxmUE90Y2IxaEoxNzJvbmxIc3ZuQUFBPSIsImF6cCI6IjRjMDAzOWU1LTY4MTYtNDhlOC1iMzEzLWY3NzY5MWZmMWM1ZSIsImF6cGFjciI6IjEiLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJucEVxVTFoR1pVbXlISy1MUVdJQ0FBIiwidmVyIjoiMi4wIn0.CXcPx7LfatlRsOX4QG-jaC-guwcY3PFxpFICqwfoOTxAjHpeJNFXOpFeA3Qb5VKM6Yw5LyA9eraL5QDJB_4uMLCCKErPXMyoSm8Hw-GGZkHgFV5ciQXSXhE-IfOinqHE_0Lkt_VLR2q6ekOncnJeCR111QCqt3D8R0Ud0gvyLv_oONxDtqg7HUgNGEfioB-BDnBsO4RN7NGrWQFbyPxPmhi8a_Xc7j5Bb9jeiiIQbVaWkIrrPN31aWY1tEZLvdN0VluYlOa0EBVrzpXXZkIyWx99mpklg0lsy7mRyjuM1xydmyyGkzbiCKtODOanf8UwTjkTg5XTIluxe79_hVk2JQ")); + Assert.assertEquals("To skill valid v1 token", true, SkillValidation.isSkillToken("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsIng1dCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiI0YzMzYzQyMS1mN2QzLTRiNmMtOTkyYi0zNmU3ZTZkZTg3NjEiLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC9kNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIvIiwiaWF0IjoxNTcxMTg5NjMwLCJuYmYiOjE1NzExODk2MzAsImV4cCI6MTU3MTE5MzUzMCwiYWlvIjoiNDJWZ1lJZzY1aDFXTUVPd2JmTXIwNjM5V1lLckFBPT0iLCJhcHBpZCI6IjRjMDAzOWU1LTY4MTYtNDhlOC1iMzEzLWY3NzY5MWZmMWM1ZSIsImFwcGlkYWNyIjoiMSIsImlkcCI6Imh0dHBzOi8vc3RzLndpbmRvd3MubmV0L2Q2ZDQ5NDIwLWYzOWItNGRmNy1hMWRjLWQ1OWE5MzU4NzFkYi8iLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJhWlpOUTY3RjRVNnNmY3d0S0R3RUFBIiwidmVyIjoiMS4wIn0.Yogk9fptxxJKO8jRkk6FrlLQsAulNNgoa0Lqv2JPkswyyizse8kcwQhxOaZOotY0UBduJ-pCcrejk6k4_O_ZReYXKz8biL9Q7Z02cU9WUMvuIGpAhttz8v0VlVSyaEJVJALc5B-U6XVUpZtG9LpE6MVror_0WMnT6T9Ijf9SuxUvdVCcmAJyZuoqudodseuFI-jtCpImEapZp0wVN4BUodrBacMbTeYjdZyAbNVBqF5gyzDztMKZR26HEz91gqulYZvJJZOJO6ejnm0j62s1tqvUVRBywvnSOon-MV0Xt2Vm0irhv6ipzTXKwWhT9rGHSLj0g8r6NqWRyPRFqLccvA")); + Assert.assertEquals("To skill valid v2 token", true, SkillValidation.isSkillToken("Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiIsImtpZCI6ImFQY3R3X29kdlJPb0VOZzNWb09sSWgydGlFcyJ9.eyJhdWQiOiI0YzAwMzllNS02ODE2LTQ4ZTgtYjMxMy1mNzc2OTFmZjFjNWUiLCJpc3MiOiJodHRwczovL2xvZ2luLm1pY3Jvc29mdG9ubGluZS5jb20vZDZkNDk0MjAtZjM5Yi00ZGY3LWExZGMtZDU5YTkzNTg3MWRiL3YyLjAiLCJpYXQiOjE1NzExODk3NTUsIm5iZiI6MTU3MTE4OTc1NSwiZXhwIjoxNTcxMTkzNjU1LCJhaW8iOiI0MlZnWUpnZDROZkZKeG1tMTdPaVMvUk8wZll2QUE9PSIsImF6cCI6IjRjMzNjNDIxLWY3ZDMtNGI2Yy05OTJiLTM2ZTdlNmRlODc2MSIsImF6cGFjciI6IjEiLCJ0aWQiOiJkNmQ0OTQyMC1mMzliLTRkZjctYTFkYy1kNTlhOTM1ODcxZGIiLCJ1dGkiOiJMc2ZQME9JVkNVS1JzZ1IyYlFBQkFBIiwidmVyIjoiMi4wIn0.SggsEbEyXDYcg6EdhK-RA1y6S97z4hwEccXc6a3ymnHP-78frZ3N8rPLsqLoK5QPGA_cqOXsX1zduA4vlFSy3MfTV_npPfsyWa1FIse96-2_3qa9DIP8bhvOHXEVZeq-r-0iF972waFyPPC_KVYWnIgAcunGhFWvLhhOUx9dPgq7824qTq45ma1rOqRoYbhhlRn6PJDymIin5LeOzDGJJ8YVLnFUgntc6_4z0P_fnuMktzar88CUTtGvR4P7XNJhS8v9EwYQujglsJNXg7LNcwV7qOxDYWJtT_UMuMAts9ctD6FkuTGX_-6FTqmdUPPUS4RWwm4kkl96F_dXnos9JA")); + } + + @Test + public void IdentityValidationTests() { + String audience = UUID.randomUUID().toString(); + String appId = UUID.randomUUID().toString(); + Map claims = new HashMap<>(); + ClaimsIdentity mockIdentity = mock(ClaimsIdentity.class); + CredentialProvider mockCredentials = mock(CredentialProvider.class); + + // Null identity + CompletionException exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(null, null).join(); + }); + Assert.assertEquals("Invalid Identity", exception.getCause().getMessage()); + + // not authenticated identity + when(mockIdentity.isAuthenticated()).thenReturn(false); + exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(mockIdentity, null).join(); + }); + Assert.assertEquals("Token Not Authenticated", exception.getCause().getMessage()); + + // No version claims + when(mockIdentity.isAuthenticated()).thenReturn(true); + when(mockIdentity.claims()).thenReturn(claims); + exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(mockIdentity, null).join(); + }); + Assert.assertEquals( + AuthenticationConstants.VERSION_CLAIM + " claim is required on skill Tokens.", + exception.getCause().getMessage() + ); + + // No audience claim + claims.put(AuthenticationConstants.VERSION_CLAIM, "1.0"); + exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(mockIdentity, null).join(); + }); + Assert.assertEquals( + AuthenticationConstants.AUDIENCE_CLAIM + " claim is required on skill Tokens.", + exception.getCause().getMessage() + ); + + // Invalid AppId in in appId or azp + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, audience); + when(mockCredentials.isValidAppId(any())).thenReturn(CompletableFuture.completedFuture(true)); + exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(mockIdentity, mockCredentials).join(); + }); + Assert.assertEquals("Invalid appId.", exception.getCause().getMessage()); + + // Invalid AppId in audience + claims.put(AuthenticationConstants.APPID_CLAIM, appId); + when(mockCredentials.isValidAppId(any())).thenReturn(CompletableFuture.completedFuture(false)); + exception = Assert.assertThrows(CompletionException.class, () -> { + SkillValidation.validateIdentity(mockIdentity, mockCredentials).join(); + }); + Assert.assertEquals("Invalid audience.", exception.getCause().getMessage()); + + // All checks pass (no exception thrown) + when(mockCredentials.isValidAppId(any())).thenReturn(CompletableFuture.completedFuture(true)); + SkillValidation.validateIdentity(mockIdentity, mockCredentials).join(); + } + + @Test + public void CreateAnonymousSkillClaimTest() { + ClaimsIdentity sut = SkillValidation.createAnonymousSkillClaim(); + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID, JwtTokenValidation.getAppIdFromClaims(sut.claims())); + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, sut.getType()); + } +} From 6da532d374d4f8c42ce638e0eab22acfaaef6fb5 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 12 Feb 2021 10:20:22 -0600 Subject: [PATCH 070/221] Teams Auth Sample (#973) --- .../bot/dialogs/prompts/OAuthPrompt.java | 7 +- .../bot/sample/authentication/MainDialog.java | 1 - samples/46.teams-auth/LICENSE | 21 + samples/46.teams-auth/README.md | 78 ++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/46.teams-auth/pom.xml | 250 +++++++++++ .../bot/sample/teamsauth/Application.java | 86 ++++ .../bot/sample/teamsauth/DialogBot.java | 53 +++ .../bot/sample/teamsauth/LogoutDialog.java | 69 +++ .../bot/sample/teamsauth/MainDialog.java | 109 +++++ .../sample/teamsauth/SimpleGraphClient.java | 46 ++ .../bot/sample/teamsauth/TeamsBot.java | 53 +++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../46.teams-auth/src/main/webapp/index.html | 418 ++++++++++++++++++ .../bot/sample/teamsauth/ApplicationTest.java | 19 + .../teamsAppManifest/icon-color.png | Bin 0 -> 1229 bytes .../teamsAppManifest/icon-outline.png | Bin 0 -> 383 bytes .../teamsAppManifest/manifest.json | 44 ++ 22 files changed, 1837 insertions(+), 3 deletions(-) create mode 100644 samples/46.teams-auth/LICENSE create mode 100644 samples/46.teams-auth/README.md create mode 100644 samples/46.teams-auth/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/46.teams-auth/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/46.teams-auth/pom.xml create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/Application.java create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/DialogBot.java create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/LogoutDialog.java create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/MainDialog.java create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/SimpleGraphClient.java create mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/TeamsBot.java create mode 100644 samples/46.teams-auth/src/main/resources/application.properties create mode 100644 samples/46.teams-auth/src/main/resources/log4j2.json create mode 100644 samples/46.teams-auth/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/46.teams-auth/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/46.teams-auth/src/main/webapp/index.html create mode 100644 samples/46.teams-auth/src/test/java/com/microsoft/bot/sample/teamsauth/ApplicationTest.java create mode 100644 samples/46.teams-auth/teamsAppManifest/icon-color.png create mode 100644 samples/46.teams-auth/teamsAppManifest/icon-outline.png create mode 100644 samples/46.teams-auth/teamsAppManifest/manifest.json diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java index d6192c74e..f5fc8347f 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java @@ -299,8 +299,11 @@ public static CompletableFuture> recognize } } } else if (isTeamsVerificationInvoke(turnContext)) { - String magicCode = (String) turnContext.getActivity().getValue(); - //var magicCode = magicCodeObject.GetValue("state", StringComparison.Ordinal)?.toString(); + HashMap values = (HashMap) turnContext.getActivity().getValue(); + String magicCode = ""; + if (values != null && values instanceof HashMap) { + magicCode = (String) values.get("state"); + } Object adapterObject = turnContext.getAdapter(); if (!(adapterObject instanceof UserTokenProvider)) { diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java index a182e8b57..2a4cb4810 100644 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java +++ b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java @@ -24,7 +24,6 @@ public MainDialog(Configuration configuration) { super("MainDialog", configuration.getProperty("ConnectionName")); OAuthPromptSettings settings = new OAuthPromptSettings(); - settings.setConnectionName(""); settings.setText("Please Sign In"); settings.setTitle("Sign In"); settings.setConnectionName(configuration.getProperty("ConnectionName")); diff --git a/samples/46.teams-auth/LICENSE b/samples/46.teams-auth/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/46.teams-auth/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/46.teams-auth/README.md b/samples/46.teams-auth/README.md new file mode 100644 index 000000000..ad55791bd --- /dev/null +++ b/samples/46.teams-auth/README.md @@ -0,0 +1,78 @@ + +# Teams Authentication Bot + +Bot Framework v4 bot using Teams authentication + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to get started with authentication in a bot for Microsoft Teams. + +The focus of this sample is how to use the Bot Framework support for oauth in your bot. Teams behaves slightly differently than other channels in this regard. Specifically an Invoke Activity is sent to the bot rather than the Event Activity used by other channels. _This Invoke Activity must be forwarded to the dialog if the OAuthPrompt is being used._ This is done by subclassing the ActivityHandler and this sample includes a reusable TeamsActivityHandler. This class is a candidate for future inclusion in the Bot Framework SDK. + +The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. The OAuth token is then used to make basic Microsoft Graph queries. + +> IMPORTANT: The manifest file in this app adds "token.botframework.com" to the list of `validDomains`. This must be included in any bot that uses the Bot Framework OAuth flow. + +## Prerequisites + +- Microsoft Teams is installed and you have an account +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-java.git + ``` + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `resources/application.properties` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`) + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) From the root of this project folder: + - Build the sample using `mvn package` + - Unless done previously, install the packages in the local cache by using `mvn install` + - Run it by using `java -jar .\target\bot-teams-auth-sample.jar` + + +## Interacting with the bot in Teams + +> Note this `manifest.json` specified that the bot will be installed in a "personal" scope only. Please refer to Teams documentation for more details. + +You can interact with this bot by sending it a message. The bot will respond by requesting you to login to AAD, then making a call to the Graph API on your behalf and returning the results. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Azure Portal](https://portal.azure.com) +- [Add Authentication to Your Bot Via Azure Bot Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Microsoft Teams Developer Platform](https://docs.microsoft.com/en-us/microsoftteams/platform/) diff --git a/samples/46.teams-auth/deploymentTemplates/template-with-new-rg.json b/samples/46.teams-auth/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/46.teams-auth/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/46.teams-auth/deploymentTemplates/template-with-preexisting-rg.json b/samples/46.teams-auth/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/46.teams-auth/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/46.teams-auth/pom.xml b/samples/46.teams-auth/pom.xml new file mode 100644 index 000000000..a77885831 --- /dev/null +++ b/samples/46.teams-auth/pom.xml @@ -0,0 +1,250 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-teams-authentication + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Teams Authentication sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.teamsauth.Application + + + + + com.microsoft.graph + microsoft-graph + 2.6.0 + + + + junit + junit + 4.12 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.11.0 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview8 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview8 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.teamsauth.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/Application.java b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/Application.java new file mode 100644 index 000000000..059a0c05b --- /dev/null +++ b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/Application.java @@ -0,0 +1,86 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsauth; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + UserState userState, + Dialog rootDialog + ) { + return new TeamsBot(conversationState, userState, rootDialog); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog(Configuration configuration) { + return new MainDialog(configuration); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/DialogBot.java b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/DialogBot.java new file mode 100644 index 000000000..61af81f99 --- /dev/null +++ b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/DialogBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsauth; + +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.teams.TeamsActivityHandler; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to + * allows multiple different bots to be run at different endpoints within the same project. This + * can be achieved by defining distinct Controller types each with dependency on distinct IBot + * types, this way ASP Dependency Injection can glue everything together without ambiguity. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have + * been used in a Dialog implementation, and the requirement is that all BotState objects are + * saved at the end of a turn. + */ +public class DialogBot extends TeamsActivityHandler { + protected Dialog dialog; + protected BotState conversationState; + protected BotState userState; + + public DialogBot( + ConversationState withConversationState, + UserState withUserState, + T withDialog + ) { + dialog = withDialog; + conversationState = withConversationState; + userState = withUserState; + } + + @Override + public CompletableFuture onTurn( + TurnContext turnContext + ) { + return super.onTurn(turnContext) + .thenCompose(result -> conversationState.saveChanges(turnContext)) + .thenCompose(result -> userState.saveChanges(turnContext)); + } + + @Override + protected CompletableFuture onMessageActivity( + TurnContext turnContext + ) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/LogoutDialog.java b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/LogoutDialog.java new file mode 100644 index 000000000..122d671af --- /dev/null +++ b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/LogoutDialog.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsauth; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.ActivityTypes; + +public class LogoutDialog extends ComponentDialog { + + private final String connectionName; + + public LogoutDialog(String id, String connectionName) { + super(id); + this.connectionName = connectionName; + } + + + @Override + protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onBeginDialog(innerDc, options); + } + + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + DialogTurnResult result = interrupt(innerDc).join(); + if (result != null) { + return CompletableFuture.completedFuture(result); + } + + return super.onContinueDialog(innerDc); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + if (text.equals("logout")) { + // The bot adapter encapsulates the authentication processes. + BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext().getAdapter(); + botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); + innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")).join(); + return innerDc.cancelAllDialogs(); + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * @return the ConnectionName value as a String. + */ + protected String getConnectionName() { + return this.connectionName; + } + +} + diff --git a/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/MainDialog.java b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/MainDialog.java new file mode 100644 index 000000000..b23d9f4e2 --- /dev/null +++ b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/MainDialog.java @@ -0,0 +1,109 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsauth; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPrompt; +import com.microsoft.bot.dialogs.prompts.OAuthPromptSettings; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.TokenResponse; +import com.microsoft.graph.models.extensions.User; + +import org.apache.commons.lang3.StringUtils; + + class MainDialog extends LogoutDialog { + + public MainDialog(Configuration configuration) { + super("MainDialog", configuration.getProperty("ConnectionName")); + + OAuthPromptSettings settings = new OAuthPromptSettings(); + settings.setText("Please Sign In"); + settings.setTitle("Sign In"); + settings.setConnectionName(configuration.getProperty("ConnectionName")); + settings.setTimeout(300000); // User has 5 minutes to login (1000 * 60 * 5) + + addDialog(new OAuthPrompt("OAuthPrompt", settings)); + + addDialog(new ConfirmPrompt("ConfirmPrompt")); + + WaterfallStep[] waterfallSteps = { + this::promptStep, + this::loginStep, + this::displayTokenPhase1, + this::displayTokenPhase2 + }; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture promptStep(WaterfallStepContext stepContext) { + return stepContext.beginDialog("OAuthPrompt", null); + } + + private CompletableFuture loginStep(WaterfallStepContext stepContext) { + // Get the token from the previous step. Note that we could also have gotten the + // token directly from the prompt itself. There instanceof an example of this in the next method. + TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + if (tokenResponse != null && tokenResponse.getToken() != null) { + SimpleGraphClient client = new SimpleGraphClient(tokenResponse.getToken()); + User me = client.getMe(); + String title = !StringUtils.isEmpty(me.jobTitle) ? me.jobTitle : "Unknown"; + + stepContext.getContext().sendActivity( + MessageFactory.text(String.format("You're logged in as %s (%s); your job title is: %s", + me.displayName, me.userPrincipalName, title))); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text("Would you like to view your token?")); + return stepContext.prompt("ConfirmPrompt", options); + } + + stepContext.getContext().sendActivity(MessageFactory.text("Login was not successful please try again.")); + return stepContext.endDialog(); + } + + private CompletableFuture displayTokenPhase1(WaterfallStepContext stepContext) { + stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")); + + if (stepContext.getResult() != null){ + boolean result = (boolean)stepContext.getResult(); + if (result) { + // Call the prompt again because we need the token. The reasons for this are: + // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry + // about refreshing it. We can always just call the prompt again to get the token. + // 2. We never know how long it will take a user to respond. By the time the + // user responds the token may have expired. The user would then be prompted to login again. + // + // There instanceof no reason to store the token locally in the bot because we can always just call + // the OAuth prompt to get the token or get a new token if needed. + return stepContext.beginDialog("OAuthPrompt"); + } + } + + return stepContext.endDialog(); + } + + private CompletableFuture displayTokenPhase2(WaterfallStepContext stepContext) { + TokenResponse tokenResponse = (TokenResponse)stepContext.getResult(); + if (tokenResponse != null) { + stepContext.getContext().sendActivity(MessageFactory.text( + String.format("Here instanceof your token %s", tokenResponse.getToken() + ))); + } + + return stepContext.endDialog(); + } +} + diff --git a/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/SimpleGraphClient.java b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/SimpleGraphClient.java new file mode 100644 index 000000000..5a11c47d0 --- /dev/null +++ b/samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/SimpleGraphClient.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.teamsauth; + +import com.microsoft.graph.logger.DefaultLogger; +import com.microsoft.graph.logger.LoggerLevel; +import com.microsoft.graph.models.extensions.Message; +import com.microsoft.graph.models.extensions.User; +import com.microsoft.graph.options.Option; +import com.microsoft.graph.options.QueryOption; +import com.microsoft.graph.requests.extensions.GraphServiceClient; +import com.microsoft.graph.models.extensions.IGraphServiceClient; +import com.microsoft.graph.requests.extensions.IMessageCollectionPage; + +import java.util.LinkedList; +import java.util.List; + +public class SimpleGraphClient { + private String token; + public SimpleGraphClient(String token) { + this.token = token; + } + + public User getMe() { + IGraphServiceClient client = getAuthenticatedClient(); + final List
+ + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.version} + + + org.apache.maven.plugins + maven-checkstyle-plugin + ${checkstyle.version} + + com/microsoft/recognizers/** + + + + + checkstyle + + + + + + + diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java index c5cbb4b61..63fdcd743 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogSet.java @@ -13,16 +13,16 @@ import java.nio.charset.StandardCharsets; import java.util.Collection; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; -import org.apache.commons.collections4.map.HashedMap; import org.apache.commons.lang3.StringUtils; /** * A collection of Dialog objects that can all call each other. */ public class DialogSet { - private Map dialogs = new HashedMap<>(); + private Map dialogs = new HashMap<>(); private StatePropertyAccessor dialogState; @JsonIgnore private BotTelemetryClient telemetryClient; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java index 61e3c31e3..ce3c6e002 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ObjectPath.java @@ -22,73 +22,8 @@ import java.util.Set; import java.util.function.BiConsumer; import java.util.stream.Collectors; -import org.apache.commons.collections4.IteratorUtils; import org.apache.commons.lang3.StringUtils; -/** - * Generic Arraylist of Object. - */ -class Segments extends ArrayList { - - /** - * Returns the first item in the collection. - * @return the first object. - */ - public Object first() { - return get(0); - } - - /** - * Returns the last item in the collection. - * @return the last object. - */ - public Object last() { - return get(size() - 1); - } - - /** - * Gets the SegmentType at the specified index. - * @param index Index of the requested segment. - * @return The SegmentType of item at the requested index. - */ - public SegmentType getSegment(int index) { - return new SegmentType(get(index)); - } -} - -/** - * A class wraps an Object and can assist in determining if it's an integer. - */ -@SuppressWarnings("checkstyle:VisibilityModifier") -class SegmentType { - - public boolean isInt; - public int intValue; - public Segments segmentsValue; - public String stringValue; - - /** - * - * @param value The object to create a SegmentType for. - */ - SegmentType(Object value) { - try { - intValue = Integer.parseInt((String) value); - isInt = true; - } catch (NumberFormatException e) { - isInt = false; - } - - if (!isInt) { - if (value instanceof Segments) { - segmentsValue = (Segments) value; - } else { - stringValue = (String) value; - } - } - } -} - /** * Helper methods for working with dynamic json objects. */ @@ -307,7 +242,9 @@ public static Collection getProperties(Object obj) { } else if (obj instanceof Map) { return ((Map) obj).keySet(); } else if (obj instanceof JsonNode) { - return IteratorUtils.toList(((JsonNode) obj).fieldNames()); + List fields = new ArrayList<>(); + ((JsonNode) obj).fieldNames().forEachRemaining(fields::add); + return fields; } else { List fields = new ArrayList<>(); for (Field field : obj.getClass().getDeclaredFields()) { diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SegmentType.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SegmentType.java new file mode 100644 index 000000000..b3e044a43 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SegmentType.java @@ -0,0 +1,33 @@ +package com.microsoft.bot.dialogs; + +/** + * A class wraps an Object and can assist in determining if it's an integer. + */ +@SuppressWarnings("checkstyle:VisibilityModifier") +class SegmentType { + + public boolean isInt; + public int intValue; + public Segments segmentsValue; + public String stringValue; + + /** + * @param value The object to create a SegmentType for. + */ + SegmentType(Object value) { + try { + intValue = Integer.parseInt((String) value); + isInt = true; + } catch (NumberFormatException e) { + isInt = false; + } + + if (!isInt) { + if (value instanceof Segments) { + segmentsValue = (Segments) value; + } else { + stringValue = (String) value; + } + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Segments.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Segments.java new file mode 100644 index 000000000..e2ea8779b --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Segments.java @@ -0,0 +1,37 @@ +package com.microsoft.bot.dialogs; + +import java.util.ArrayList; + +/** + * Generic Arraylist of Object. + */ +class Segments extends ArrayList { + + /** + * Returns the first item in the collection. + * + * @return the first object. + */ + public Object first() { + return get(0); + } + + /** + * Returns the last item in the collection. + * + * @return the last object. + */ + public Object last() { + return get(size() - 1); + } + + /** + * Gets the SegmentType at the specified index. + * + * @param index Index of the requested segment. + * @return The SegmentType of item at the requested index. + */ + public SegmentType getSegment(int index) { + return new SegmentType(get(index)); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java index aab062cee..3fddb79f7 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java @@ -52,6 +52,8 @@ public class DialogStateManager implements Map { private final DialogContext dialogContext; private int version; + private ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + /** * Initializes a new instance of the * {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class. @@ -147,7 +149,6 @@ public void setElement(String key, Object element) { if (key.indexOf(SEPARATORS[0]) == -1 && key.indexOf(SEPARATORS[1]) == -1) { MemoryScope scope = getMemoryScope(key); if (scope != null) { - ObjectMapper mapper = new ObjectMapper(); try { scope.setMemory(dialogContext, mapper.writeValueAsString(element)); } catch (JsonProcessingException e) { @@ -398,7 +399,6 @@ public void setValue(String path, Object value) { } if (value != null) { - ObjectMapper mapper = new ObjectMapper(); value = mapper.valueToTree(value); } @@ -433,7 +433,6 @@ public void removeValue(String path) { * @return JsonNode that which represents all memory scopes. */ public JsonNode getMemorySnapshot() { - ObjectMapper mapper = new ObjectMapper(); ObjectNode result = mapper.createObjectNode(); List scopes = configuration.getMemoryScopes().stream().filter((x) -> x.getIncludeInSnapshot()) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java index ab2ddac4a..bccccf058 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/DialogContextMemoryScope.java @@ -3,15 +3,15 @@ package com.microsoft.bot.dialogs.memory.scopes; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.schema.Serialization; import java.util.Optional; import com.microsoft.bot.dialogs.DialogContext; import com.microsoft.bot.dialogs.DialogInstance; import com.microsoft.bot.dialogs.ScopePath; -import org.json.JSONArray; -import org.json.JSONObject; - /** * DialogContextMemoryScope maps "dialogcontext" -> properties. */ @@ -39,8 +39,8 @@ public final Object getMemory(DialogContext dialogContext) { throw new IllegalArgumentException("dialogContext cannot be null."); } - JSONObject memory = new JSONObject(); - JSONArray stack = new JSONArray(); + ObjectNode memory = Serialization.createObjectNode(); + ArrayNode stack = Serialization.createArrayNode(); DialogContext currentDc = dialogContext; // go to leaf node @@ -52,7 +52,7 @@ public final Object getMemory(DialogContext dialogContext) { // (PORTERS NOTE: javascript stack is reversed with top of stack on end) currentDc.getStack().forEach(item -> { if (item.getId().startsWith("ActionScope[")) { - stack.put(item.getId()); + stack.add(item.getId()); } }); @@ -61,7 +61,7 @@ public final Object getMemory(DialogContext dialogContext) { } // top of stack is stack[0]. - memory.put(stackKey, stack); + memory.set(stackKey, stack); memory.put(activeDialogKey, Optional.ofNullable(dialogContext) .map(DialogContext::getActiveDialog) .map(DialogInstance::getId) diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java index 9f822ef58..c3eca38b3 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ComponentDialogTests.java @@ -230,11 +230,7 @@ public void BasicComponentDialogTest() throws UnsupportedDataTypeException { new TestFlow(adapter, (turnContext) -> { DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); DialogSet dialogs = new DialogSet(dialogState); - try { - dialogs.add(new TestComponentDialog()); - } catch (UnsupportedDataTypeException e) { - e.printStackTrace(); - } + dialogs.add(new TestComponentDialog()); DialogContext dc = dialogs.createContext(turnContext).join(); @@ -272,11 +268,7 @@ public void NestedComponentDialogTest() { DialogState state = dialogState.get(turnContext, () -> new DialogState()).join(); DialogSet dialogs = new DialogSet(dialogState); - try { - dialogs.add(new TestNestedComponentDialog()); - } catch (UnsupportedDataTypeException e) { - e.printStackTrace(); - } + dialogs.add(new TestNestedComponentDialog()); DialogContext dc = dialogs.createContext(turnContext).join(); @@ -491,7 +483,7 @@ public void flush() { } private class TestComponentDialog extends ComponentDialog { - private TestComponentDialog() throws UnsupportedDataTypeException { + private TestComponentDialog() { super("TestComponentDialog"); addDialog(createWaterfall()); addDialog(new NumberPrompt("number", null, PromptCultureModels.ENGLISH_CULTURE, Integer.class)); @@ -499,7 +491,7 @@ private TestComponentDialog() throws UnsupportedDataTypeException { } private final class TestNestedComponentDialog extends ComponentDialog { - private TestNestedComponentDialog() throws UnsupportedDataTypeException { + private TestNestedComponentDialog() { super("TestNestedComponentDialog"); WaterfallStep[] steps = new WaterfallStep[] { new WaterfallStep1(), diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java index ec38ca1a7..776dcb3ac 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogStateManagerTests.java @@ -180,6 +180,7 @@ public void TestEntitiesRetrieval() { DialogTestFunction testFunction = dc -> { ObjectMapper mapper = new ObjectMapper(); + mapper.findAndRegisterModules(); String[] array = new String[] { "test1", diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java index 53daaf4c8..9d50d0a99 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Serialization.java @@ -8,6 +8,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import java.io.IOException; import java.util.concurrent.CompletableFuture; @@ -259,5 +260,13 @@ public static JsonNode asNode(byte b) { public static ObjectNode createObjectNode() { return objectMapper.createObjectNode(); } + + /** + * Creates an ArrayNode. + * @return ArrayNode. + */ + public static ArrayNode createArrayNode() { + return objectMapper.createArrayNode(); + } } diff --git a/pom.xml b/pom.xml index 65cad6fc2..392c43b48 100644 --- a/pom.xml +++ b/pom.xml @@ -243,22 +243,22 @@ com.fasterxml.jackson.module jackson-module-parameter-names - 2.9.9 + 2.12.1 com.fasterxml.jackson.datatype jackson-datatype-jdk8 - 2.9.9 + 2.12.1 com.fasterxml.jackson.datatype jackson-datatype-jsr310 - 2.9.9 + 2.12.1 com.fasterxml.jackson.core jackson-databind - 2.9.10.7 + 2.12.1 @@ -269,12 +269,12 @@ com.auth0 java-jwt - 3.8.2 + 3.13.0 com.auth0 jwks-rsa - 0.8.3 + 0.15.0 org.slf4j @@ -289,7 +289,12 @@ commons-io commons-io - 2.6 + 2.8.0 + + + com.google.guava + guava + 30.1-jre @@ -590,12 +595,12 @@ org.apache.maven.plugins maven-site-plugin - 3.7.1 + 3.9.1 org.apache.maven.plugins maven-project-info-reports-plugin - 3.0.0 + 3.1.1 From 0a7c6c1f9089dbd467a7e9ae6e4923ed29eba113 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 24 Feb 2021 11:15:06 -0600 Subject: [PATCH 086/221] Luis: Now using okhttp3 instead of httpclient (#1019) --- libraries/bot-ai-luis-v3/pom.xml | 12 ------------ .../com/microsoft/bot/ai/luis/LuisApplication.java | 9 +++------ 2 files changed, 3 insertions(+), 18 deletions(-) diff --git a/libraries/bot-ai-luis-v3/pom.xml b/libraries/bot-ai-luis-v3/pom.xml index 943354b84..6ecfe0ba0 100644 --- a/libraries/bot-ai-luis-v3/pom.xml +++ b/libraries/bot-ai-luis-v3/pom.xml @@ -86,18 +86,6 @@ json 20190722 - - org.apache.httpcomponents - httpcore - 4.4.13 - compile - - - org.apache.httpcomponents - httpclient - 4.5.13 - compile - diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java index 1d8dc181e..b7e7b9a75 100644 --- a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisApplication.java @@ -3,8 +3,7 @@ package com.microsoft.bot.ai.luis; -import org.apache.http.NameValuePair; -import org.apache.http.client.utils.URIBuilder; +import okhttp3.HttpUrl; import java.net.MalformedURLException; import java.net.URISyntaxException; import java.net.URL; @@ -168,11 +167,9 @@ private void parse(String applicationEndpoint) { } try { - - String endpointKeyParsed = new URIBuilder(applicationEndpoint).getQueryParams() + String endpointKeyParsed = HttpUrl.parse(applicationEndpoint) + .queryParameterValues("subscription-key") .stream() - .filter(param -> param.getName().equalsIgnoreCase("subscription-key")) - .map(NameValuePair::getValue) .findFirst() .orElse(""); From 349ab351af2e1c3e1ade7298f8b6cb2f0169e83b Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Thu, 25 Feb 2021 13:28:05 -0300 Subject: [PATCH 087/221] [SDK][Bot-Dialogs] Update Recognizers-Text internal library (#1021) * Update Recognizers-Text Choice library * Update Recognizers-Text Datetime library * Update Recognizers-Text Number library * Update Recognizers-Text NumberWithUnit library * Update Recognizers-Text Main library * Add Recognizers-Text Expression library * Add Recognizers-Text Sequence library * Remove deprecated workaround as it's fixed in RT --- .../dialogs/choices/ChoiceRecognizers.java | 2 +- .../text/choice/resources/ChineseChoice.java | 8 +- .../text/choice/resources/EnglishChoice.java | 8 +- .../text/choice/resources/FrenchChoice.java | 8 +- .../choice/resources/PortugueseChoice.java | 8 +- .../text/choice/resources/SpanishChoice.java | 8 +- .../datetime/resources/EnglishDateTime.java | 6 + .../datetime/resources/SpanishDateTime.java | 10 + .../SpanishDateTimeParserConfiguration.java | 12 +- ...nishDateTimePeriodParserConfiguration.java | 2 +- .../text/datetime/utilities/TimexUtility.java | 27 +- .../text/expression/Constants.java | 55 ++ .../text/expression/DateRange.java | 27 + .../text/expression/Resolution.java | 71 +++ .../recognizers/text/expression/Time.java | 52 ++ .../text/expression/TimeRange.java | 26 + .../expression/TimexConstraintsHelper.java | 102 ++++ .../text/expression/TimexConvert.java | 16 + .../text/expression/TimexCreator.java | 88 +++ .../text/expression/TimexDateHelpers.java | 126 ++++ .../text/expression/TimexFormat.java | 195 ++++++ .../text/expression/TimexHelpers.java | 515 ++++++++++++++++ .../text/expression/TimexInference.java | 100 +++ .../text/expression/TimexParsing.java | 56 ++ .../text/expression/TimexProperty.java | 445 ++++++++++++++ .../text/expression/TimexRange.java | 36 ++ .../text/expression/TimexRangeResolver.java | 266 ++++++++ .../text/expression/TimexRegex.java | 93 +++ .../text/expression/TimexRelativeConvert.java | 14 + .../text/expression/TimexResolver.java | 572 ++++++++++++++++++ .../recognizers/text/expression/TimexSet.java | 20 + .../text/expression/TimexUnit.java | 41 ++ .../text/expression/TimexValue.java | 75 +++ .../english/TimexConstantsEnglish.java | 54 ++ .../english/TimexConvertEnglish.java | 206 +++++++ .../english/TimexRelativeConvertEnglish.java | 184 ++++++ .../extractors/NumberRangeExtractor.java | 8 +- .../number/models/AbstractNumberModel.java | 3 +- .../number/parsers/NumberFormatUtility.java | 14 +- .../text/number/resources/EnglishNumeric.java | 10 +- .../text/number/resources/GermanNumeric.java | 107 +++- .../resources/FrenchNumericWithUnit.java | 4 +- .../resources/GermanNumericWithUnit.java | 6 +- .../recognizers/text/sequence/Constants.java | 71 +++ .../text/sequence/SequenceOptions.java | 11 + .../text/sequence/SequenceRecognizer.java | 238 ++++++++ .../config/BaseSequenceConfiguration.java | 18 + .../config/ISequenceConfiguration.java | 10 + .../text/sequence/config/IpConfiguration.java | 38 ++ .../config/PhoneNumberConfiguration.java | 94 +++ .../sequence/config/URLConfiguration.java | 38 ++ .../english/extractors/EmailExtractor.java | 13 + .../EnglishIpExtractorConfiguration.java | 19 + ...lishPhoneNumberExtractorConfiguration.java | 18 + .../EnglishURLExtractorConfiguration.java | 19 + .../english/extractors/GUIDExtractor.java | 9 + .../english/extractors/HashTagExtractor.java | 9 + .../english/extractors/MentionExtractor.java | 9 + .../sequence/english/parsers/EmailParser.java | 15 + .../sequence/english/parsers/GUIDParser.java | 48 ++ .../english/parsers/HashTagParser.java | 11 + .../sequence/english/parsers/IpParser.java | 11 + .../english/parsers/MentionParser.java | 11 + .../english/parsers/PhoneNumberParser.java | 108 ++++ .../sequence/english/parsers/URLParser.java | 11 + .../extractors/BaseEmailExtractor.java | 65 ++ .../extractors/BaseGUIDExtractor.java | 29 + .../extractors/BaseHashTagExtractor.java | 29 + .../sequence/extractors/BaseIpExtractor.java | 111 ++++ .../extractors/BaseMentionExtractor.java | 29 + .../extractors/BasePhoneNumberExtractor.java | 216 +++++++ ...BasePhoneNumberExtractorConfiguration.java | 23 + .../extractors/BaseSequenceExtractor.java | 101 ++++ .../sequence/extractors/BaseURLExtractor.java | 57 ++ .../models/AbstractSequenceModel.java | 64 ++ .../text/sequence/models/EmailModel.java | 15 + .../text/sequence/models/GUIDModel.java | 54 ++ .../text/sequence/models/HashTagModel.java | 15 + .../text/sequence/models/IpAddressModel.java | 52 ++ .../text/sequence/models/MentionModel.java | 15 + .../sequence/models/PhoneNumberModel.java | 54 ++ .../text/sequence/models/URLModel.java | 15 + .../text/sequence/parsers/BaseIpParser.java | 47 ++ .../sequence/parsers/BaseSequenceParser.java | 17 + .../text/sequence/resources/BaseEmail.java | 33 + .../text/sequence/resources/BaseGUID.java | 20 + .../text/sequence/resources/BaseHashtag.java | 17 + .../text/sequence/resources/BaseIp.java | 64 ++ .../text/sequence/resources/BaseMention.java | 17 + .../sequence/resources/BasePhoneNumbers.java | 119 ++++ .../text/sequence/resources/BaseURL.java | 48 ++ .../text/sequence/resources/ChineseIp.java | 31 + .../resources/ChinesePhoneNumbers.java | 30 + .../text/sequence/resources/ChineseURL.java | 31 + .../resources/EnglishPhoneNumbers.java | 19 + .../resources/PortuguesePhoneNumbers.java | 19 + .../text/utilities/RegExpUtility.java | 42 +- .../text/utilities/StringUtility.java | 6 +- 98 files changed, 5920 insertions(+), 99 deletions(-) create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/DateRange.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Resolution.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Time.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimeRange.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConstraintsHelper.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConvert.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexCreator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexDateHelpers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexFormat.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexHelpers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexInference.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexParsing.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexProperty.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRange.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRangeResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRegex.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRelativeConvert.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexResolver.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexSet.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexUnit.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexValue.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/english/TimexConstantsEnglish.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/english/TimexConvertEnglish.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/english/TimexRelativeConvertEnglish.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/Constants.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/SequenceOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/SequenceRecognizer.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/config/BaseSequenceConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/config/ISequenceConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/config/IpConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/config/PhoneNumberConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/config/URLConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/EmailExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/EnglishIpExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/EnglishPhoneNumberExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/EnglishURLExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/GUIDExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/HashTagExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/extractors/MentionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/EmailParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/GUIDParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/HashTagParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/IpParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/MentionParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/PhoneNumberParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/english/parsers/URLParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseEmailExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseGUIDExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseHashTagExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseIpExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseMentionExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BasePhoneNumberExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BasePhoneNumberExtractorConfiguration.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseSequenceExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/extractors/BaseURLExtractor.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/AbstractSequenceModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/EmailModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/GUIDModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/HashTagModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/IpAddressModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/MentionModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/PhoneNumberModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/models/URLModel.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/parsers/BaseIpParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/parsers/BaseSequenceParser.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseEmail.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseGUID.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseHashtag.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseIp.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseMention.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BasePhoneNumbers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/BaseURL.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/ChineseIp.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/ChinesePhoneNumbers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/ChineseURL.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/EnglishPhoneNumbers.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/sequence/resources/PortuguesePhoneNumbers.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java index 8ae3e5d0a..e8709fb73 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java @@ -131,7 +131,7 @@ private static List> recognizeNumbers(String utterance, return result.stream().map(r -> new ModelResult() {{ setStart(r.start); - setEnd(r.end - 1); // bug in 1.0-SNAPSHOT, should not have to decrement + setEnd(r.end); setText(r.text); setResolution(new FoundChoice() {{ setValue(r.resolution.get("value").toString()); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java index 19fa19526..e7cba0bb0 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/ChineseChoice.java @@ -17,7 +17,11 @@ public class ChineseChoice { public static final String TokenizerRegex = "[^\\u3040-\\u30ff\\u3400-\\u4dbf\\u4e00-\\u9fff\\uf900-\\ufaff\\uff66-\\uff9f]"; - public static final String TrueRegex = "(好[的啊呀嘞哇]|没问题|可以|中|好|同意|行|是的|是|对)|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + public static final String SkinToneRegex = "(\\uD83C\\uDFFB|\\uD83C\\uDFFC|\\uD83C\\uDFFD|\\uD83C\\uDFFE|\\uD83C\\uDFFF)"; - public static final String FalseRegex = "(不行|不好|拒绝|否定|不中|不可以|不是的|不是|不对|不)|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; + public static final String TrueRegex = "(好[的啊呀嘞哇]|没问题|可以|中|好|同意|行|是的|是|对)|(\\uD83D\\uDC4D|\\uD83D\\uDC4C){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); + + public static final String FalseRegex = "(不行|不好|拒绝|否定|不中|不可以|不是的|不是|不对|不)|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java index 440e5a162..82d06783b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/EnglishChoice.java @@ -17,7 +17,11 @@ public class EnglishChoice { public static final String TokenizerRegex = "[^\\w\\d]"; - public static final String TrueRegex = "\\b(true|yes|yep|yup|yeah|y|sure|ok|agree)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C|\\u0001f44c)"; + public static final String SkinToneRegex = "(\\uD83C\\uDFFB|\\uD83C\\uDFFC|\\uD83C\\uDFFD|\\uD83C\\uDFFE|\\uD83C\\uDFFF)"; - public static final String FalseRegex = "\\b(false|nope|nop|no|not\\s+ok|disagree)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90|\\u0001F44E|\\u0001F590)"; + public static final String TrueRegex = "\\b(true|yes|yep|yup|yeah|y|sure|ok|agree)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C|\\u0001f44c){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); + + public static final String FalseRegex = "\\b(false|nope|nop|no|not\\s+ok|disagree)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90|\\u0001F44E|\\u0001F590){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java index 7dcff1c3a..2f3e0104d 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/FrenchChoice.java @@ -17,7 +17,11 @@ public class FrenchChoice { public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; - public static final String TrueRegex = "\\b(s[uû]r|ouais|oui|yep|y|sure|approuver|accepter|consentir|d'accord|ça march[eé])\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + public static final String SkinToneRegex = "(\\uD83C\\uDFFB|\\uD83C\\uDFFC|\\uD83C\\uDFFD|\\uD83C\\uDFFE|\\uD83C\\uDFFF)"; - public static final String FalseRegex = "\\b(faux|nan|non|pas\\s+d'accord|pas\\s+concorder|n'est\\s+pas\\s+(correct|ok)|pas)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; + public static final String TrueRegex = "\\b(s[uû]r|ouais|oui|yep|y|sure|approuver|accepter|consentir|d'accord|ça march[eé])\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); + + public static final String FalseRegex = "\\b(faux|nan|non|pas\\s+d'accord|pas\\s+concorder|n'est\\s+pas\\s+(correct|ok)|pas)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java index 7c6b48ef0..8a33849f3 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/PortugueseChoice.java @@ -17,7 +17,11 @@ public class PortugueseChoice { public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; - public static final String TrueRegex = "\\b(verdade|verdadeir[oa]|sim|isso|claro|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + public static final String SkinToneRegex = "(\\uD83C\\uDFFB|\\uD83C\\uDFFC|\\uD83C\\uDFFD|\\uD83C\\uDFFE|\\uD83C\\uDFFF)"; - public static final String FalseRegex = "\\b(falso|n[aã]o|incorreto|nada disso)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; + public static final String TrueRegex = "\\b(verdade|verdadeir[oa]|sim|isso|claro|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); + + public static final String FalseRegex = "\\b(falso|n[aã]o|incorreto|nada disso)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java index d3cd40413..2752bcb4a 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/choice/resources/SpanishChoice.java @@ -17,7 +17,11 @@ public class SpanishChoice { public static final String TokenizerRegex = "[^\\w\\d\\u00E0-\\u00FC]"; - public static final String TrueRegex = "\\b(verdad|verdadero|sí|sip|s|si|cierto|por supuesto|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C)"; + public static final String SkinToneRegex = "(\\uD83C\\uDFFB|\\uD83C\\uDFFC|\\uD83C\\uDFFD|\\uD83C\\uDFFE|\\uD83C\\uDFFF)"; - public static final String FalseRegex = "\\b(falso|no|nop|n|no)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90)"; + public static final String TrueRegex = "\\b(verdad|verdadero|sí|sip|s|si|cierto|por supuesto|ok)\\b|(\\uD83D\\uDC4D|\\uD83D\\uDC4C){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); + + public static final String FalseRegex = "\\b(falso|no|nop|n|no)\\b|(\\uD83D\\uDC4E|\\u270B|\\uD83D\\uDD90){SkinToneRegex}?" + .replace("{SkinToneRegex}", SkinToneRegex); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java index d24b8ef6a..f91dc7b5b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/EnglishDateTime.java @@ -1231,8 +1231,11 @@ public class EnglishDateTime { public static final ImmutableMap DayOfMonth = ImmutableMap.builder() .put("1st", 1) + .put("1th", 1) .put("2nd", 2) + .put("2th", 2) .put("3rd", 3) + .put("3th", 3) .put("4th", 4) .put("5th", 5) .put("6th", 6) @@ -1268,8 +1271,11 @@ public class EnglishDateTime { .put("30th", 30) .put("31st", 31) .put("01st", 1) + .put("01th", 1) .put("02nd", 2) + .put("02th", 2) .put("03rd", 3) + .put("03th", 3) .put("04th", 4) .put("05th", 5) .put("06th", 6) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java index fb51b40ac..019ed94cb 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/resources/SpanishDateTime.java @@ -369,6 +369,16 @@ public class SpanishDateTime { public static final String PmTimeRegex = "(?(esta|(por|de|a|en)\\s+la)\\s+(tarde|noche))"; + public static final String NightTimeRegex = "(noche)"; + + public static final String LastNightTimeRegex = "(anoche)"; + + public static final String NowTimeRegex = "(ahora|mismo|momento)"; + + public static final String RecentlyTimeRegex = "(mente)"; + + public static final String AsapTimeRegex = "(posible|pueda[ns]?|podamos)"; + public static final String LessThanOneHour = "(?((\\s+y\\s+)?cuarto|(\\s*)menos cuarto|(\\s+y\\s+)media|{BaseDateTime.DeltaMinuteRegex}(\\s+(minutos?|mins?))|{DeltaMinuteNumRegex}(\\s+(minutos?|mins?))))" .replace("{BaseDateTime.DeltaMinuteRegex}", BaseDateTime.DeltaMinuteRegex) .replace("{DeltaMinuteNumRegex}", DeltaMinuteNumRegex); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java index 8ad340630..8ef21ca4d 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimeParserConfiguration.java @@ -40,6 +40,7 @@ public class SpanishDateTimeParserConfiguration extends BaseOptionsConfiguration public final Pattern nowRegex; public final Pattern amTimeRegex; public final Pattern pmTimeRegex; + public final Pattern lastNightTimeRegex; public final Pattern simpleTimeOfTodayAfterRegex; public final Pattern simpleTimeOfTodayBeforeRegex; public final Pattern specificTimeOfDayRegex; @@ -80,6 +81,7 @@ public SpanishDateTimeParserConfiguration(ICommonDateTimeParserConfiguration con pmTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.PmRegex); amTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.AmTimeRegex); + lastNightTimeRegex = RegExpUtility.getSafeRegExp(SpanishDateTime.LastNightTimeRegex); } @Override @@ -122,12 +124,18 @@ public int getSwiftDay(String text) { Matcher regexMatcher = SpanishDatePeriodParserConfiguration.previousPrefixRegex.matcher(trimmedText); int swift = 0; + if (regexMatcher.find()) { - swift = 1; + swift = -1; } else { - regexMatcher = SpanishDatePeriodParserConfiguration.nextPrefixRegex.matcher(trimmedText); + regexMatcher = this.lastNightTimeRegex.matcher(trimmedText); if (regexMatcher.find()) { swift = -1; + } else { + regexMatcher = SpanishDatePeriodParserConfiguration.nextPrefixRegex.matcher(trimmedText); + if (regexMatcher.find()) { + swift = 1; + } } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java index 4626f201a..76ed8cec5 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/spanish/parsers/SpanishDateTimePeriodParserConfiguration.java @@ -332,7 +332,7 @@ public int getSwiftPrefix(String text) { Matcher regexMatcher = regex.matcher(trimmedText); int swift = 0; - if (regexMatcher.find() || trimmedText.equals("anoche")) { + if (regexMatcher.find() || trimmedText.startsWith("anoche")) { swift = -1; } else { regex = Pattern.compile(SpanishDateTime.NextPrefixRegex); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java index 5c643317f..7c6e79aa5 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java @@ -1,6 +1,7 @@ package com.microsoft.recognizers.text.datetime.utilities; import com.google.common.collect.ImmutableMap; +import com.microsoft.recognizers.datatypes.timex.expression.TimexHelpers; import com.microsoft.recognizers.text.datetime.Constants; import com.microsoft.recognizers.text.datetime.DatePeriodTimexType; import com.microsoft.recognizers.text.datetime.DateTimeResolutionKey; @@ -14,6 +15,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; public class TimexUtility { private static final HashMap DatePeriodTimexTypeToTimexSuffix = new HashMap() { @@ -28,33 +30,14 @@ public class TimexUtility { public static String generateCompoundDurationTimex(Map unitToTimexComponents, ImmutableMap unitValueMap) { List unitList = new ArrayList<>(unitToTimexComponents.keySet()); unitList.sort((x, y) -> unitValueMap.get(x) < unitValueMap.get(y) ? 1 : -1); - boolean isTimeDurationAlreadyExist = false; - StringBuilder timexBuilder = new StringBuilder(Constants.GeneralPeriodPrefix); - - for (String unitKey : unitList) { - String timexComponent = unitToTimexComponents.get(unitKey); - - // The Time Duration component occurs first time - if (!isTimeDurationAlreadyExist && isTimeDurationTimex(timexComponent)) { - timexBuilder.append(Constants.TimeTimexPrefix); - timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); - isTimeDurationAlreadyExist = true; - } else { - timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); - } - } - return timexBuilder.toString(); + unitList = unitList.stream().map(t -> unitToTimexComponents.get(t)).collect(Collectors.toList()); + return TimexHelpers.generateCompoundDurationTimex(unitList); } - private static boolean isTimeDurationTimex(String timex) { + private static Boolean isTimeDurationTimex(String timex) { return timex.startsWith(Constants.GeneralPeriodPrefix + Constants.TimeTimexPrefix); } - private static String getDurationTimexWithoutPrefix(String timex) { - // Remove "PT" prefix for TimeDuration, Remove "P" prefix for DateDuration - return timex.substring(isTimeDurationTimex(timex) ? 2 : 1); - } - public static String getDatePeriodTimexUnitCount(LocalDateTime begin, LocalDateTime end, DatePeriodTimexType timexType, Boolean equalDurationLength) { String unitCount = "XX"; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Constants.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Constants.java new file mode 100644 index 000000000..b1e1d9ee4 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Constants.java @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +public class Constants { + + // Timex + public static final String TIMEX_YEAR = "Y"; + public static final String TIMEX_MONTH = "M"; + public static final String TIMEX_MONTH_FULL = "MON"; + public static final String TIMEX_WEEK = "W"; + public static final String TIMEX_DAY = "D"; + public static final String TIMEX_BUSINESS_DAY = "BD"; + public static final String TIMEX_WEEKEND = "WE"; + public static final String TIMEX_HOUR = "H"; + public static final String TIMEX_MINUTE = "M"; + public static final String TIMEX_SECOND = "S"; + public static final String TIMEX_NIGHT = "NI"; + public static final Character TIMEX_FUZZY = 'X'; + public static final String TIMEX_FUZZY_YEAR = "XXXX"; + public static final String TIMEX_FUZZY_MONTH = "XX"; + public static final String TIMEX_FUZZY_WEEK = "WXX"; + public static final String TIMEX_FUZZY_DAY = "XX"; + public static final String DATE_TIMEX_CONNECTOR = "-"; + public static final String TIME_TIMEX_CONNECTOR = ":"; + public static final String GENERAL_PERIOD_PREFIX = "P"; + public static final String TIME_TIMEX_PREFIX = "T"; + + public static final String YEAR_UNIT = "year"; + public static final String MONTH_UNIT = "month"; + public static final String WEEK_UNIT = "week"; + public static final String DAY_UNIT = "day"; + public static final String HOUR_UNIT = "hour"; + public static final String MINUTE_UNIT = "minute"; + public static final String SECOND_UNIT = "second"; + public static final String TIME_DURATION_UNIT = "s"; + + public static final String AM = "AM"; + public static final String PM = "PM"; + + public static final int INVALID_VALUE = -1; + + public static class TimexTypes { + public static final String PRESENT = "present"; + public static final String DEFINITE = "definite"; + public static final String DATE = "date"; + public static final String DATE_TIME = "datetime"; + public static final String DATE_RANGE = "daterange"; + public static final String DURATION = "duration"; + public static final String TIME = "time"; + public static final String TIME_RANGE = "timerange"; + public static final String DATE_TIME_RANGE = "datetimerange"; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/DateRange.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/DateRange.java new file mode 100644 index 000000000..6a2ecb362 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/DateRange.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.time.LocalDateTime; + +public class DateRange { + private LocalDateTime start; + private LocalDateTime end; + + public LocalDateTime getStart() { + return start; + } + + public void setStart(LocalDateTime withStart) { + this.start = withStart; + } + + public LocalDateTime getEnd() { + return end; + } + + public void setEnd(LocalDateTime withEnd) { + this.end = withEnd; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Resolution.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Resolution.java new file mode 100644 index 000000000..1e7e23cc7 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Resolution.java @@ -0,0 +1,71 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.util.ArrayList; +import java.util.List; + +public class Resolution { + private List values; + + public List getValues() { + return this.values; + } + + public Resolution() { + this.values = new ArrayList(); + } + + public static class Entry { + private String timex; + + private String type; + + private String value; + + private String start; + + private String end; + + public String getTimex() { + return timex; + } + + public void setTimex(String withTimex) { + this.timex = withTimex; + } + + public String getType() { + return type; + } + + public void setType(String withType) { + this.type = withType; + } + + public String getValue() { + return value; + } + + public void setValue(String withValue) { + this.value = withValue; + } + + public String getStart() { + return start; + } + + public void setStart(String withStart) { + this.start = withStart; + } + + public String getEnd() { + return end; + } + + public void setEnd(String withEnd) { + this.end = withEnd; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Time.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Time.java new file mode 100644 index 000000000..a0f178deb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/Time.java @@ -0,0 +1,52 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +public class Time { + private Integer hour; + + private Integer minute; + + private Integer second; + + public Time(Integer withSeconds) { + this.hour = (int)Math.floor(withSeconds / 3600000d); + this.minute = (int)Math.floor((withSeconds - (this.hour * 3600000)) / 60000d); + this.second = (withSeconds - (this.hour * 3600000) - (this.minute * 60000)) / 1000; + } + + public Time(Integer withHour, Integer withMinute, Integer withSecond) { + this.hour = withHour; + this.minute = withMinute; + this.second = withSecond; + } + + public Integer getTime() { + return (this.second * 1000) + (this.minute * 60000) + (this.hour * 3600000); + } + + public Integer getHour() { + return hour; + } + + public void setHour(Integer withHour) { + this.hour = withHour; + } + + public Integer getMinute() { + return minute; + } + + public void setMinute(Integer withMinute) { + this.minute = withMinute; + } + + public Integer getSecond() { + return second; + } + + public void setSecond(Integer withSecond) { + this.second = withSecond; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimeRange.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimeRange.java new file mode 100644 index 000000000..541bff588 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimeRange.java @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +public class TimeRange { + private Time start; + + private Time end; + + public Time getStart() { + return start; + } + + public void setStart(Time withStart) { + this.start = withStart; + } + + public Time getEnd() { + return end; + } + + public void setEnd(Time withEnd) { + this.end = withEnd; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConstraintsHelper.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConstraintsHelper.java new file mode 100644 index 000000000..76d1953a6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConstraintsHelper.java @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.util.List; + +public class TimexConstraintsHelper { + public static List collapseTimeRanges(List ranges) { + List r = ranges; + + while (TimexConstraintsHelper.innerCollapseTimeRanges(r)) { + + } + + r.sort((a, b) -> a.getStart().getTime() - b.getStart().getTime()); + + return r; + } + + public static List collapseDateRanges(List ranges) { + List r = ranges; + + while (TimexConstraintsHelper.innerCollapseDateRanges(r)) { + + } + + r.sort((a, b) -> a.getStart().compareTo(b.getStart())); + return r; + } + + public static Boolean isOverlapping(TimeRange r1, TimeRange r2) { + return (r1.getEnd().getTime() > r2.getStart().getTime() && r1.getStart().getTime() <= r2.getStart().getTime()) || + (r1.getStart().getTime() < r2.getEnd().getTime() && + r1.getStart().getTime() >= r2.getStart().getTime()); + } + + private static Boolean isOverlapping(DateRange r1, DateRange r2) { + return (r1.getEnd().isAfter(r2.getStart()) && (r1.getStart().isBefore(r2.getStart()) || r1.getStart().isEqual(r2.getStart()))) || + (r1.getStart().isBefore(r2.getEnd()) && (r1.getStart().isAfter(r2.getStart()) || r1.getStart().isEqual(r2.getStart()))); + } + + private static TimeRange collapseOverlapping(TimeRange r1, TimeRange r2) { + return new TimeRange() { + { + setStart(new Time(Math.max(r1.getStart().getTime(), r2.getStart().getTime()))); + setEnd(new Time(Math.min(r1.getEnd().getTime(), r2.getEnd().getTime()))); + } + }; + } + + private static DateRange collapseOverlapping(DateRange r1, DateRange r2) { + return new DateRange() { + { + setStart(r1.getStart().compareTo(r2.getStart()) > 0 ? r1.getStart() : r2.getStart()); + setEnd(r1.getEnd().compareTo(r2.getEnd()) < 0 ? r1.getEnd() : r2.getEnd()); + } + }; + } + + private static Boolean innerCollapseTimeRanges(List ranges) { + if (ranges.size() == 1) { + return false; + } + + for (int i = 0; i < ranges.size(); i++) { + TimeRange r1 = ranges.get(i); + for (int j = i + 1; j < ranges.size(); j++) { + TimeRange r2 = ranges.get(j); + if (TimexConstraintsHelper.isOverlapping(r1, r2)) { + ranges.subList(i, 1).clear(); + ranges.subList(j - 1, 1).clear(); + ranges.add(TimexConstraintsHelper.collapseOverlapping(r1, r2)); + return true; + } + } + } + + return false; + } + + private static Boolean innerCollapseDateRanges(List ranges) { + if (ranges.size() == 1) { + return false; + } + + for (int i = 0; i < ranges.size(); i++) { + DateRange r1 = ranges.get(i); + for (int j = i + 1; j < ranges.size(); j++) { + DateRange r2 = ranges.get(j); + if (TimexConstraintsHelper.isOverlapping(r1, r2)) { + ranges.subList(i, 1).clear(); + ranges.subList(j - 1, 1).clear(); + ranges.add(TimexConstraintsHelper.collapseOverlapping(r1, r2)); + return true; + } + } + } + + return false; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConvert.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConvert.java new file mode 100644 index 000000000..9b7844177 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexConvert.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import com.microsoft.recognizers.datatypes.timex.expression.english.TimexConvertEnglish; + +public class TimexConvert { + public static String convertTimexToString(TimexProperty timex) { + return TimexConvertEnglish.convertTimexToString(timex); + } + + public static String convertTimexSetToString(TimexSet timexSet) { + return TimexConvertEnglish.convertTimexSetToString(timexSet); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexCreator.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexCreator.java new file mode 100644 index 000000000..94920fdfd --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexCreator.java @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.math.BigDecimal; +import java.time.DayOfWeek; +import java.time.LocalDateTime; + +public class TimexCreator { + // The following constants are consistent with the Recognizer results + public static final String MONDAY = "XXXX-WXX-1"; + public static final String TUESDAY = "XXXX-WXX-2"; + public static final String WEDNESDAY = "XXXX-WXX-3"; + public static final String THURSDAY = "XXXX-WXX-4"; + public static final String FRIDAY = "XXXX-WXX-5"; + public static final String SATURDAY = "XXXX-WXX-6"; + public static final String SUNDAY = "XXXX-WXX-7"; + public static final String MORNING = "(T08,T12,PT4H)"; + public static final String AFTERNOON = "(T12,T16,PT4H)"; + public static final String EVENING = "(T16,T20,PT4H)"; + public static final String DAYTIME = "(T08,T18,PT10H)"; + public static final String NIGHT = "(T20,T24,PT10H)"; + + public static String today(LocalDateTime date) { + return TimexProperty.fromDate(date == null ? LocalDateTime.now() : date).getTimexValue(); + } + + public static String tomorrow(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + d = d.plusDays(1); + return TimexProperty.fromDate(d).getTimexValue(); + } + + public static String yesterday(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + d = d.plusDays(-1); + return TimexProperty.fromDate(d).getTimexValue(); + } + + public static String weekFromToday(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + TimexProperty t = TimexProperty.fromDate(d); + t.setDays(new BigDecimal(7)); + return t.getTimexValue(); + } + + public static String weekBackFromToday(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + d = d.plusDays(-7); + TimexProperty t = TimexProperty.fromDate(d); + t.setDays(new BigDecimal(7)); + return t.getTimexValue(); + } + + public static String thisWeek(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + d = d.plusDays(-7); + LocalDateTime start = TimexDateHelpers.dateOfNextDay(DayOfWeek.MONDAY, d); + TimexProperty t = TimexProperty.fromDate(start); + t.setDays(new BigDecimal(7)); + return t.getTimexValue(); + } + + public static String nextWeek(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + LocalDateTime start = TimexDateHelpers.dateOfNextDay(DayOfWeek.MONDAY, d); + TimexProperty t = TimexProperty.fromDate(start); + t.setDays(new BigDecimal(7)); + return t.getTimexValue(); + } + + public static String lastWeek(LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + LocalDateTime start = TimexDateHelpers.dateOfLastDay(DayOfWeek.MONDAY, d); + start = start.plusDays(-7); + TimexProperty t = TimexProperty.fromDate(start); + t.setDays(new BigDecimal(7)); + return t.getTimexValue(); + } + + public static String nextWeeksFromToday(Integer n, LocalDateTime date) { + LocalDateTime d = (date == null) ? LocalDateTime.now() : date; + TimexProperty t = TimexProperty.fromDate(d); + t.setDays(new BigDecimal(n * 7)); + return t.getTimexValue(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexDateHelpers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexDateHelpers.java new file mode 100644 index 000000000..cdd5707fb --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexDateHelpers.java @@ -0,0 +1,126 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +public class TimexDateHelpers { + public static LocalDateTime tomorrow(LocalDateTime date) { + date = date.plusDays(1); + return date; + } + + public static LocalDateTime yesterday(LocalDateTime date) { + date = date.plusDays(-1); + return date; + } + + public static Boolean datePartEquals(LocalDateTime dateX, LocalDateTime dateY) { + return (dateX.getYear() == dateY.getYear()) && + (dateX.getMonthValue() == dateY.getMonthValue()) && + (dateX.getDayOfMonth() == dateY.getDayOfMonth()); + } + + public static boolean isDateInWeek(LocalDateTime date, LocalDateTime startOfWeek) { + LocalDateTime d = startOfWeek; + for (int i = 0; i < 7; i++) { + if (TimexDateHelpers.datePartEquals(date, d)) { + return true; + } + + d = d.plusDays(1); + } + + return false; + } + + public static Boolean isThisWeek(LocalDateTime date, LocalDateTime referenceDate) { + // Note ISO 8601 week starts on a Monday + LocalDateTime startOfWeek = referenceDate; + while (TimexDateHelpers.getUSDayOfWeek(startOfWeek.getDayOfWeek()) > TimexDateHelpers.getUSDayOfWeek(DayOfWeek.MONDAY)) { + startOfWeek = startOfWeek.plusDays(-1); + } + + return TimexDateHelpers.isDateInWeek(date, startOfWeek); + } + + public static Boolean isNextWeek(LocalDateTime date, LocalDateTime referenceDate) { + LocalDateTime nextWeekDate = referenceDate; + nextWeekDate = nextWeekDate.plusDays(7); + return TimexDateHelpers.isThisWeek(date, nextWeekDate); + } + + public static Boolean isLastWeek(LocalDateTime date, LocalDateTime referenceDate) { + LocalDateTime nextWeekDate = referenceDate; + nextWeekDate = nextWeekDate.plusDays(-7); + return TimexDateHelpers.isThisWeek(date, nextWeekDate); + } + + public static Integer weekOfYear(LocalDateTime date) { + LocalDateTime ds = LocalDateTime.of(date.getYear(), 1, 1, 0, 0); + LocalDateTime de = LocalDateTime.of(date.getYear(), date.getMonthValue(), date.getDayOfMonth(), 0, 0); + Integer weeks = 1; + + while (ds.compareTo(de) < 0) { + Integer dayOfWeek = TimexDateHelpers.getUSDayOfWeek(ds.getDayOfWeek()); + + Integer isoDayOfWeek = (dayOfWeek == 0) ? 7 : dayOfWeek; + if (isoDayOfWeek == 7) { + weeks++; + } + + ds = ds.plusDays(1); + } + + return weeks; + } + + public static String fixedFormatNumber(Integer n, Integer size) { + return String.format("%1$" + size + "s", n.toString()).replace(' ', '0'); + } + + public static LocalDateTime dateOfLastDay(DayOfWeek day, LocalDateTime referenceDate) { + LocalDateTime result = referenceDate; + result = result.plusDays(-1); + + while (result.getDayOfWeek() != day) { + result = result.plusDays(-1); + } + + return result; + } + + public static LocalDateTime dateOfNextDay(DayOfWeek day, LocalDateTime referenceDate) { + LocalDateTime result = referenceDate; + result = result.plusDays(1); + + while (result.getDayOfWeek() != day) { + result = result.plusDays(1); + } + + return result; + } + + public static List datesMatchingDay(DayOfWeek day, LocalDateTime start, LocalDateTime end) { + List result = new ArrayList(); + LocalDateTime d = start; + + while (!TimexDateHelpers.datePartEquals(d, end)) { + if (d.getDayOfWeek() == day) { + result.add(d); + } + + d = d.plusDays(1); + } + + return result; + } + + public static Integer getUSDayOfWeek(DayOfWeek dayOfWeek) { + return dayOfWeek.getValue() % 7; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexFormat.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexFormat.java new file mode 100644 index 000000000..c7f8d2eba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexFormat.java @@ -0,0 +1,195 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.math.BigDecimal; +import java.text.NumberFormat; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +public class TimexFormat { + public static String format(TimexProperty timex) { + HashSet types = timex.getTypes().size() != 0 ? timex.getTypes() : TimexInference.infer(timex); + + if (types.contains(Constants.TimexTypes.PRESENT)) { + return "PRESENT_REF"; + } + + if ((types.contains(Constants.TimexTypes.DATE_TIME_RANGE) || types.contains(Constants.TimexTypes.DATE_RANGE) || + types.contains(Constants.TimexTypes.TIME_RANGE)) && types.contains(Constants.TimexTypes.DURATION)) { + TimexRange range = TimexHelpers.expandDateTimeRange(timex); + return String.format("(%1$s,%2$s,%3$s)", TimexFormat.format(range.getStart()), + TimexFormat.format(range.getEnd()), TimexFormat.format(range.getDuration())); + } + + if (types.contains(Constants.TimexTypes.DATE_TIME_RANGE)) { + return String.format("%1$s%2$s", TimexFormat.formatDate(timex), TimexFormat.formatTimeRange(timex)); + } + + if (types.contains(Constants.TimexTypes.DATE_RANGE)) { + return TimexFormat.formatDateRange(timex); + } + + if (types.contains(Constants.TimexTypes.TIME_RANGE)) { + return TimexFormat.formatTimeRange(timex); + } + + if (types.contains(Constants.TimexTypes.DATE_TIME)) { + return String.format("%1$s%2$s", TimexFormat.formatDate(timex), TimexFormat.formatTime(timex)); + } + + if (types.contains(Constants.TimexTypes.DURATION)) { + return TimexFormat.formatDuration(timex); + } + + if (types.contains(Constants.TimexTypes.DATE)) { + return TimexFormat.formatDate(timex); + } + + if (types.contains(Constants.TimexTypes.TIME)) { + return TimexFormat.formatTime(timex); + } + + return new String(); + } + + private static String formatDuration(TimexProperty timex) { + List timexList = new ArrayList(); + NumberFormat nf = NumberFormat.getInstance(Locale.getDefault()); + + if (timex.getYears() != null) { + nf.setMaximumFractionDigits(timex.getYears().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Year, + timex.getYears() != null ? timex.getYears() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getMonths() != null) { + nf.setMaximumFractionDigits(timex.getMonths().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Month, + timex.getMonths() != null ? timex.getMonths() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getWeeks() != null) { + nf.setMaximumFractionDigits(timex.getWeeks().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Week, + timex.getWeeks() != null ? timex.getWeeks() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getDays() != null) { + nf.setMaximumFractionDigits(timex.getDays().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Day, + timex.getDays() != null ? timex.getDays() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getHours() != null) { + nf.setMaximumFractionDigits(timex.getHours().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Hour, + timex.getHours() != null ? timex.getHours() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getMinutes() != null) { + nf.setMaximumFractionDigits(timex.getMinutes().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Minute, + timex.getMinutes() != null ? timex.getMinutes() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + if (timex.getSeconds() != null) { + nf.setMaximumFractionDigits(timex.getSeconds().scale()); + timexList.add(TimexHelpers.generateDurationTimex(TimexUnit.Second, + timex.getSeconds() != null ? timex.getSeconds() : BigDecimal.valueOf(Constants.INVALID_VALUE))); + } + + return TimexHelpers.generateCompoundDurationTimex(timexList); + } + + private static String formatTime(TimexProperty timex) { + if (timex.getMinute() == 0 && timex.getSecond() == 0) { + return String.format("T%s", TimexDateHelpers.fixedFormatNumber(timex.getHour(), 2)); + } + + if (timex.getSecond() == 0) { + return String.format("T%1$s:%2$s", TimexDateHelpers.fixedFormatNumber(timex.getHour(), 2), + TimexDateHelpers.fixedFormatNumber(timex.getMinute(), 2)); + } + + return String.format("T%1$s:%2$s:%3$s", TimexDateHelpers.fixedFormatNumber(timex.getHour(), 2), + TimexDateHelpers.fixedFormatNumber(timex.getMinute(), 2), + TimexDateHelpers.fixedFormatNumber(timex.getSecond(), 2)); + } + + private static String formatDate(TimexProperty timex) { + Integer year = timex.getYear() != null ? timex.getYear() : Constants.INVALID_VALUE; + Integer month = timex.getWeekOfYear() != null ? timex.getWeekOfYear() + : (timex.getMonth() != null ? timex.getMonth() : Constants.INVALID_VALUE); + Integer day = timex.getDayOfWeek() != null ? timex.getDayOfWeek() + : timex.getDayOfMonth() != null ? timex.getDayOfMonth() : Constants.INVALID_VALUE; + Integer weekOfMonth = timex.getWeekOfMonth() != null ? timex.getWeekOfMonth() : Constants.INVALID_VALUE; + + return TimexHelpers.generateDateTimex(year, month, day, weekOfMonth, timex.getDayOfWeek() != null); + } + + private static String formatDateRange(TimexProperty timex) { + if (timex.getYear() != null && timex.getWeekOfYear() != null && timex.getWeekend() != null) { + return String.format("%1$s-W%2$s-WE", TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4), + TimexDateHelpers.fixedFormatNumber(timex.getWeekOfYear(), 2)); + } + + if (timex.getYear() != null && timex.getWeekOfYear() != null) { + return String.format("%1$s-W%2$s", TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4), + TimexDateHelpers.fixedFormatNumber(timex.getWeekOfYear(), 2)); + } + + if (timex.getYear() != null && timex.getMonth() != null && timex.getWeekOfMonth() != null) { + return String.format("%1$s-%2$s-W%3$s", TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4), + TimexDateHelpers.fixedFormatNumber(timex.getMonth(), 2), + TimexDateHelpers.fixedFormatNumber(timex.getWeekOfMonth(), 2)); + } + + if (timex.getYear() != null && timex.getSeason() != null) { + return String.format("%1$s-%2$s", TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4), + timex.getSeason()); + } + + if (timex.getSeason() != null) { + return timex.getSeason(); + } + + if (timex.getYear() != null && timex.getMonth() != null) { + return String.format("%1$s-%2$s", TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4), + TimexDateHelpers.fixedFormatNumber(timex.getMonth(), 2)); + } + + if (timex.getYear() != null) { + return TimexDateHelpers.fixedFormatNumber(timex.getYear(), 4); + } + + if (timex.getMonth() != null && timex.getWeekOfMonth() != null && timex.getDayOfWeek() != null) { + return String.format("%1$s-%2$s-%3$s-%4$s-%5$s", Constants.TIMEX_FUZZY_YEAR, + TimexDateHelpers.fixedFormatNumber(timex.getMonth(), 2), Constants.TIMEX_FUZZY_WEEK, + timex.getWeekOfMonth(), timex.getDayOfWeek()); + } + + if (timex.getMonth() != null && timex.getWeekOfMonth() != null) { + return String.format("%1$s-%2$s-W%3$02d", Constants.TIMEX_FUZZY_YEAR, + TimexDateHelpers.fixedFormatNumber(timex.getMonth(), 2), timex.getWeekOfMonth()); + } + + if (timex.getMonth() != null) { + return String.format("%1$s-%2$s", Constants.TIMEX_FUZZY_YEAR, + TimexDateHelpers.fixedFormatNumber(timex.getMonth(), 2)); + } + + return new String(); + } + + private static String formatTimeRange(TimexProperty timex) { + if (timex.getPartOfDay() != null) { + return String.format("T%s", timex.getPartOfDay()); + } + + return new String(); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexHelpers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexHelpers.java new file mode 100644 index 000000000..322c696de --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexHelpers.java @@ -0,0 +1,515 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.math.BigDecimal; +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.time.temporal.TemporalField; +import java.time.temporal.WeekFields; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Locale; + +import org.apache.commons.lang3.tuple.Pair; + +public class TimexHelpers { + public static final HashMap TIMEX_UNIT_TO_STRING_MAP = new HashMap() { + { + put(TimexUnit.Year, Constants.TIMEX_YEAR); + put(TimexUnit.Month, Constants.TIMEX_MONTH); + put(TimexUnit.Week, Constants.TIMEX_WEEK); + put(TimexUnit.Day, Constants.TIMEX_DAY); + put(TimexUnit.Hour, Constants.TIMEX_HOUR); + put(TimexUnit.Minute, Constants.TIMEX_MINUTE); + put(TimexUnit.Second, Constants.TIMEX_SECOND); + } + }; + + public static final List TimeTimexUnitList = Arrays.asList(TimexUnit.Hour, TimexUnit.Minute, + TimexUnit.Second); + + public static TimexRange expandDateTimeRange(TimexProperty timex) { + HashSet types = timex.getTypes().size() != 0 ? timex.getTypes() : TimexInference.infer(timex); + + if (types.contains(Constants.TimexTypes.DURATION)) { + TimexProperty start = TimexHelpers.cloneDateTime(timex); + TimexProperty duration = TimexHelpers.cloneDuration(timex); + return new TimexRange() { + { + setStart(start); + setEnd(TimexHelpers.timexDateTimeAdd(start, duration)); + setDuration(duration); + } + }; + } else { + if (timex.getYear() != null) { + Pair dateRange; + if (timex.getMonth() != null && timex.getWeekOfMonth() != null) { + dateRange = TimexHelpers.monthWeekDateRange(timex.getYear(), timex.getMonth(), + timex.getWeekOfMonth()); + } else if (timex.getMonth() != null) { + dateRange = TimexHelpers.monthDateRange(timex.getYear(), timex.getMonth()); + } else if (timex.getWeekOfYear() != null) { + dateRange = TimexHelpers.yearWeekDateRange(timex.getYear(), timex.getWeekOfYear(), + timex.getWeekend()); + } else { + dateRange = TimexHelpers.yearDateRange(timex.getYear()); + } + return new TimexRange() { + { + setStart(dateRange.getLeft()); + setEnd(dateRange.getRight()); + } + }; + } + } + + return new TimexRange() { + { + setStart(new TimexProperty()); + setEnd(new TimexProperty()); + } + }; + } + + public static TimexRange expandTimeRange(TimexProperty timex) { + if (!timex.getTypes().contains(Constants.TimexTypes.TIME_RANGE)) { + throw new IllegalArgumentException("argument must be a timerange: timex"); + } + + if (timex.getPartOfDay() != null) { + switch (timex.getPartOfDay()) { + case "DT": + timex = new TimexProperty(TimexCreator.DAYTIME); + break; + case "MO": + timex = new TimexProperty(TimexCreator.MORNING); + break; + case "AF": + timex = new TimexProperty(TimexCreator.AFTERNOON); + break; + case "EV": + timex = new TimexProperty(TimexCreator.EVENING); + break; + case "NI": + timex = new TimexProperty(TimexCreator.NIGHT); + break; + default: + throw new IllegalArgumentException("unrecognized part of day timerange: timex"); + } + } + + Integer hour = timex.getHour(); + Integer minute = timex.getMinute(); + Integer second = timex.getSecond(); + TimexProperty start = new TimexProperty() { + { + setHour(hour); + setMinute(minute); + setSecond(second); + } + }; + TimexProperty duration = TimexHelpers.cloneDuration(timex); + + return new TimexRange() { + { + setStart(start); + setEnd(TimexHelpers.timeAdd(start, duration)); + setDuration(duration); + } + }; + } + + public static TimexProperty timexDateAdd(TimexProperty start, TimexProperty duration) { + if (start.getDayOfWeek() != null) { + TimexProperty end = start.clone(); + if (duration.getDays() != null) { + Integer newDayOfWeek = end.getDayOfWeek() + (int)Math.round(duration.getDays().doubleValue()); + end.setDayOfWeek(newDayOfWeek); + } + + return end; + } + + if (start.getMonth() != null && start.getDayOfMonth() != null) { + Double durationDays = null; + if (duration.getDays() != null) { + durationDays = duration.getDays().doubleValue(); + } + + if (durationDays == null && duration.getWeeks() != null) { + durationDays = 7 * duration.getWeeks().doubleValue(); + } + + if (durationDays != null) { + if (start.getYear() != null) { + LocalDateTime d = LocalDateTime.of(start.getYear(), start.getMonth(), start.getDayOfMonth(), 0, 0, + 0); + LocalDateTime d2 = d.plusDays(durationDays.longValue()); + return new TimexProperty() { + { + setYear(d2.getYear()); + setMonth(d2.getMonthValue()); + setDayOfMonth(d2.getDayOfMonth()); + } + }; + } else { + LocalDateTime d = LocalDateTime.of(2001, start.getMonth(), start.getDayOfMonth(), 0, 0, 0); + LocalDateTime d2 = d.plusDays(durationDays.longValue()); + return new TimexProperty() { + { + setMonth(d2.getMonthValue()); + setDayOfMonth(d2.getDayOfMonth()); + } + }; + } + } + + if (duration.getYears() != null) { + if (start.getYear() != null) { + return new TimexProperty() { + { + setYear(start.getYear() + (int)Math.round(duration.getYears().doubleValue())); + setMonth(start.getMonth()); + setDayOfMonth(start.getDayOfMonth()); + } + }; + } + } + + if (duration.getMonths() != null) { + if (start.getMonth() != null) { + return new TimexProperty() { + { + setYear(start.getYear()); + setMonth(start.getMonth() + (int)Math.round(duration.getMonths().doubleValue())); + setDayOfMonth(start.getDayOfMonth()); + } + }; + } + } + } + + return start; + } + + public static String generateCompoundDurationTimex(List timexList) { + Boolean isTimeDurationAlreadyExist = false; + StringBuilder timexBuilder = new StringBuilder(Constants.GENERAL_PERIOD_PREFIX); + + for (String timexComponent : timexList) { + // The Time Duration component occurs first time + if (!isTimeDurationAlreadyExist && isTimeDurationTimex(timexComponent)) { + timexBuilder.append(Constants.TIME_TIMEX_PREFIX.concat(getDurationTimexWithoutPrefix(timexComponent))); + isTimeDurationAlreadyExist = true; + } else { + timexBuilder.append(getDurationTimexWithoutPrefix(timexComponent)); + } + } + + return timexBuilder.toString(); + } + + public static String generateDateTimex(Integer year, Integer monthOrWeekOfYear, Integer day, Integer weekOfMonth, + boolean byWeek) { + String yearString = year == Constants.INVALID_VALUE ? Constants.TIMEX_FUZZY_YEAR + : TimexDateHelpers.fixedFormatNumber(year, 4); + String monthWeekString = monthOrWeekOfYear == Constants.INVALID_VALUE ? Constants.TIMEX_FUZZY_MONTH + : TimexDateHelpers.fixedFormatNumber(monthOrWeekOfYear, 2); + String dayString; + if (byWeek) { + dayString = day.toString(); + if (weekOfMonth != Constants.INVALID_VALUE) { + monthWeekString = monthWeekString + String.format("-%s-", Constants.TIMEX_FUZZY_WEEK) + + weekOfMonth.toString(); + } else { + monthWeekString = Constants.TIMEX_WEEK + monthWeekString; + } + } else { + dayString = day == Constants.INVALID_VALUE ? Constants.TIMEX_FUZZY_DAY + : TimexDateHelpers.fixedFormatNumber(day, 2); + } + + return String.join("-", yearString, monthWeekString, dayString); + } + + public static String generateDurationTimex(TimexUnit unit, BigDecimal value) { + if (value.intValue() == Constants.INVALID_VALUE) { + return new String(); + } + + StringBuilder timexBuilder = new StringBuilder(Constants.GENERAL_PERIOD_PREFIX); + if (TimeTimexUnitList.contains(unit)) { + timexBuilder.append(Constants.TIME_TIMEX_PREFIX); + } + + timexBuilder.append(value.toString()); + timexBuilder.append(TIMEX_UNIT_TO_STRING_MAP.get(unit)); + return timexBuilder.toString(); + } + + public static TimexProperty timexTimeAdd(TimexProperty start, TimexProperty duration) { + + TimexProperty result = start.clone(); + if (duration.getMinutes() != null) { + result.setMinute(result.getMinute() + (int)Math.round(duration.getMinutes().doubleValue())); + + if (result.getMinute() > 59) { + result.setHour(((result.getHour() != null) ? result.getHour() : 0) + 1); + result.setMinute(result.getMinute() % 60); + } + } + + if (duration.getHours() != null) { + result.setHour(result.getHour() + (int)Math.round(duration.getHours().doubleValue())); + } + + if (result.getHour() != null && result.getHour() > 23) { + Double days = Math.floor(result.getHour() / 24d); + Integer hour = result.getHour() % 24; + result.setHour(hour); + + if (result.getYear() != null && result.getMonth() != null && result.getDayOfMonth() != null) { + LocalDateTime d = LocalDateTime.of(result.getYear(), result.getMonth(), result.getDayOfMonth(), 0, 0, + 0); + d = d.plusDays(days.longValue()); + + result.setYear(d.getYear()); + result.setMonth(d.getMonthValue()); + result.setDayOfMonth(d.getDayOfMonth()); + + return result; + } + + if (result.getDayOfWeek() != null) { + result.setDayOfWeek(result.getDayOfWeek() + (int)Math.round(days)); + return result; + } + } + + return result; + } + + public static TimexProperty timexDateTimeAdd(TimexProperty start, TimexProperty duration) { + return TimexHelpers.timexTimeAdd(TimexHelpers.timexDateAdd(start, duration), duration); + } + + public static LocalDateTime dateFromTimex(TimexProperty timex) { + Integer year = timex.getYear() != null ? timex.getYear() : 2001; + Integer month = timex.getMonth() != null ? timex.getMonth() : 1; + Integer day = timex.getDayOfMonth() != null ? timex.getDayOfMonth() : 1; + Integer hour = timex.getHour() != null ? timex.getHour() : 0; + Integer minute = timex.getMinute() != null ? timex.getMinute() : 0; + Integer second = timex.getSecond() != null ? timex.getSecond() : 0; + LocalDateTime date = LocalDateTime.of(year, month, day, hour, minute, second); + + return date; + } + + public static Time timeFromTimex(TimexProperty timex) { + Integer hour = timex.getHour() != null ? timex.getHour() : 0; + Integer minute = timex.getMinute() != null ? timex.getMinute() : 0; + Integer second = timex.getSecond() != null ? timex.getSecond() : 0; + return new Time(hour, minute, second); + } + + public static DateRange dateRangeFromTimex(TimexProperty timex) { + TimexRange expanded = TimexHelpers.expandDateTimeRange(timex); + return new DateRange() { + { + setStart(TimexHelpers.dateFromTimex(expanded.getStart())); + setEnd(TimexHelpers.dateFromTimex(expanded.getEnd())); + } + }; + } + + public static TimeRange timeRangeFromTimex(TimexProperty timex) { + TimexRange expanded = TimexHelpers.expandTimeRange(timex); + return new TimeRange() { + { + setStart(TimexHelpers.timeFromTimex(expanded.getStart())); + setEnd(TimexHelpers.timeFromTimex(expanded.getEnd())); + } + }; + } + + public static String formatResolvedDateValue(String dateValue, String timeValue) { + return String.format("%1$s %2$s", dateValue, timeValue); + } + + public static Pair monthWeekDateRange(Integer year, Integer month, + Integer weekOfMonth) { + LocalDateTime start = TimexHelpers.generateMonthWeekDateStart(year, month, weekOfMonth); + LocalDateTime end = start.plusDays(7); + TimexProperty value1 = new TimexProperty() { + { + setYear(start.getYear()); + setMonth(start.getMonth().getValue()); + setDayOfMonth(start.getDayOfMonth()); + } + }; + TimexProperty value2 = new TimexProperty() { + { + setYear(end.getYear()); + setMonth(end.getMonth().getValue()); + setDayOfMonth(end.getDayOfMonth()); + } + }; + return Pair.of(value1, value2); + } + + public static Pair monthDateRange(Integer year, Integer month) { + TimexProperty value1 = new TimexProperty() { + { + setYear(year); + setMonth(month); + setDayOfMonth(1); + } + }; + TimexProperty value2 = new TimexProperty() { + { + setYear(month == 12 ? year + 1 : year); + setMonth(month == 12 ? 1 : month + 1); + setDayOfMonth(1); + } + }; + return Pair.of(value1, value2); + } + + public static Pair yearDateRange(Integer year) { + TimexProperty value1 = new TimexProperty() { + { + setYear(year); + setMonth(1); + setDayOfMonth(1); + } + }; + TimexProperty value2 = new TimexProperty() { + { + setYear(year + 1); + setMonth(1); + setDayOfMonth(1); + } + }; + return Pair.of(value1, value2); + } + + public static Pair yearWeekDateRange(Integer year, Integer weekOfYear, + Boolean isWeekend) { + LocalDateTime firstMondayInWeek = TimexHelpers.firstDateOfWeek(year, weekOfYear, null); + + LocalDateTime start = (isWeekend == null || !isWeekend) ? firstMondayInWeek + : TimexDateHelpers.dateOfNextDay(DayOfWeek.SATURDAY, firstMondayInWeek); + LocalDateTime end = firstMondayInWeek.plusDays(7); + TimexProperty value1 = new TimexProperty() { + { + setYear(start.getYear()); + setMonth(start.getMonth().getValue()); + setDayOfMonth(start.getDayOfMonth()); + } + }; + TimexProperty value2 = new TimexProperty() { + { + setYear(end.getYear()); + setMonth(end.getMonth().getValue()); + setDayOfMonth(end.getDayOfMonth()); + } + }; + return Pair.of(value1, value2); + } + + // this is based on + // https://stackoverflow.com/questions/19901666/get-date-of-first-and-last-day-of-week-knowing-week-number/34727270 + public static LocalDateTime firstDateOfWeek(Integer year, Integer weekOfYear, Locale cultureInfo) { + // ISO uses FirstFourDayWeek, and Monday as first day of week, according to + // https://en.wikipedia.org/wiki/ISO_8601 + LocalDateTime jan1 = LocalDateTime.of(year, 1, 1, 0, 0); + Integer daysOffset = DayOfWeek.MONDAY.getValue() - TimexDateHelpers.getUSDayOfWeek(jan1.getDayOfWeek()); + LocalDateTime firstWeekDay = jan1; + firstWeekDay = firstWeekDay.plusDays(daysOffset); + + TemporalField woy = WeekFields.ISO.weekOfYear(); + Integer firstWeek = jan1.get(woy); + + if ((firstWeek <= 1 || firstWeek >= 52) && daysOffset >= -3) { + weekOfYear -= 1; + } + + firstWeekDay = firstWeekDay.plusDays(weekOfYear * 7); + + return firstWeekDay; + } + + public static LocalDateTime generateMonthWeekDateStart(Integer year, Integer month, Integer weekOfMonth) { + LocalDateTime dateInWeek = LocalDateTime.of(year, month, 1 + ((weekOfMonth - 1) * 7), 0, 0); + + // Align the date of the week according to Thursday, base on ISO 8601, + // https://en.wikipedia.org/wiki/ISO_8601 + if (dateInWeek.getDayOfWeek().getValue() > DayOfWeek.THURSDAY.getValue()) { + dateInWeek = dateInWeek.plusDays(7 - dateInWeek.getDayOfWeek().getValue() + 1); + } else { + dateInWeek = dateInWeek.plusDays(1 - dateInWeek.getDayOfWeek().getValue()); + } + + return dateInWeek; + } + + private static TimexProperty timeAdd(TimexProperty start, TimexProperty duration) { + Integer second = start.getSecond() + + (int)(duration.getSeconds() != null ? duration.getSeconds().intValue() : 0); + Integer minute = start.getMinute() + second / 60 + + (duration.getMinutes() != null ? duration.getMinutes().intValue() : 0); + Integer hour = start.getHour() + (minute / 60) + + (duration.getHours() != null ? duration.getHours().intValue() : 0); + + return new TimexProperty() { + { + setHour((hour == 24 && minute % 60 == 0 && second % 60 == 0) ? hour : hour % 24); + setMinute(minute % 60); + setSecond(second % 60); + } + }; + } + + private static TimexProperty cloneDateTime(TimexProperty timex) { + TimexProperty result = timex.clone(); + result.setYears(null); + result.setMonths(null); + result.setWeeks(null); + result.setDays(null); + result.setHours(null); + result.setMinutes(null); + result.setSeconds(null); + return result; + } + + private static TimexProperty cloneDuration(TimexProperty timex) { + TimexProperty result = timex.clone(); + result.setYear(null); + result.setMonth(null); + result.setDayOfMonth(null); + result.setDayOfWeek(null); + result.setWeekOfYear(null); + result.setWeekOfMonth(null); + result.setSeason(null); + result.setHour(null); + result.setMinute(null); + result.setSecond(null); + result.setWeekend(null); + result.setPartOfDay(null); + return result; + } + + private static Boolean isTimeDurationTimex(String timex) { + return timex.startsWith(Constants.GENERAL_PERIOD_PREFIX.concat(Constants.TIME_TIMEX_PREFIX)); + } + + private static String getDurationTimexWithoutPrefix(String timex) { + // Remove "PT" prefix for TimeDuration, Remove "P" prefix for DateDuration + return timex.substring(isTimeDurationTimex(timex) ? 2 : 1); + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexInference.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexInference.java new file mode 100644 index 000000000..b7e8d11b6 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexInference.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.util.HashSet; + +public class TimexInference { + public static HashSet infer(TimexProperty timexProperty) { + HashSet types = new HashSet(); + + if (TimexInference.isPresent(timexProperty)) { + types.add(Constants.TimexTypes.PRESENT); + } + + if (TimexInference.isDefinite(timexProperty)) { + types.add(Constants.TimexTypes.DEFINITE); + } + + if (TimexInference.isDate(timexProperty)) { + types.add(Constants.TimexTypes.DATE); + } + + if (TimexInference.isDateRange(timexProperty)) { + types.add(Constants.TimexTypes.DATE_RANGE); + } + + if (TimexInference.isDuration(timexProperty)) { + types.add(Constants.TimexTypes.DURATION); + } + + if (TimexInference.isTime(timexProperty)) { + types.add(Constants.TimexTypes.TIME); + } + + if (TimexInference.isTimeRange(timexProperty)) { + types.add(Constants.TimexTypes.TIME_RANGE); + } + + if (types.contains(Constants.TimexTypes.PRESENT)) { + types.add(Constants.TimexTypes.DATE); + types.add(Constants.TimexTypes.TIME); + } + + if (types.contains(Constants.TimexTypes.TIME) && types.contains(Constants.TimexTypes.DURATION)) { + types.add(Constants.TimexTypes.TIME_RANGE); + } + + if (types.contains(Constants.TimexTypes.DATE) && types.contains(Constants.TimexTypes.TIME)) { + types.add(Constants.TimexTypes.DATE_TIME); + } + + if (types.contains(Constants.TimexTypes.DATE) && types.contains(Constants.TimexTypes.DURATION)) { + types.add(Constants.TimexTypes.DATE_RANGE); + } + + if (types.contains(Constants.TimexTypes.DATE_TIME) && types.contains(Constants.TimexTypes.DURATION)) { + types.add((Constants.TimexTypes.DATE_TIME_RANGE)); + } + + if (types.contains(Constants.TimexTypes.DATE) && types.contains(Constants.TimexTypes.TIME_RANGE)) { + types.add(Constants.TimexTypes.DATE_TIME_RANGE); + } + + return types; + } + + private static Boolean isPresent(TimexProperty timexProperty) { + return timexProperty.getNow() != null && timexProperty.getNow() == true; + } + + private static Boolean isDuration(TimexProperty timexProperty) { + return timexProperty.getYears() != null || timexProperty.getMonths() != null || timexProperty.getWeeks() != null || + timexProperty.getDays() != null | timexProperty.getHours() != null || + timexProperty.getMinutes() != null || timexProperty.getSeconds() != null; + } + + private static Boolean isTime(TimexProperty timexProperty) { + return timexProperty.getHour() != null && timexProperty.getMinute() != null && timexProperty.getSecond() != null; + } + + private static Boolean isDate(TimexProperty timexProperty) { + return timexProperty.getDayOfMonth() != null || timexProperty.getDayOfWeek() != null; + } + + private static Boolean isTimeRange(TimexProperty timexProperty) { + return timexProperty.getPartOfDay() != null; + } + + private static Boolean isDateRange(TimexProperty timexProperty) { + return (timexProperty.getDayOfMonth() == null && timexProperty.getDayOfWeek() == null) && + (timexProperty.getYear() != null || timexProperty.getMonth() != null || + timexProperty.getSeason() != null || timexProperty.getWeekOfYear() != null || + timexProperty.getWeekOfMonth() != null); + } + + private static Boolean isDefinite(TimexProperty timexProperty) { + return timexProperty.getYear() != null & timexProperty.getMonth() != null && timexProperty.getDayOfMonth() != null; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexParsing.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexParsing.java new file mode 100644 index 000000000..6692d755e --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexParsing.java @@ -0,0 +1,56 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.util.HashMap; +import java.util.Map; + +public class TimexParsing { + public static void parseString(String timex, TimexProperty timexProperty) { + // a reference to the present + if (timex == "PRESENT_REF") { + timexProperty.setNow(true); + } else if (timex.startsWith("P")) { + // duration + TimexParsing.extractDuration(timex, timexProperty); + } else if (timex.startsWith("(") && timex.endsWith(")")) { + // range indicated with start and end dates and a duration + TimexParsing.extractStartEndRange(timex, timexProperty); + } else { + // date andt ime and their respective ranges + TimexParsing.extractDateTime(timex, timexProperty); + } + } + + private static void extractDuration(String s, TimexProperty timexProperty) { + Map extracted = new HashMap(); + TimexRegex.extract("period", s, extracted); + timexProperty.assignProperties(extracted); + } + + private static void extractStartEndRange(String s, TimexProperty timexProperty) { + String[] parts = s.substring(1, s.length() - 1).split(","); + + if (parts.length == 3) { + TimexParsing.extractDateTime(parts[0], timexProperty); + TimexParsing.extractDuration(parts[2], timexProperty); + } + } + + private static void extractDateTime(String s, TimexProperty timexProperty) { + Integer indexOfT = s.indexOf("T"); + + if (indexOfT == -1) { + Map extracted = new HashMap(); + TimexRegex.extract("date", s, extracted); + timexProperty.assignProperties(extracted); + + } else { + Map extracted = new HashMap(); + TimexRegex.extract("date", s.substring(0, indexOfT), extracted); + TimexRegex.extract("time", s.substring(indexOfT), extracted); + timexProperty.assignProperties(extracted); + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexProperty.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexProperty.java new file mode 100644 index 000000000..97ac5691c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexProperty.java @@ -0,0 +1,445 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.HashSet; +import java.util.Map; +import java.util.Map.Entry; + +import org.apache.commons.lang3.StringUtils; + +public class TimexProperty { + private Time time; + + private String timexValue; + + private HashSet types; + + private Boolean now; + + private BigDecimal years; + + private BigDecimal months; + + private BigDecimal weeks; + + private BigDecimal days; + + private BigDecimal hours; + + private BigDecimal minutes; + + private BigDecimal seconds; + + private Integer year; + + private Integer month; + + private Integer dayOfMonth; + + private Integer dayOfWeek; + + private String season; + + private Integer weekOfYear; + + private Boolean weekend; + + public Integer weekOfMonth; + + private Integer hour; + + private Integer minute; + + private Integer second; + + private String partOfDay; + + public TimexProperty() { + + } + + public TimexProperty(String timex) { + TimexParsing.parseString(timex, this); + } + + public String getTimexValue() { + return TimexFormat.format(this); + } + + public void setTimexValue(String withTimexValue) { + this.timexValue = withTimexValue; + } + + public HashSet getTypes() { + return TimexInference.infer(this); + } + + public void setTypes(HashSet withTypes) { + this.types = withTypes; + } + + public Boolean getNow() { + return now; + } + + public void setNow(Boolean withNow) { + this.now = withNow; + } + + public BigDecimal getYears() { + return years; + } + + public void setYears(BigDecimal withYears) { + this.years = withYears; + } + + public BigDecimal getMonths() { + return months; + } + + public void setMonths(BigDecimal withMonths) { + this.months = withMonths; + } + + public BigDecimal getWeeks() { + return weeks; + } + + public void setWeeks(BigDecimal withWeeks) { + this.weeks = withWeeks; + } + + public BigDecimal getDays() { + return days; + } + + public void setDays(BigDecimal withDays) { + this.days = withDays; + } + + public BigDecimal getHours() { + return hours; + } + + public void setHours(BigDecimal withHours) { + this.hours = withHours; + } + + public BigDecimal getMinutes() { + return minutes; + } + + public void setMinutes(BigDecimal withMinutes) { + this.minutes = withMinutes; + } + + public BigDecimal getSeconds() { + return seconds; + } + + public void setSeconds(BigDecimal withSeconds) { + this.seconds = withSeconds; + } + + public Integer getYear() { + return year; + } + + public void setYear(Integer withYear) { + this.year = withYear; + } + + public Integer getMonth() { + return month; + } + + public void setMonth(Integer withMonth) { + this.month = withMonth; + } + + public Integer getDayOfMonth() { + return dayOfMonth; + } + + public void setDayOfMonth(Integer withDayOfMonth) { + this.dayOfMonth = withDayOfMonth; + } + + public Integer getDayOfWeek() { + return dayOfWeek; + } + + public void setDayOfWeek(Integer withDayOfWeek) { + this.dayOfWeek = withDayOfWeek; + } + + public String getSeason() { + return season; + } + + public void setSeason(String withSeason) { + this.season = withSeason; + } + + public Integer getWeekOfYear() { + return weekOfYear; + } + + public void setWeekOfYear(Integer withWeekOfYear) { + this.weekOfYear = withWeekOfYear; + } + + public Boolean getWeekend() { + return weekend; + } + + public void setWeekend(Boolean withWeekend) { + this.weekend = withWeekend; + } + + public Integer getWeekOfMonth() { + return weekOfMonth; + } + + public void setWeekOfMonth(Integer withWeekOfMonth) { + this.weekOfMonth = withWeekOfMonth; + } + + public Integer getHour() { + if (this.time != null) { + return this.time.getHour(); + } + + return null; + } + + public void setHour(Integer withHour) { + if (withHour != null) { + if (this.time == null) { + this.time = new Time(withHour, 0, 0); + } else { + this.time.setHour(withHour); + } + } else { + this.time = null; + } + } + + public Integer getMinute() { + if (this.time != null) { + return this.time.getMinute(); + } + + return null; + } + + public void setMinute(Integer withMinute) { + if (withMinute != null) { + if (this.time == null) { + time = new Time(0, withMinute, 0); + } else { + time.setMinute(withMinute); + } + } else { + this.time = null; + } + } + + public Integer getSecond() { + if (this.time != null) { + return this.time.getSecond(); + } + + return null; + } + + public void setSecond(Integer withSecond) { + if (withSecond != null) { + if (this.time == null) { + this.time = new Time(0, 0, withSecond); + } else { + this.time.setSecond(withSecond); + } + } else { + this.time = null; + } + } + + public String getPartOfDay() { + return partOfDay; + } + + public void setPartOfDay(String wthPartOfDay) { + this.partOfDay = wthPartOfDay; + } + + public static TimexProperty fromDate(LocalDateTime date) { + TimexProperty timex = new TimexProperty() { + { + setYear(date.getYear()); + setMonth(date.getMonthValue()); + setDayOfMonth(date.getDayOfMonth()); + } + }; + return timex; + } + + public static TimexProperty fromDateTime(LocalDateTime datetime) { + TimexProperty timex = TimexProperty.fromDate(datetime); + timex.setHour(datetime.getHour()); + timex.setMinute(datetime.getMinute()); + timex.setSecond(datetime.getSecond()); + return timex; + } + + public static TimexProperty fromTime(Time time) { + return new TimexProperty() { + { + setHour(time.getHour()); + setMinute(time.getMinute()); + setSecond(time.getSecond()); + } + }; + } + + @Override + public String toString() { + return TimexConvert.convertTimexToString(this); + } + + public String toNaturalLanguage(LocalDateTime referenceDate) { + return TimexRelativeConvert.convertTimexToStringRelative(this, referenceDate); + } + + public TimexProperty clone() { + Boolean now = this.getNow(); + BigDecimal years = this.getYears(); + BigDecimal months = this.getMonths(); + BigDecimal weeks = this.getWeeks(); + BigDecimal days = this.getDays(); + BigDecimal hours = this.getHours(); + BigDecimal minutes = this.getMinutes(); + BigDecimal seconds = this.getSeconds(); + Integer year = this.getYear(); + Integer month = this.getMonth(); + Integer dayOfMonth = this.getDayOfMonth(); + Integer dayOfWeek = this.getDayOfWeek(); + String season = this.getSeason(); + Integer weekOfYear = this.getWeekOfYear(); + Boolean weekend = this.getWeekend(); + Integer innerWeekOfMonth = this.getWeekOfMonth(); + Integer hour = this.getHour(); + Integer minute = this.getMinute(); + Integer second = this.getSecond(); + String partOfDay = this.getPartOfDay(); + + return new TimexProperty() { + { + setNow(now); + setYears(years); + setMonths(months); + setWeeks(weeks); + setDays(days); + setHours(hours); + setMinutes(minutes); + setSeconds(seconds); + setYear(year); + setMonth(month); + setDayOfMonth(dayOfMonth); + setDayOfWeek(dayOfWeek); + setSeason(season); + setWeekOfYear(weekOfYear); + setWeekend(weekend); + setWeekOfMonth(innerWeekOfMonth); + setHour(hour); + setMinute(minute); + setSecond(second); + setPartOfDay(partOfDay); + } + }; + } + + public void assignProperties(Map source) { + for (Entry item : source.entrySet()) { + + if (StringUtils.isBlank(item.getValue())) { + continue; + } + + switch (item.getKey()) { + case "year": + setYear(Integer.parseInt(item.getValue())); + break; + case "month": + setMonth(Integer.parseInt(item.getValue())); + break; + case "dayOfMonth": + setDayOfMonth(Integer.parseInt(item.getValue())); + break; + case "dayOfWeek": + setDayOfWeek(Integer.parseInt(item.getValue())); + break; + case "season": + setSeason(item.getValue()); + break; + case "weekOfYear": + setWeekOfYear(Integer.parseInt(item.getValue())); + break; + case "weekend": + setWeekend(true); + break; + case "weekOfMonth": + setWeekOfMonth(Integer.parseInt(item.getValue())); + break; + case "hour": + setHour(Integer.parseInt(item.getValue())); + break; + case "minute": + setMinute(Integer.parseInt(item.getValue())); + break; + case "second": + setSecond(Integer.parseInt(item.getValue())); + break; + case "partOfDay": + setPartOfDay(item.getValue()); + break; + case "dateUnit": + this.assignDateDuration(source); + break; + case "hourAmount": + setHours(new BigDecimal(item.getValue())); + break; + case "minuteAmount": + setMinutes(new BigDecimal(item.getValue())); + break; + case "secondAmount": + setSeconds(new BigDecimal(item.getValue())); + break; + default: + } + } + } + + private void assignDateDuration(Map source) { + switch (source.get("dateUnit")) { + case "Y": + this.years = new BigDecimal(source.get("amount")); + break; + case "M": + this.months = new BigDecimal(source.get("amount")); + break; + case "W": + this.weeks = new BigDecimal(source.get("amount")); + break; + case "D": + this.days = new BigDecimal(source.get("amount")); + break; + default: + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRange.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRange.java new file mode 100644 index 000000000..f15efd61a --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRange.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +public class TimexRange { + private TimexProperty start; + + private TimexProperty end; + + private TimexProperty duration; + + public TimexProperty getStart() { + return start; + } + + public void setStart(TimexProperty withStart) { + this.start = withStart; + } + + public TimexProperty getEnd() { + return end; + } + + public void setEnd(TimexProperty withEnd) { + this.end = withEnd; + } + + public TimexProperty getDuration() { + return duration; + } + + public void setDuration(TimexProperty withDuration) { + this.duration = withDuration; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRangeResolver.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRangeResolver.java new file mode 100644 index 000000000..c1f2bc76c --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/expression/TimexRangeResolver.java @@ -0,0 +1,266 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.recognizers.datatypes.timex.expression; + +import java.time.DayOfWeek; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +public class TimexRangeResolver { + public static List evaluate(Set candidates, List constraints) { + List timexConstraints = constraints.stream().map(x -> { + return new TimexProperty(x); + }).collect(Collectors.toList()); + Set candidatesWithDurationsResolved = TimexRangeResolver.resolveDurations(candidates, timexConstraints); + Set candidatesAccordingToDate = TimexRangeResolver + .resolveByDateRangeConstraints(candidatesWithDurationsResolved, timexConstraints); + Set candidatesWithAddedTime = TimexRangeResolver.resolveByTimeConstraints(candidatesAccordingToDate, + timexConstraints); + Set candidatesFilteredByTime = TimexRangeResolver.resolveByTimeRangeConstraints(candidatesWithAddedTime, + timexConstraints); + + List timexResults = candidatesFilteredByTime.stream().map(x -> { + return new TimexProperty(x); + }).collect(Collectors.toList()); + + return timexResults; + } + + public static Set resolveDurations(Set candidates, List constraints) { + Set results = new HashSet(); + for (String candidate : candidates) { + TimexProperty timex = new TimexProperty(candidate); + if (timex.getTypes().contains(Constants.TimexTypes.DURATION)) { + List r = TimexRangeResolver.resolveDuration(timex, constraints); + for (TimexProperty resolved : r) { + results.add(resolved.getTimexValue()); + } + } else { + results.add(candidate); + } + } + + return results; + } + + private static List resolveDuration(TimexProperty candidate, List constraints) { + List results = new ArrayList(); + for (TimexProperty constraint : constraints) { + if (constraint.getTypes().contains(Constants.TimexTypes.DATE_TIME)) { + results.add(TimexHelpers.timexDateTimeAdd(constraint, candidate)); + } else if (constraint.getTypes().contains(Constants.TimexTypes.TIME)) { + results.add(TimexHelpers.timexTimeAdd(constraint, candidate)); + } + } + + return results; + } + + private static Set resolveByDateRangeConstraints(Set candidates, + List timexConstraints) { + List dateRangeconstraints = timexConstraints.stream().filter(timex -> { + return timex.getTypes().contains(Constants.TimexTypes.DATE_RANGE); + }).map(timex -> { + return TimexHelpers.dateRangeFromTimex(timex); + }).collect(Collectors.toList()); + + List collapseDateRanges = TimexConstraintsHelper.collapseDateRanges(dateRangeconstraints); + + if (collapseDateRanges.isEmpty()) { + return candidates; + } + + List resolution = new ArrayList(); + for (String timex : candidates) { + List r = TimexRangeResolver.resolveDate(new TimexProperty(timex), collapseDateRanges); + resolution.addAll(r); + } + + return TimexRangeResolver.removeDuplicates(resolution); + } + + private static List resolveDate(TimexProperty timex, List constraints) { + List result = new ArrayList(); + for (DateRange constraint : constraints) { + result.addAll(TimexRangeResolver.resolveDateAgainstConstraint(timex, constraint)); + } + + return result; + } + + private static Set resolveByTimeRangeConstraints(Set candidates, + List timexConstrainst) { + List timeRangeConstraints = timexConstrainst.stream().filter(timex -> { + return timex.getTypes().contains(Constants.TimexTypes.TIME_RANGE); + }).map(timex -> { + return TimexHelpers.timeRangeFromTimex(timex); + }).collect(Collectors.toList()); + + List collapsedTimeRanges = TimexConstraintsHelper.collapseTimeRanges(timeRangeConstraints); + + if (collapsedTimeRanges.isEmpty()) { + return candidates; + } + + List resolution = new ArrayList(); + for (String timex : candidates) { + TimexProperty t = new TimexProperty(timex); + if (t.getTypes().contains(Constants.TimexTypes.TIME_RANGE)) { + List r = TimexRangeResolver.resolveTimeRange(t, collapsedTimeRanges); + resolution.addAll(r); + } else if (t.getTypes().contains(Constants.TimexTypes.TIME)) { + List r = TimexRangeResolver.resolveTime(t, collapsedTimeRanges); + resolution.addAll(r); + } + } + + return TimexRangeResolver.removeDuplicates(resolution); + } + + private static List resolveTimeRange(TimexProperty timex, List constraints) { + TimeRange candidate = TimexHelpers.timeRangeFromTimex(timex); + + List result = new ArrayList(); + for (TimeRange constraint : constraints) { + if (TimexConstraintsHelper.isOverlapping(candidate, constraint)) { + Integer start = Math.max(candidate.getStart().getTime(), constraint.getStart().getTime()); + Time time = new Time(start); + + // TODO: consider a method on TimexProperty to do this clone/overwrite pattern + TimexProperty resolved = timex.clone(); + resolved.setPartOfDay(null); + resolved.setSeconds(null); + resolved.setMinutes(null); + resolved.setHours(null); + resolved.setSecond(time.getSecond()); + resolved.setMinute(time.getMinute()); + resolved.setHour(time.getHour()); + + result.add(resolved.getTimexValue()); + } + } + + return result; + } + + private static List resolveTime(TimexProperty timex, List constraints) { + List result = new ArrayList(); + for (TimeRange constraint : constraints) { + result.addAll(TimexRangeResolver.resolveTimeAgainstConstraint(timex, constraint)); + } + + return result; + } + + private static List resolveTimeAgainstConstraint(TimexProperty timex, TimeRange constraint) { + Time t = new Time(timex.getHour(), timex.getMinute(), timex.getSecond()); + if (t.getTime() >= constraint.getStart().getTime() && t.getTime() < constraint.getEnd().getTime()) { + return new ArrayList() { + { + add(timex.getTimexValue()); + } + }; + } + + return new ArrayList(); + } + + private static Set removeDuplicates(List original) { + return new HashSet(original); + } + + private static List resolveDefiniteAgainstConstraint(TimexProperty timex, DateRange constraint) { + LocalDateTime timexDate = TimexHelpers.dateFromTimex(timex); + if (timexDate.compareTo(constraint.getStart()) >= 0 && timexDate.compareTo(constraint.getEnd()) < 0) { + return new ArrayList() { + { + add(timex.getTimexValue()); + } + }; + } + + return new ArrayList(); + } + + private static List resolveDateAgainstConstraint(TimexProperty timex, DateRange constraint) { + if (timex.getMonth() != null && timex.getDayOfMonth() != null) { + List result = new ArrayList(); + for (int year = constraint.getStart().getYear(); year <= constraint.getEnd() + .getYear(); year++) { + TimexProperty t = timex.clone(); + t.setYear(year); + result.addAll(TimexRangeResolver.resolveDefiniteAgainstConstraint(t, constraint)); + } + + return result; + } + + if (timex.getDayOfWeek() != null) { + // convert between ISO day of week and .NET day of week + DayOfWeek day = timex.getDayOfWeek() == 7 ? DayOfWeek.SUNDAY : DayOfWeek.of(timex.getDayOfWeek()); + List dates = TimexDateHelpers.datesMatchingDay(day, constraint.getStart(), constraint.getEnd()); + List result = new ArrayList(); + + for (LocalDateTime d : dates) { + TimexProperty t = timex.clone(); + t.setDayOfWeek(null); + t.setYear(d.getYear()); + t.setMonth(d.getMonthValue()); + t.setDayOfMonth(d.getDayOfMonth()); + result.add(t.getTimexValue()); + } + + return result; + } + + if (timex.getHour() != null) { + List result = new ArrayList(); + LocalDateTime day = constraint.getStart(); + while (day.compareTo(constraint.getEnd()) <= 0) { + TimexProperty t = timex.clone(); + t.setYear(day.getYear()); + t.setMonth(day.getMonthValue()); + t.setDayOfMonth(day.getDayOfMonth()); + result.addAll(TimexRangeResolver.resolveDefiniteAgainstConstraint(t, constraint)); + day = day.plusDays(1); + } + + return result; + } + + return new ArrayList(); + } + + private static Set resolveByTimeConstraints(Set candidates, List timexConstrainst) { + List + + + com.microsoft.azure + azure-documentdb + 2.4.3 + + + + com.microsoft.bot + bot-builder + + + com.microsoft.bot + bot-integration-core + + + com.microsoft.bot + bot-dialogs + + com.microsoft.bot bot-builder + ${project.version} + test-jar + test diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java new file mode 100644 index 000000000..0c54444be --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * Helper class to escape CosmosDB keys. + */ +public final class CosmosDbKeyEscape { + + private CosmosDbKeyEscape() { + // not called + } + + private static final Integer ESCAPE_LENGTH = 3; + + /** + * Older libraries had a max key length of 255. The limit is now 1023. In this + * library, 255 remains the default for backwards compat. To override this + * behavior, and use the longer limit set + * CosmosDbPartitionedStorageOptions.CompatibilityMode to false. + * https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits. + */ + public static final Integer MAX_KEY_LENGTH = 255; + + /** + * The list of illegal characters for Cosmos DB Keys comes from this list on the + * CosmostDB docs: + * https://docs.microsoft.com/dotnet/api/microsoft.azure.documents.resource.id?view=azure-dotnet#remarks + * + * Note: We are also escaping the "*" character, as that what we're using as our + * escape character. + * + * Note: The Java version escapes more than .NET since otherwise it errors out. + * The additional characters are quote, single quote, semi-colon. + */ + private static final char[] ILLEGAL_KEYS = new char[] {'\\', '?', '/', '#', '*', ';', '\"', '\''}; + + /** + * We are escaping illegal characters using a "*{AsciiCodeInHex}" pattern. This + * means a key of "?test?" would be escaped as "*3ftest*3f". + */ + private static final Map ILLEGAL_KEY_CHARACTER_REPLACEMENT_MAP = Arrays + .stream(ArrayUtils.toObject(ILLEGAL_KEYS)) + .collect(Collectors.toMap(c -> c, c -> "*" + String.format("%02x", (int) c))); + + /** + * Converts the key into a DocumentID that can be used safely with Cosmos DB. + * + * @param key The key to escape. + * @return An escaped key that can be used safely with CosmosDB. + * + * @see #ILLEGAL_KEYS + */ + public static String escapeKey(String key) { + return escapeKey(key, new String(), true); + } + + /** + * Converts the key into a DocumentID that can be used safely with Cosmos DB. + * + * @param key The key to escape. + * @param suffix The string to add at the end of all row keys. + * @param compatibilityMode True if running in compatability mode and keys + * should be truncated in order to support previous + * CosmosDb max key length of 255. This behavior can be + * overridden by setting + * {@link CosmosDbPartitionedStorage.compatibilityMode} + * to false. * + * @return An escaped key that can be used safely with CosmosDB. + */ + public static String escapeKey(String key, String suffix, Boolean compatibilityMode) { + if (StringUtils.isBlank(key)) { + throw new IllegalArgumentException("key"); + } + + suffix = suffix == null ? new String() : suffix; + + Integer firstIllegalCharIndex = StringUtils.indexOfAny(key, new String(ILLEGAL_KEYS)); + + // If there are no illegal characters, and the key is within length costraints, + // return immediately and avoid any further processing/allocations + if (firstIllegalCharIndex == -1) { + return truncateKeyIfNeeded(key.concat(suffix), compatibilityMode); + } + + // Allocate a builder that assumes that all remaining characters might be + // replaced + // to avoid any extra allocations + StringBuilder sanitizedKeyBuilder = new StringBuilder( + key.length() + ((key.length() - firstIllegalCharIndex) * ESCAPE_LENGTH)); + + // Add all good characters up to the first bad character to the builder first + for (Integer index = 0; index < firstIllegalCharIndex; index++) { + sanitizedKeyBuilder.append(key.charAt(index)); + } + + Map illegalCharacterReplacementMap = ILLEGAL_KEY_CHARACTER_REPLACEMENT_MAP; + + // Now walk the remaining characters, starting at the first known bad character, + // replacing any bad ones with + // their designated replacement value from the + for (Integer index = firstIllegalCharIndex; index < key.length(); index++) { + Character ch = key.charAt(index); + + // Check if this next character is considered illegal and, if so, append its + // replacement; + // otherwise just append the good character as is + if (illegalCharacterReplacementMap.containsKey(ch)) { + sanitizedKeyBuilder.append(illegalCharacterReplacementMap.get(ch)); + } else { + sanitizedKeyBuilder.append(ch); + } + } + + if (StringUtils.isNotBlank(key)) { + sanitizedKeyBuilder.append(suffix); + } + + return truncateKeyIfNeeded(sanitizedKeyBuilder.toString(), compatibilityMode); + } + + private static String truncateKeyIfNeeded(String key, Boolean truncateKeysForCompatibility) { + if (!truncateKeysForCompatibility) { + return key; + } + + if (key.length() > MAX_KEY_LENGTH) { + String hash = String.format("%x", key.hashCode()); + key = key.substring(0, MAX_KEY_LENGTH - hash.length()) + hash; + } + + return key; + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java new file mode 100644 index 000000000..3a9c32ee1 --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java @@ -0,0 +1,487 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.azure.documentdb.AccessCondition; +import com.microsoft.azure.documentdb.AccessConditionType; +import com.microsoft.azure.documentdb.Database; +import com.microsoft.azure.documentdb.Document; +import com.microsoft.azure.documentdb.DocumentClient; +import com.microsoft.azure.documentdb.DocumentClientException; +import com.microsoft.azure.documentdb.DocumentCollection; +import com.microsoft.azure.documentdb.PartitionKey; +import com.microsoft.azure.documentdb.PartitionKeyDefinition; +import com.microsoft.azure.documentdb.RequestOptions; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.StoreItem; +import com.microsoft.bot.connector.ExecutorFactory; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +/** + * Implements an CosmosDB based storage provider using partitioning for a bot. + */ +public class CosmosDbPartitionedStorage implements Storage { + private Logger logger = LoggerFactory.getLogger(CosmosDbPartitionedStorage.class); + private CosmosDbPartitionedStorageOptions cosmosDbStorageOptions; + private ObjectMapper objectMapper; + private final Object cacheSync = new Object(); + private DocumentClient client; + private Database databaseCache; + private DocumentCollection collectionCache; + + /** + * Initializes a new instance of the CosmosDbPartitionedStorage class. using the + * provided CosmosDB credentials, database ID, and container ID. + * + * @param withCosmosDbStorageOptions Cosmos DB partitioned storage configuration + * options. + */ + public CosmosDbPartitionedStorage(CosmosDbPartitionedStorageOptions withCosmosDbStorageOptions) { + if (withCosmosDbStorageOptions == null) { + throw new IllegalArgumentException("CosmosDbPartitionStorageOptions is required."); + } + + if (withCosmosDbStorageOptions.getCosmosDbEndpoint() == null) { + throw new IllegalArgumentException("Service EndPoint for CosmosDB is required: cosmosDbEndpoint"); + } + + if (StringUtils.isBlank(withCosmosDbStorageOptions.getAuthKey())) { + throw new IllegalArgumentException("AuthKey for CosmosDB is required: authKey"); + } + + if (StringUtils.isBlank(withCosmosDbStorageOptions.getDatabaseId())) { + throw new IllegalArgumentException("DatabaseId is required: databaseId"); + } + + if (StringUtils.isBlank(withCosmosDbStorageOptions.getContainerId())) { + throw new IllegalArgumentException("ContainerId is required: containerId"); + } + + Boolean compatibilityMode = withCosmosDbStorageOptions.getCompatibilityMode(); + if (compatibilityMode == null) { + withCosmosDbStorageOptions.setCompatibilityMode(true); + } + + if (StringUtils.isNotBlank(withCosmosDbStorageOptions.getKeySuffix())) { + if (withCosmosDbStorageOptions.getCompatibilityMode()) { + throw new IllegalArgumentException( + "CompatibilityMode cannot be 'true' while using a KeySuffix: withCosmosDbStorageOptions"); + } + + // In order to reduce key complexity, we do not allow invalid characters in a + // KeySuffix + // If the KeySuffix has invalid characters, the EscapeKey will not match + String suffixEscaped = CosmosDbKeyEscape.escapeKey(withCosmosDbStorageOptions.getKeySuffix()); + if (!withCosmosDbStorageOptions.getKeySuffix().equals(suffixEscaped)) { + throw new IllegalArgumentException(String.format("Cannot use invalid Row Key characters: %s %s", + withCosmosDbStorageOptions.getKeySuffix(), "withCosmosDbStorageOptions")); + } + } + + cosmosDbStorageOptions = withCosmosDbStorageOptions; + + objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .findAndRegisterModules().enableDefaultTyping(); + + client = new DocumentClient(cosmosDbStorageOptions.getCosmosDbEndpoint(), cosmosDbStorageOptions.getAuthKey(), + cosmosDbStorageOptions.getConnectionPolicy(), cosmosDbStorageOptions.getConsistencyLevel()); + } + + /** + * Reads storage items from storage. + * + * @param keys A collection of Ids for each item to be retrieved. + * @return A dictionary containing the retrieved items. + */ + @Override + public CompletableFuture> read(String[] keys) { + if (keys == null) { + throw new IllegalArgumentException("keys"); + } + + if (keys.length == 0) { + // No keys passed in, no result to return. + return CompletableFuture.completedFuture(new HashMap<>()); + } + + return getCollection().thenApplyAsync(collection -> { + // Issue all of the reads at once + List> documentFutures = new ArrayList<>(); + for (String key : keys) { + documentFutures.add(getDocumentById(CosmosDbKeyEscape.escapeKey(key, + cosmosDbStorageOptions.getKeySuffix(), cosmosDbStorageOptions.getCompatibilityMode()))); + } + + // Map each returned Document to it's original value. + Map storeItems = new HashMap<>(); + documentFutures.forEach(documentFuture -> { + Document document = documentFuture.join(); + if (document != null) { + try { + // We store everything in a DocumentStoreItem. Get that. + JsonNode stateNode = objectMapper.readTree(document.toJson()); + DocumentStoreItem storeItem = objectMapper.treeToValue(stateNode, DocumentStoreItem.class); + + // DocumentStoreItem contains the original object. + JsonNode dataNode = objectMapper.readTree(storeItem.getDocument()); + Object item = objectMapper.treeToValue(dataNode, Class.forName(storeItem.getType())); + + if (item instanceof StoreItem) { + ((StoreItem) item).setETag(storeItem.getETag()); + } + storeItems.put(storeItem.getReadId(), item); + } catch (IOException | ClassNotFoundException e) { + logger.warn("Error reading from container", e); + } + } + }); + + return storeItems; + }, ExecutorFactory.getExecutor()); + } + + /** + * Inserts or updates one or more items into the Cosmos DB container. + * + * @param changes A dictionary of items to be inserted or updated. The + * dictionary item key is used as the ID for the inserted / + * updated item. + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture write(Map changes) { + if (changes == null) { + throw new IllegalArgumentException("changes"); + } + + if (changes.isEmpty()) { + return CompletableFuture.completedFuture(null); + } + + return getCollection().thenApplyAsync(collection -> { + for (Map.Entry change : changes.entrySet()) { + try { + ObjectNode node = objectMapper.valueToTree(change.getValue()); + + // Remove etag from JSON object that was copied from StoreItem. + // The ETag information is updated as an _etag attribute in the document + // metadata. + node.remove("eTag"); + + DocumentStoreItem documentChange = new DocumentStoreItem() { + { + setId(CosmosDbKeyEscape.escapeKey(change.getKey(), cosmosDbStorageOptions.getKeySuffix(), + cosmosDbStorageOptions.getCompatibilityMode())); + setReadId(change.getKey()); + setDocument(node.toString()); + setType(change.getValue().getClass().getTypeName()); + } + }; + + Document document = new Document(objectMapper.writeValueAsString(documentChange)); + + RequestOptions options = new RequestOptions(); + options.setPartitionKey(new PartitionKey(documentChange.partitionKey())); + + if (change.getValue() instanceof StoreItem) { + String etag = ((StoreItem) change.getValue()).getETag(); + if (!StringUtils.isEmpty(etag)) { + // if we have an etag, do opt. concurrency replace + AccessCondition condition = new AccessCondition(); + condition.setType(AccessConditionType.IfMatch); + condition.setCondition(etag); + + options.setAccessCondition(condition); + } else if (etag != null) { + logger.warn("write change, empty eTag: " + change.getKey()); + continue; + } + } + + client.upsertDocument(collection.getSelfLink(), document, options, true); + + } catch (JsonProcessingException | DocumentClientException e) { + logger.warn("Error upserting document: " + change.getKey(), e); + } + } + + return null; + }); + } + + /** + * Deletes one or more items from the Cosmos DB container. + * + * @param keys An array of Ids for the items to be deleted. + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture delete(String[] keys) { + if (keys == null) { + throw new IllegalArgumentException("keys"); + } + + // issue the deletes in parallel + return getCollection().thenCompose(collection -> Arrays.stream(keys).map(key -> { + String escapedKey = CosmosDbKeyEscape.escapeKey(key, cosmosDbStorageOptions.getKeySuffix(), + cosmosDbStorageOptions.getCompatibilityMode()); + return getDocumentById(escapedKey).thenApplyAsync(document -> { + if (document != null) { + try { + RequestOptions options = new RequestOptions(); + options.setPartitionKey(new PartitionKey(escapedKey)); + + client.deleteDocument(document.getSelfLink(), options); + } catch (DocumentClientException e) { + logger.warn("Unable to delete document", e); + throw new CompletionException(e); + } + } + + return null; + }, ExecutorFactory.getExecutor()); + }).collect(CompletableFutures.toFutureList()).thenApply(deleteResponses -> null)); + } + + private Database getDatabase() { + if (databaseCache == null) { + // Get the database if it exists + List databaseList = client + .queryDatabases("SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getDatabaseId() + "'", + null) + .getQueryIterable().toList(); + + if (databaseList.size() > 0) { + // Cache the database object so we won't have to query for it + // later to retrieve the selfLink. + databaseCache = databaseList.get(0); + } else { + // Create the database if it doesn't exist. + try { + Database databaseDefinition = new Database(); + databaseDefinition.setId(cosmosDbStorageOptions.getDatabaseId()); + + databaseCache = client.createDatabase(databaseDefinition, null).getResource(); + } catch (DocumentClientException e) { + // able to query or create the collection. + // Verify your connection, endpoint, and key. + logger.error("getDatabase", e); + throw new RuntimeException(e); + } + } + } + + return databaseCache; + } + + private CompletableFuture getCollection() { + if (collectionCache != null) { + return CompletableFuture.completedFuture(collectionCache); + } + + synchronized (cacheSync) { + if (collectionCache != null) { + return CompletableFuture.completedFuture(collectionCache); + } + + return CompletableFuture.supplyAsync(() -> { + // Get the collection if it exists. + List collectionList = client.queryCollections(getDatabase().getSelfLink(), + "SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getContainerId() + "'", null) + .getQueryIterable().toList(); + + if (collectionList.size() > 0) { + // Cache the collection object so we won't have to query for it + // later to retrieve the selfLink. + collectionCache = collectionList.get(0); + } else { + // Create the collection if it doesn't exist. + try { + DocumentCollection collectionDefinition = new DocumentCollection(); + collectionDefinition.setId(cosmosDbStorageOptions.getContainerId()); + + PartitionKeyDefinition partitionKeyDefinition = new PartitionKeyDefinition(); + partitionKeyDefinition.setPaths(Collections.singleton(DocumentStoreItem.PARTITION_KEY_PATH)); + collectionDefinition.setPartitionKey(partitionKeyDefinition); + + RequestOptions options = new RequestOptions() { + { + setOfferThroughput(cosmosDbStorageOptions.getContainerThroughput()); + } + }; + + collectionCache = client + .createCollection(getDatabase().getSelfLink(), collectionDefinition, options) + .getResource(); + } catch (DocumentClientException e) { + // able to query or create the collection. + // Verify your connection, endpoint, and key. + logger.error("getCollection", e); + throw new RuntimeException("getCollection", e); + } + } + + return collectionCache; + }, ExecutorFactory.getExecutor()); + } + } + + private CompletableFuture getDocumentById(String id) { + return getCollection().thenApplyAsync(collection -> { + // Retrieve the document using the DocumentClient. + List documentList = client + .queryDocuments(collection.getSelfLink(), "SELECT * FROM root r WHERE r.id='" + id + "'", null) + .getQueryIterable().toList(); + + if (documentList.size() > 0) { + return documentList.get(0); + } else { + return null; + } + }, ExecutorFactory.getExecutor()); + } + + /** + * Internal data structure for storing items in a CosmosDB Collection. + */ + private static class DocumentStoreItem implements StoreItem { + // PartitionKey path to be used for this document type + public static final String PARTITION_KEY_PATH = "/id"; + + @JsonProperty(value = "id") + private String id; + + @JsonProperty(value = "realId") + private String readId; + + @JsonProperty(value = "document") + private String document; + + @JsonProperty(value = "_etag") + private String eTag; + + @JsonProperty(value = "type") + private String type; + + /** + * Gets the sanitized Id/Key used as PrimaryKey. + * + * @return The ID. + */ + public String getId() { + return id; + } + + /** + * Sets the sanitized Id/Key used as PrimaryKey. + * + * @param withId The ID. + */ + public void setId(String withId) { + id = withId; + } + + /** + * Gets the un-sanitized Id/Key. + * + * @return The ID. + */ + public String getReadId() { + return readId; + } + + /** + * Sets the un-sanitized Id/Key. + * + * @param withReadId The ID. + */ + public void setReadId(String withReadId) { + readId = withReadId; + } + + /** + * Gets the persisted object. + * + * @return The item data. + */ + public String getDocument() { + return document; + } + + /** + * Sets the persisted object. + * + * @param withDocument The item data. + */ + public void setDocument(String withDocument) { + document = withDocument; + } + + /** + * Get ETag information for handling optimistic concurrency updates. + * + * @return The eTag value. + */ + @Override + public String getETag() { + return eTag; + } + + /** + * Set ETag information for handling optimistic concurrency updates. + * + * @param withETag The eTag value. + */ + @Override + public void setETag(String withETag) { + eTag = withETag; + } + + /** + * The type of the document data. + * + * @return The class name of the data being stored. + */ + public String getType() { + return type; + } + + /** + * The fully qualified class name of the data being stored. + * + * @param withType The class name of the data. + */ + public void setType(String withType) { + type = withType; + } + + /** + * The value used for the PartitionKey. + * + * @return In this case, the id field. + */ + public String partitionKey() { + return id; + } + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java new file mode 100644 index 000000000..196f09a16 --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import com.microsoft.azure.documentdb.ConnectionPolicy; +import com.microsoft.azure.documentdb.ConsistencyLevel; +import com.microsoft.bot.integration.Configuration; + +/** + * Cosmos DB Partitioned Storage Options. + */ +public class CosmosDbPartitionedStorageOptions { + private static final Integer DEFAULT_THROUGHPUT = 400; + private static final ConsistencyLevel DEFAULT_CONSISTENCY = ConsistencyLevel.Session; + + private String cosmosDbEndpoint; + private String authKey; + private String databaseId; + private String containerId; + private String keySuffix; + private Integer containerThroughput; + private ConnectionPolicy connectionPolicy; + private ConsistencyLevel consistencyLevel; + private Boolean compatibilityMode; + + /** + * Constructs an empty options object. + */ + public CosmosDbPartitionedStorageOptions() { + connectionPolicy = ConnectionPolicy.GetDefault(); + consistencyLevel = DEFAULT_CONSISTENCY; + containerThroughput = DEFAULT_THROUGHPUT; + } + + /** + * Construct with properties from Configuration. + * + * @param configuration The Configuration object to read properties from. + */ + public CosmosDbPartitionedStorageOptions(Configuration configuration) { + cosmosDbEndpoint = configuration.getProperty("cosmosdb.dbEndpoint"); + authKey = configuration.getProperty("cosmosdb.authKey"); + databaseId = configuration.getProperty("cosmosdb.databaseId"); + containerId = configuration.getProperty("cosmosdb.containerId"); + + // will likely need to expand this to read policy settings from Configuration. + connectionPolicy = ConnectionPolicy.GetDefault(); + + // will likely need to read consistency level from config. + consistencyLevel = DEFAULT_CONSISTENCY; + + try { + containerThroughput = Integer.parseInt(configuration.getProperty("cosmosdb.throughput")); + } catch (NumberFormatException e) { + containerThroughput = DEFAULT_THROUGHPUT; + } + } + + /** + * Gets the CosmosDB endpoint. + * + * @return The DB endpoint. + */ + public String getCosmosDbEndpoint() { + return cosmosDbEndpoint; + } + + /** + * Sets the CosmosDB endpoint. + * + * @param withCosmosDbEndpoint The DB endpoint to use. + */ + public void setCosmosDbEndpoint(String withCosmosDbEndpoint) { + cosmosDbEndpoint = withCosmosDbEndpoint; + } + + /** + * Gets the authentication key for Cosmos DB. + * + * @return The auth key for the DB. + */ + public String getAuthKey() { + return authKey; + } + + /** + * Sets the authentication key for Cosmos DB. + * + * @param withAuthKey The auth key to use. + */ + public void setAuthKey(String withAuthKey) { + authKey = withAuthKey; + } + + /** + * Gets the database identifier for Cosmos DB instance. + * + * @return The CosmosDB DB id. + */ + public String getDatabaseId() { + return databaseId; + } + + /** + * Sets the database identifier for Cosmos DB instance. + * + * @param withDatabaseId The CosmosDB id. + */ + public void setDatabaseId(String withDatabaseId) { + databaseId = withDatabaseId; + } + + /** + * Gets the container identifier. + * + * @return The container/collection ID. + */ + public String getContainerId() { + return containerId; + } + + /** + * Sets the container identifier. + * + * @param withContainerId The container/collection ID. + */ + public void setContainerId(String withContainerId) { + containerId = withContainerId; + } + + /** + * Gets the ConnectionPolicy for the CosmosDB. + * + * @return The ConnectionPolicy settings. + */ + public ConnectionPolicy getConnectionPolicy() { + return connectionPolicy; + } + + /** + * Sets the ConnectionPolicy for the CosmosDB. + * + * @param withConnectionPolicy The ConnectionPolicy settings. + */ + public void setConnectionPolicy(ConnectionPolicy withConnectionPolicy) { + connectionPolicy = withConnectionPolicy; + } + + /** + * Represents the consistency levels supported for Azure Cosmos DB client + * operations in the Azure Cosmos DB database service. + * + * The requested ConsistencyLevel must match or be weaker than that provisioned + * for the database account. Consistency levels by order of strength are Strong, + * BoundedStaleness, Session and Eventual. + * + * @return The ConsistencyLevel + */ + public ConsistencyLevel getConsistencyLevel() { + return consistencyLevel; + } + + /** + * Represents the consistency levels supported for Azure Cosmos DB client + * operations in the Azure Cosmos DB database service. + * + * The requested ConsistencyLevel must match or be weaker than that provisioned + * for the database account. Consistency levels by order of strength are Strong, + * BoundedStaleness, Session and Eventual. + * + * @param withConsistencyLevel The ConsistencyLevel to use. + */ + public void setConsistencyLevel(ConsistencyLevel withConsistencyLevel) { + consistencyLevel = withConsistencyLevel; + } + + /** + * Gets the throughput set when creating the Container. Defaults to 400. + * + * @return The container throughput. + */ + public Integer getContainerThroughput() { + return containerThroughput; + } + + /** + * Sets the throughput set when creating the Container. Defaults to 400. + * + * @param withContainerThroughput The desired thoughput. + */ + public void setContainerThroughput(Integer withContainerThroughput) { + containerThroughput = withContainerThroughput; + } + + /** + * Gets a value indicating whether or not to run in Compatibility Mode. Early + * versions of CosmosDb had a key length limit of 255. Keys longer than this + * were truncated in CosmosDbKeyEscape. This remains the default behavior, but + * can be overridden by setting CompatibilityMode to false. This setting will + * also allow for using older collections where no PartitionKey was specified. + * + * Note: CompatibilityMode cannot be 'true' if KeySuffix is used. + * @return The compatibilityMode + */ + public Boolean getCompatibilityMode() { + return compatibilityMode; + } + + /** + * Sets a value indicating whether or not to run in Compatibility Mode. Early + * versions of CosmosDb had a key length limit of 255. Keys longer than this + * were truncated in CosmosDbKeyEscape. This remains the default behavior, but + * can be overridden by setting CompatibilityMode to false. This setting will + * also allow for using older collections where no PartitionKey was specified. + * + * Note: CompatibilityMode cannot be 'true' if KeySuffix is used. + * + * @param withCompatibilityMode Currently, max key length for cosmosdb is 1023: + * https://docs.microsoft.com/en-us/azure/cosmos-db/concepts-limits#per-item-limits + * The default for backwards compatibility is 255, + * CosmosDbKeyEscape.MaxKeyLength. + */ + public void setCompatibilityMode(Boolean withCompatibilityMode) { + this.compatibilityMode = withCompatibilityMode; + } + + /** + * Gets the suffix to be added to every key. See + * CosmosDbKeyEscape.EscapeKey(string). Note:CompatibilityMode must be set to + * 'false' to use a KeySuffix. When KeySuffix is used, keys will NOT be + * truncated but an exception will be thrown if the key length is longer than + * allowed by CosmosDb. + * + * @return String containing only valid CosmosDb key characters. (e.g. not: + * '\\', '?', '/', '#', '*'). + */ + public String getKeySuffix() { + return keySuffix; + } + + /** + * Sets the suffix to be added to every key. See + * CosmosDbKeyEscape.EscapeKey(string). Note:CompatibilityMode must be set to + * 'false' to use a KeySuffix. When KeySuffix is used, keys will NOT be + * truncated but an exception will be thrown if the key length is longer than + * allowed by CosmosDb. + * + * @param withKeySuffix String containing only valid CosmosDb key characters. + * (e.g. not: '\\', '?', '/', '#', '*'). + */ + public void setKeySuffix(String withKeySuffix) { + this.keySuffix = withKeySuffix; + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java new file mode 100644 index 000000000..28eafcbba --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for bot-integration-core. + */ +package com.microsoft.bot.azure; diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDBKeyEscapeTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDBKeyEscapeTests.java new file mode 100644 index 000000000..1a1d11c4f --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDBKeyEscapeTests.java @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import org.junit.Assert; +import org.junit.Test; + +public class CosmosDBKeyEscapeTests { + @Test(expected = IllegalArgumentException.class) + public void sanitizeKeyShouldFailWithNullKey() { + // Null key should throw + CosmosDbKeyEscape.escapeKey(null); + } + + @Test(expected = IllegalArgumentException.class) + public void sanitizeKeyShouldFailWithEmptyKey() { + // Empty string should throw + CosmosDbKeyEscape.escapeKey(new String()); + } + + @Test(expected = IllegalArgumentException.class) + public void sanitizeKeyShouldFailWithWhitespaceKey() { + // Whitespace key should throw + CosmosDbKeyEscape.escapeKey(" "); + } + + @Test + public void sanitizeKeyShouldNotChangeAValidKey() { + String validKey = "Abc12345"; + String sanitizedKey = CosmosDbKeyEscape.escapeKey(validKey); + Assert.assertEquals(validKey, sanitizedKey); + } + + @Test + public void longKeyShouldBeTruncated() { + StringBuilder tooLongKey = new StringBuilder(); + for (int i = 0; i < CosmosDbKeyEscape.MAX_KEY_LENGTH + 1; i++) { + tooLongKey.append("a"); + } + + String sanitizedKey = CosmosDbKeyEscape.escapeKey(tooLongKey.toString()); + Assert.assertTrue(sanitizedKey.length() <= CosmosDbKeyEscape.MAX_KEY_LENGTH); + + // The resulting key should be: + String hash = String.format("%x", tooLongKey.toString().hashCode()); + String correctKey = sanitizedKey.substring(0, CosmosDbKeyEscape.MAX_KEY_LENGTH - hash.length()) + hash; + + Assert.assertEquals(correctKey, sanitizedKey); + } + + @Test + public void longKeyWithIllegalCharactersShouldBeTruncated() { + StringBuilder tooLongKey = new StringBuilder(); + for (int i = 0; i < CosmosDbKeyEscape.MAX_KEY_LENGTH + 1; i++) { + tooLongKey.append("a"); + } + + String tooLongKeyWithIllegalCharacters = "?test?" + tooLongKey.toString(); + String sanitizedKey = CosmosDbKeyEscape.escapeKey(tooLongKeyWithIllegalCharacters); + + // Verify the key ws truncated + Assert.assertTrue(sanitizedKey.length() <= CosmosDbKeyEscape.MAX_KEY_LENGTH); + + // Make sure the escaping still happened + Assert.assertTrue(sanitizedKey.startsWith("*3ftest*3f")); + } + + @Test + public void sanitizeKeyShouldEscapeIllegalCharacter() + { + // Ascii code of "?" is "3f". + String sanitizedKey = CosmosDbKeyEscape.escapeKey("?test?"); + Assert.assertEquals("*3ftest*3f", sanitizedKey); + + // Ascii code of "/" is "2f". + String sanitizedKey2 = CosmosDbKeyEscape.escapeKey("/test/"); + Assert.assertEquals("*2ftest*2f", sanitizedKey2); + + // Ascii code of "\" is "5c". + String sanitizedKey3 = CosmosDbKeyEscape.escapeKey("\\test\\"); + Assert.assertEquals("*5ctest*5c", sanitizedKey3); + + // Ascii code of "#" is "23". + String sanitizedKey4 = CosmosDbKeyEscape.escapeKey("#test#"); + Assert.assertEquals("*23test*23", sanitizedKey4); + + // Ascii code of "*" is "2a". + String sanitizedKey5 = CosmosDbKeyEscape.escapeKey("*test*"); + Assert.assertEquals("*2atest*2a", sanitizedKey5); + + // Check a compound key + String compoundSanitizedKey = CosmosDbKeyEscape.escapeKey("?#/"); + Assert.assertEquals("*3f*23*2f", compoundSanitizedKey); + } + + @Test + public void collisionsShouldNotHappen() + { + String validKey = "*2atest*2a"; + String validKey2 = "*test*"; + + // If we failed to esacpe the "*", then validKey2 would + // escape to the same value as validKey. To prevent this + // we makes sure to escape the *. + + // Ascii code of "*" is "2a". + String escaped1 = CosmosDbKeyEscape.escapeKey(validKey); + String escaped2 = CosmosDbKeyEscape.escapeKey(validKey2); + + Assert.assertNotEquals(escaped1, escaped2); + } + + @Test + public void longKeyShouldNotBeTruncatedWithFalseCompatibilityMode() { + StringBuilder tooLongKey = new StringBuilder(); + for (int i = 0; i < CosmosDbKeyEscape.MAX_KEY_LENGTH + 1; i++) { + tooLongKey.append("a"); + } + + String sanitizedKey = CosmosDbKeyEscape.escapeKey(tooLongKey.toString(), new String(), false); + Assert.assertEquals(CosmosDbKeyEscape.MAX_KEY_LENGTH + 1, sanitizedKey.length()); + + // The resulting key should be identical + Assert.assertEquals(tooLongKey.toString(), sanitizedKey); + } + + @Test + public void longKeyWithIllegalCharactersShouldNotBeTruncatedWithFalseCompatibilityMode() + { + StringBuilder tooLongKey = new StringBuilder(); + for (int i = 0; i < CosmosDbKeyEscape.MAX_KEY_LENGTH + 1; i++) { + tooLongKey.append("a"); + } + + String longKeyWithIllegalCharacters = "?test?" + tooLongKey.toString(); + String sanitizedKey = CosmosDbKeyEscape.escapeKey(longKeyWithIllegalCharacters, new String(), false); + + // Verify the key was NOT truncated + Assert.assertEquals(longKeyWithIllegalCharacters.length() + 4, sanitizedKey.length()); + + // Make sure the escaping still happened + Assert.assertTrue(sanitizedKey.startsWith("*3ftest*3f")); + } + + @Test + public void keySuffixIsAddedToEndOfKey() + { + String suffix = "test suffix"; + String key = "this is a test"; + String sanitizedKey = CosmosDbKeyEscape.escapeKey(key, suffix, false); + + // Verify the suffix was added to the end of the key + Assert.assertEquals(sanitizedKey, key + suffix); + } +} diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java new file mode 100644 index 000000000..20e28e22c --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java @@ -0,0 +1,272 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import com.microsoft.azure.documentdb.ConnectionPolicy; +import com.microsoft.azure.documentdb.ConsistencyLevel; +import com.microsoft.azure.documentdb.Database; +import com.microsoft.azure.documentdb.DocumentClient; +import com.microsoft.azure.documentdb.DocumentClientException; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.StorageBaseTests; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * The CosmosDB tests require the CosmosDB Emulator to be installed and running. + * + * More info at: https://aka.ms/documentdb-emulator-docs + * + * Also... Java requires the CosmosDB Emulator cert to be installed. See "Export the SSL certificate" in + * the link above to export the cert. Then import the cert into the Java JDK using: + * + * https://docs.microsoft.com/en-us/azure/java/java-sdk-add-certificate-ca-store?view=azure-java-stable + * + * Note: Don't ignore the first step of "At an administrator command prompt, navigate to your JDK's jdk\jre\lib\security folder" + */ +public class CosmosDbPartitionStorageTests extends StorageBaseTests { + private static boolean emulatorIsRunning = false; + private static final String NO_EMULATOR_MESSAGE = "This test requires CosmosDB Emulator! go to https://aka.ms/documentdb-emulator-docs to download and install."; + + private static String CosmosServiceEndpoint = "https://localhost:8081"; + private static String CosmosAuthKey = "C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw=="; + private static String CosmosDatabaseName = "test-db"; + private static String CosmosCollectionName = "bot-storage"; + + private Storage storage; + + @BeforeClass + public static void allTestsInit() throws IOException, InterruptedException, DocumentClientException { + Process p = Runtime.getRuntime().exec + ("cmd /C \"" + System.getenv("ProgramFiles") + "\\Azure Cosmos DB Emulator\\CosmosDB.Emulator.exe\" /GetStatus"); + + int result = p.waitFor(); + if (result == 2) { + emulatorIsRunning = true; + + DocumentClient client = new DocumentClient( + CosmosServiceEndpoint, + CosmosAuthKey, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + createDatabaseIfNeeded(client); + } + } + + @AfterClass + public static void allTestCleanup() throws DocumentClientException { + if (emulatorIsRunning) { + DocumentClient client = new DocumentClient( + CosmosServiceEndpoint, + CosmosAuthKey, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session); + + List databaseList = client + .queryDatabases( + "SELECT * FROM root r WHERE r.id='" + CosmosDatabaseName + + "'", null).getQueryIterable().toList(); + if (databaseList.size() > 0) { + client.deleteDatabase(databaseList.get(0).getSelfLink(), null); + } + } + } + + @Before + public void testInit() { + storage = new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey(CosmosAuthKey); + setContainerId(CosmosCollectionName); + setCosmosDbEndpoint(CosmosServiceEndpoint); + setDatabaseId(CosmosDatabaseName); + }}); + } + + @After + public void testCleanup() { + storage = null; + } + + @Test + public void constructorShouldThrowOnInvalidOptions() { + try { + new CosmosDbPartitionedStorage(null); + Assert.fail("should have thrown for null options"); + } catch(IllegalArgumentException e) { + // all good + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey("test"); + setContainerId("testId"); + setDatabaseId("testDb"); + }}); + Assert.fail("should have thrown for missing end point"); + } catch (IllegalArgumentException e) { + + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey(null); + setContainerId("testId"); + setDatabaseId("testDb"); + setCosmosDbEndpoint("testEndpoint"); + }}); + Assert.fail("should have thrown for missing auth key"); + } catch (IllegalArgumentException e) { + + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey("testAuthKey"); + setContainerId("testId"); + setDatabaseId(null); + setCosmosDbEndpoint("testEndpoint"); + }}); + Assert.fail("should have thrown for missing db id"); + } catch (IllegalArgumentException e) { + + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey("testAuthKey"); + setContainerId(null); + setDatabaseId("testDb"); + setCosmosDbEndpoint("testEndpoint"); + }}); + Assert.fail("should have thrown for missing collection id"); + } catch (IllegalArgumentException e) { + + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey("testAuthKey"); + setContainerId("testId"); + setDatabaseId("testDb"); + setCosmosDbEndpoint("testEndpoint"); + setKeySuffix("?#*test"); + setCompatibilityMode(false); + }}); + Assert.fail("should have thrown for invalid Row Key characters in KeySuffix"); + } catch (IllegalArgumentException e) { + + } + + try { + new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey("testAuthKey"); + setContainerId("testId"); + setDatabaseId("testDb"); + setCosmosDbEndpoint("testEndpoint"); + setKeySuffix("thisisatest"); + setCompatibilityMode(true); + }}); + Assert.fail("should have thrown for CompatibilityMode 'true' while using a KeySuffix"); + } catch (IllegalArgumentException e) { + + } + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void createObjectCosmosDBPartitionTest() { + assertEmulator(); + super.createObjectTest(storage); + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void readUnknownCosmosDBPartitionTest() { + assertEmulator(); + super.readUnknownTest(storage); + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void updateObjectCosmosDBPartitionTest() { + assertEmulator(); + super.updateObjectTest(storage); + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void deleteObjectCosmosDBPartitionTest() { + assertEmulator(); + super.deleteObjectTest(storage); + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void deleteUnknownObjectCosmosDBPartitionTest() { + assertEmulator(); + storage.delete(new String[] {"unknown_delete"}); + } + + // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! + @Test + public void handleCrazyKeysCosmosDBPartition() { + assertEmulator(); + super.handleCrazyKeys(storage); + } + + @Test + public void readingEmptyKeysReturnsEmptyDictionary() { + Map state = storage.read(new String[] {}).join(); + Assert.assertNotNull(state); + Assert.assertEquals(0, state.size()); + } + + @Test(expected = IllegalArgumentException.class) + public void readingNullKeysThrowException() { + storage.read(null).join(); + } + + @Test(expected = IllegalArgumentException.class) + public void writingNullStoreItemsThrowException() { + storage.write(null); + } + + @Test + public void writingNoStoreItemsDoesntThrow() { + storage.write(new HashMap<>()); + } + + private static void createDatabaseIfNeeded(DocumentClient client) throws DocumentClientException { + // Get the database if it exists + List databaseList = client + .queryDatabases( + "SELECT * FROM root r WHERE r.id='" + CosmosDatabaseName + + "'", null).getQueryIterable().toList(); + + if (databaseList.size() == 0) { + // Create the database if it doesn't exist. + Database databaseDefinition = new Database(); + databaseDefinition.setId(CosmosDatabaseName); + + client.createDatabase( + databaseDefinition, null).getResource(); + } + } + + private void assertEmulator() { + if (!emulatorIsRunning) { + Assert.fail(NO_EMULATOR_MESSAGE); + } + } +} From 24dc4e99d6dd17a5e31334f6e70daa303e50084a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 1 Mar 2021 10:32:56 -0600 Subject: [PATCH 091/221] CosmosDB unit tests optional (if emulator is installed). Temporary. (#1026) --- .../azure/CosmosDbPartitionStorageTests.java | 108 +++++++++++------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java index 20e28e22c..f33c6bfad 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java @@ -10,6 +10,7 @@ import com.microsoft.azure.documentdb.DocumentClientException; import com.microsoft.bot.builder.Storage; import com.microsoft.bot.builder.StorageBaseTests; +import java.io.File; import org.junit.After; import org.junit.AfterClass; import org.junit.Assert; @@ -47,20 +48,24 @@ public class CosmosDbPartitionStorageTests extends StorageBaseTests { @BeforeClass public static void allTestsInit() throws IOException, InterruptedException, DocumentClientException { - Process p = Runtime.getRuntime().exec - ("cmd /C \"" + System.getenv("ProgramFiles") + "\\Azure Cosmos DB Emulator\\CosmosDB.Emulator.exe\" /GetStatus"); - - int result = p.waitFor(); - if (result == 2) { - emulatorIsRunning = true; - - DocumentClient client = new DocumentClient( - CosmosServiceEndpoint, - CosmosAuthKey, - ConnectionPolicy.GetDefault(), - ConsistencyLevel.Session); - - createDatabaseIfNeeded(client); + File emulator = new File(System.getenv("ProgramFiles") + "\\Azure Cosmos DB Emulator\\CosmosDB.Emulator.exe"); + if (emulator.exists()) { + Process p = Runtime.getRuntime().exec + ("cmd /C \"" + emulator.getAbsolutePath() + " /GetStatus"); + + int result = p.waitFor(); + if (result == 2) { + emulatorIsRunning = true; + + DocumentClient client = new DocumentClient( + CosmosServiceEndpoint, + CosmosAuthKey, + ConnectionPolicy.GetDefault(), + ConsistencyLevel.Session + ); + + createDatabaseIfNeeded(client); + } } } @@ -85,12 +90,14 @@ public static void allTestCleanup() throws DocumentClientException { @Before public void testInit() { - storage = new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey(CosmosAuthKey); - setContainerId(CosmosCollectionName); - setCosmosDbEndpoint(CosmosServiceEndpoint); - setDatabaseId(CosmosDatabaseName); - }}); + if (emulatorIsRunning) { + storage = new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ + setAuthKey(CosmosAuthKey); + setContainerId(CosmosCollectionName); + setCosmosDbEndpoint(CosmosServiceEndpoint); + setDatabaseId(CosmosDatabaseName); + }}); + } } @After @@ -186,65 +193,83 @@ public void constructorShouldThrowOnInvalidOptions() { // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void createObjectCosmosDBPartitionTest() { - assertEmulator(); - super.createObjectTest(storage); + if (runIfEmulator()) { + super.createObjectTest(storage); + } } // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void readUnknownCosmosDBPartitionTest() { - assertEmulator(); - super.readUnknownTest(storage); + if (runIfEmulator()) { + super.readUnknownTest(storage); + } } // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void updateObjectCosmosDBPartitionTest() { - assertEmulator(); - super.updateObjectTest(storage); + if (runIfEmulator()) { + super.updateObjectTest(storage); + } } // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void deleteObjectCosmosDBPartitionTest() { - assertEmulator(); - super.deleteObjectTest(storage); + if (runIfEmulator()) { + super.deleteObjectTest(storage); + } } // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void deleteUnknownObjectCosmosDBPartitionTest() { - assertEmulator(); - storage.delete(new String[] {"unknown_delete"}); + if (runIfEmulator()) { + storage.delete(new String[]{"unknown_delete"}); + } } // NOTE: THESE TESTS REQUIRE THAT THE COSMOS DB EMULATOR IS INSTALLED AND STARTED !!!!!!!!!!!!!!!!! @Test public void handleCrazyKeysCosmosDBPartition() { - assertEmulator(); - super.handleCrazyKeys(storage); + if (runIfEmulator()) { + super.handleCrazyKeys(storage); + } } @Test public void readingEmptyKeysReturnsEmptyDictionary() { - Map state = storage.read(new String[] {}).join(); - Assert.assertNotNull(state); - Assert.assertEquals(0, state.size()); + if (runIfEmulator()) { + Map state = storage.read(new String[]{}).join(); + Assert.assertNotNull(state); + Assert.assertEquals(0, state.size()); + } } @Test(expected = IllegalArgumentException.class) public void readingNullKeysThrowException() { - storage.read(null).join(); + if (runIfEmulator()) { + storage.read(null).join(); + } else { + throw new IllegalArgumentException("bogus exception"); + } } @Test(expected = IllegalArgumentException.class) public void writingNullStoreItemsThrowException() { - storage.write(null); + if (runIfEmulator()) { + storage.write(null); + } else { + throw new IllegalArgumentException("bogus exception"); + } } @Test public void writingNoStoreItemsDoesntThrow() { - storage.write(new HashMap<>()); + if (runIfEmulator()) { + storage.write(new HashMap<>()); + } } private static void createDatabaseIfNeeded(DocumentClient client) throws DocumentClientException { @@ -264,9 +289,12 @@ private static void createDatabaseIfNeeded(DocumentClient client) throws Documen } } - private void assertEmulator() { + private boolean runIfEmulator() { if (!emulatorIsRunning) { - Assert.fail(NO_EMULATOR_MESSAGE); + System.out.println(NO_EMULATOR_MESSAGE); + return false; } + + return true; } } From e51a84dd1e0ee1bc75d92022dc75a61f4c5a031c Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 5 Mar 2021 09:55:49 -0600 Subject: [PATCH 092/221] Dropped private Sun security imports. Now using pregenerated certs. (#1032) --- .../bot/connector/JwtTokenExtractorTests.java | 106 +++++++----------- .../resources/bot-connector-expired.pkcs12 | Bin 0 -> 2702 bytes .../src/test/resources/bot-connector.key | 27 +++++ .../src/test/resources/bot-connector.pkcs12 | Bin 0 -> 2702 bytes 4 files changed, 66 insertions(+), 67 deletions(-) create mode 100644 libraries/bot-connector/src/test/resources/bot-connector-expired.pkcs12 create mode 100644 libraries/bot-connector/src/test/resources/bot-connector.key create mode 100644 libraries/bot-connector/src/test/resources/bot-connector.pkcs12 diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java index de9604db5..96f6b1bdd 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java @@ -14,15 +14,17 @@ import com.microsoft.bot.connector.authentication.OpenIdMetadataKey; import com.microsoft.bot.connector.authentication.TokenValidationParameters; import java.io.IOException; -import java.math.BigInteger; +import java.io.InputStream; import java.security.GeneralSecurityException; import java.security.KeyPair; -import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.KeyStoreException; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; -import java.security.SecureRandom; +import java.security.UnrecoverableKeyException; import java.security.cert.Certificate; import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.security.interfaces.RSAPrivateKey; import java.security.interfaces.RSAPublicKey; @@ -34,20 +36,21 @@ import java.util.concurrent.CompletionException; import org.junit.Before; import org.junit.Test; -import sun.security.x509.AlgorithmId; -import sun.security.x509.CertificateAlgorithmId; -import sun.security.x509.CertificateSerialNumber; -import sun.security.x509.CertificateValidity; -import sun.security.x509.CertificateVersion; -import sun.security.x509.CertificateX509Key; -import sun.security.x509.X500Name; -import sun.security.x509.X509CertImpl; -import sun.security.x509.X509CertInfo; +/** + * Test Notes: + * + * The PKCS12 certificates were created using these steps: + * https://kb.globalscape.com/Knowledgebase/11039/Generating-a-PKCS12-Private-Key-and-Public-Certificate + * + * For the expired cert, just specify a negative number of days in step #4. + * + * For both valid and expired certs, these unit tests expect the alias for both to be "bot-connector-pkcs12" + * and the password to be "botframework" + */ public class JwtTokenExtractorTests { - private X509Certificate validCertificate; - private X509Certificate expiredCertificate; - private KeyPair keyPair; + private CertInfo valid; + private CertInfo expired; @Before public void setup() throws GeneralSecurityException, IOException { @@ -55,25 +58,15 @@ public void setup() throws GeneralSecurityException, IOException { EmulatorValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false; GovernmentChannelValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false; - // create keys - keyPair = createKeyPair(); - Date now = new Date(); - Date from = new Date(now.getTime() - (10 * 86400000L)); - - // create expired certificate - Date to = new Date(now.getTime() - (9 * 86400000L)); - expiredCertificate = createSelfSignedCertificate(keyPair, from, to); - - // create valid certificate - to = new Date(now.getTime() + (9 * 86400000L)); - validCertificate = createSelfSignedCertificate(keyPair, from, to); + valid = loadCert("bot-connector.pkcs12"); + expired = loadCert("bot-connector-expired.pkcs12"); } @Test(expected = CompletionException.class) public void JwtTokenExtractor_WithExpiredCert_ShouldNotAllowCertSigningKey() { // this should throw a CompletionException (which contains an AuthenticationException) buildExtractorAndValidateToken( - expiredCertificate, keyPair.getPrivate() + expired.cert, expired.keypair.getPrivate() ).join(); } @@ -81,7 +74,7 @@ public void JwtTokenExtractor_WithExpiredCert_ShouldNotAllowCertSigningKey() { public void JwtTokenExtractor_WithValidCert_ShouldAllowCertSigningKey() { // this should not throw buildExtractorAndValidateToken( - validCertificate, keyPair.getPrivate() + valid.cert, valid.keypair.getPrivate() ).join(); } @@ -92,7 +85,7 @@ public void JwtTokenExtractor_WithExpiredToken_ShouldNotAllow() { Date issuedAt = new Date(now.getTime() - 86400000L); buildExtractorAndValidateToken( - expiredCertificate, keyPair.getPrivate(), issuedAt + expired.cert, expired.keypair.getPrivate(), issuedAt ).join(); } @@ -161,45 +154,24 @@ private static TokenValidationParameters createTokenValidationParameters(X509Cer }}; } - private KeyPair createKeyPair() throws NoSuchAlgorithmException { - // note that this isn't allowing for a "kid" value - KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(2048); - return generator.generateKeyPair(); + private static class CertInfo { + public X509Certificate cert; + public KeyPair keypair; } - private static X509Certificate createSelfSignedCertificate( - KeyPair pair, Date from, Date to - ) throws GeneralSecurityException, IOException { - String dn = "CN=Bot, OU=BotFramework, O=Microsoft, C=US"; - String algorithm = "SHA256withRSA"; - - PrivateKey privateKey = pair.getPrivate(); - X509CertInfo info = new X509CertInfo(); - - CertificateValidity interval = new CertificateValidity(from, to); - BigInteger sn = new BigInteger(64, new SecureRandom()); - X500Name owner = new X500Name(dn); - - info.set(X509CertInfo.VALIDITY, interval); - info.set(X509CertInfo.SERIAL_NUMBER, new CertificateSerialNumber(sn)); - info.set(X509CertInfo.SUBJECT, owner); - info.set(X509CertInfo.ISSUER, owner); - info.set(X509CertInfo.KEY, new CertificateX509Key(pair.getPublic())); - info.set(X509CertInfo.VERSION, new CertificateVersion(CertificateVersion.V3)); - AlgorithmId algo = new AlgorithmId(AlgorithmId.sha256WithRSAEncryption_oid); - info.set(X509CertInfo.ALGORITHM_ID, new CertificateAlgorithmId(algo)); - - // Sign the cert to identify the algorithm that's used. - X509CertImpl cert = new X509CertImpl(info); - cert.sign(privateKey, algorithm); - - // Update the algorithm, and resign. - algo = (AlgorithmId)cert.get(X509CertImpl.SIG_ALG); - info.set(CertificateAlgorithmId.NAME + "." + CertificateAlgorithmId.ALGORITHM, algo); - cert = new X509CertImpl(info); - cert.sign(privateKey, algorithm); - return cert; + private static CertInfo loadCert(String pkcs12File) + throws KeyStoreException, CertificateException, NoSuchAlgorithmException, IOException, + UnrecoverableKeyException { + InputStream fis = ClassLoader.getSystemResourceAsStream(pkcs12File); + KeyStore p12 = KeyStore.getInstance("pkcs12"); + p12.load(fis, "botframework".toCharArray()); + + return new CertInfo() {{ + cert = (X509Certificate) p12.getCertificate("bot-connector-pkcs12"); + keypair = new KeyPair(cert.getPublicKey(), + (PrivateKey) p12.getKey("bot-connector-pkcs12", "botframework".toCharArray()) + ); + }}; } private static String encodeCertificate(Certificate certificate) { diff --git a/libraries/bot-connector/src/test/resources/bot-connector-expired.pkcs12 b/libraries/bot-connector/src/test/resources/bot-connector-expired.pkcs12 new file mode 100644 index 0000000000000000000000000000000000000000..ac947006105f57b29bda8445c1067e00aa60484d GIT binary patch literal 2702 zcmZXUc{CJ`7RSe!u?$g?EMvw_)*0EyI;OG>Ll}%TiBPu5-eBxx-y&qEMP-+SvV;ba zEwZExBBJa=&+nY~UhkZD?z!iC@ArGp{qut$LlS^=^awHp$G|KZqZhM#ijI-4hz!vI zks%r76p=wyCsrAd4sap`e-ke@ z26@+e#fWYk`uTnfxa%fXw*T2uy2(ve-ehTMS_EAWsg+2rWc~$zt#kF0(?wMJtvmbI z9a2xFoIP_WP_mecgKlYm#_CSo5qd2p+?dAWYL)n2fKutAtIOkfTPZK-J!ba{?h#LI zoR&?-6Z0DKOOpL+@40D>UY%59qwioer(%MeREssvn2($+%qZRQ z2@ktdowyRaY+j0ZD&m!6vE|L)x)R!qsJtn4D~F$A^geL%>C+~3j*rtClWr9xyj!SI z;!#vH4__&k9$k0e_--DHbl9CTEd4?BW?R5i1(ty1nA4S1c-xp%qW)w$H-~2n>GW0R z=;NTxi7y@4e5#GMpvh}RmBY7CLG8Vr4P2_|Hg*b&&Ty0b(avYJ^v!6^?DyuT@8#cq zDkAmxB@`%|j3wTpTy(Be5w0cx2!?x9l~dOgxyDheo+P!ALN@e5AF*FS<`}ja4SAQ1 zn^UKkPfD&>nvm0rm(-nPJ5Wo!XmN+>z^%BxUkdbLxX4(d{m%`TAdlHBo5(Ll+m&SL zb&NCkqKH>0ZF^GJ!1iud(Q4N9*0x&a%;fWd6(uEKT2>dulg>m5@*Qs7lY~p^b-&0h zn1k03?#u_Lnt^90qk^AwVRdMmVT)@w@|hUl)GT0=@?DWS!+fmrbn|^8{J*tszBFjy zwQ5+DBGt<^DAV0Ib^C37{Nv@LSZPe3Zch_^-Sl}+ z500AF94Ly9&0Ywjq^=5{zuNuhY=j_+#_1GIZu zfW=NTCcNz%3y8Avnt+4>km1@xNEX*5c|!i|JvQkV9Ht>W6;7*AV5l@?L3)h5Ge+N}{8OHLq>?_X|#Vo%7H`sXQQ>UM=_8j3W zo`6Gt$eSVdRk<)XZN?DfnLky*$(FF(vbuQ{48ZNv9Tj6Gxkb1P+G@hbgg(MKtkSn6 zSdPU4+6-%1_pIR5w2hPzOCPz_e)vW&XGnQh=)=9b2_G%~B`iMM;)Z{)sCMr+Ej5k1 zwlWM@PMRUa_G6cb-B1-(=!4kMkB>g%!aZw!>4a_g2z2JC&$lEfQ55blr&L{lMUcV4 z{{yHZGS~w|2D_fv&L{1_!15nlLFj-dTw6Z@+V}sbSXi|FJKN0MJO5A&L?iKLgv8;PAWS z&meI-S6UCkkEIu6!F(GjiOfe_RIJ!nbaFi{#J#krIs+fl^(?av+DGJHj6TKoh-9B$ z+jEJx3`UIP?J5y-364+-?yjgEC4vU^A6=+n*`Tct41P33YZu_xUG%pg{m%t_+d?;r zJ1*15#H95nT3OA`xMV~}yD`%s{s<(go+r@GuW$s`HyUrEWyN?#p=l*iK>zT|yn>lJ zxGBMUBu-bGUVLKNV!P41EO7*NHdRK!?;1(J7{Y03Xfm9ws_d)2Uci#QSQDuDXzVW#|KSR;}|aK-j1sN z#Iq`BqQ*E}a5oX(uX40+HV3MnNinvPP%wZ510p&2)F-dTQ2WanKu@cNBagwp+~@#K zo8s~1;$}C>)C(Od+>zOMY{jICLjMUnKs#7j(u80d|5Od)lTwxj=jJs$Ehv;HhZ{#N z9%>)SLsyF|#nE*UIY0N3BIlG$#n0X%e7ZOdwcNDEz4C2nw3wb%^EjQs9vZoF%D&OnIH=8k#!{V?b@_M(I1&Etp>%sS4ao2U-APiQ;?O<8PX^C+2`Vpl` z-Oc9tpEJ@85#BA1HG;FT3hjfJ-(s}}m!Em<=fE8A?9 zk1F%oX<%bzm|IVXzjUTYFmFe$W^8FNI%S0>`ZQU^P?&d>jodTgZtQV0*;$TI;fN{& z)i@3;Q?0J9TT)O(*Xu@2o-Mv^LR*|GmQv7%Bj6E9F{ax8 z8E#qgxIJ`CYXx*~$9hrdB@46N<57nEx^ZTy_Oa^1I@?2_l4cM0oMLoav#YKEOng9; z>P`sYSCl0g2@%7M`w5u_k_BW{1%vYm$g!|m$)EBS$AJq^axQcc&;7!H9&BN+*S4>* z$oU6@2PRi2m?fd9r)j%6p)Zj32;u+Ik@*~w3&eS3q|(-I$kRGse$XlUuxH^Ql`w-) z{(HoUl!QqDt^vFNM1Tyy`9yD@l*`FPJehq@Ja2%<$>s+@0^|`$gdl>M0W8Hv599>{ nK%6>`{`mVSfGPe%z8QY_yKdgc1_2P$W#hPA)KIO{KY0BYs`TJA literal 0 HcmV?d00001 diff --git a/libraries/bot-connector/src/test/resources/bot-connector.key b/libraries/bot-connector/src/test/resources/bot-connector.key new file mode 100644 index 000000000..e5e43c892 --- /dev/null +++ b/libraries/bot-connector/src/test/resources/bot-connector.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEpAIBAAKCAQEAy8VcIYWh4sF2XcZF7u+lSq7YdE8skZhmtDF5w8GoEoPscGyT +wSeWjK7u+y2g5iAZsXQwzhN1VvOHznAlLrswLaORie7ch9veFeTMT60gLL2w8e6h +RUWxNJiQXjeExwk8Fhvcq7Kpl+qM4iEHvDX6iXCjEkNJ4Ghx48j9siUKMf1T8IcZ +sE3zzaaGOU5ar8NVsf9Kz1kPCOYv8zqykB45nUDsbE/q9cEkumL8ebjd1JApDJf0 +sf/PkftMvwP69QY1CJ/achUsRqDvFGLw+ZyUyXaSdPt92H92vbNzo8hn6GuxnPHy +f/Aoxjy/o5zNocsiiJvgFBFjRBZXWhk3OJWKZwIDAQABAoIBADNiKx9Q4Uea3Uw8 +STo9OAMjH/YEWQrF0XAy4a+ZT9aLab3Xw1J7txz2p9Cy6tXc1l3HHN96TKaGdoJ6 +CQZFsZpwmqybjQS9Tr1amqKk124wz0PSltwu/MZ0ikMX4OWH0J0KnZS2Usm6HZiQ +F7FAM1MhEh3y1dg+vilgb4jSikWcVp7RbcMgwG0N9oQhbPqN+Bv/E5KBpYk1Rdwt +yjSBOdDenx+TF5RrdKYxC3ouKN0BJMgtIIkv+iGce1WMtSAHkNXrgxJ9AhDquREU +Op363fad01Ulvs4Z+IDlEhrpA6oUt5hr8lStFhrhVuDPqctAWz27+YNw5ioHbimA +U48EOzECgYEA7T5dQzKXIzrDha4Spr8KbCMvXsesehWG3RLZxhU7lnjSMAhBuKGU +hZMfjmSSBR/rr7ppMjN4LLmpjqTkhojg/p+MnK9XRSvmGaSxlS1K8hANjShhx6s7 +xWbEsKQEPbuq1m0vCAzxgGkxLpRmM9ajJhFYEoSdVbc/hhVI72mxhiUCgYEA2+GI +ig0Wpbq5D1ISwljWHhpdi4d0MeO+wtogMo46AAX4kpgCEKDmPwX2igJKJjQJyWNB +PqHXPPQJyferun27baiNA5vEDzoCRvyLqKjyLrzSS+wDszR4mYhK8naILPonmIca +9BsUDcQw7x3rzVUlOKFpbRfT0qPVa5qn3T32apsCgYEAmYJvCloj3ZHajhdSzj5z +WgFyV1vQSLbBKy9VZoy6n+TR7G6LSBKVbdEC7Do7GcHL2Us/YlJXgmkoQ7qCfGL5 +YwiODZyPVZzQKOueVK6X/gVRH3NvwakU5ehXgQzACcnzAwhnFEh7w+FNB5zSfNx3 +eNxkJqdUvu/x1KrVJMU5L1kCgYEAkIokYGOUNKOXDTwterY9IpLAVX1YY4dLmfkb +W0BlXiiOq4bjLJ0oXduEolo49f4VRN5LQGnQ/I+Lc8msiK4oLEC1Wd7mNgAzCQjw +oZFVimWzdBcUo5Plhz+xzMsgXzieGMUPcdHvD9GdPUKVBGhpTF3G2ODl7LyoCdEj +cetOdesCgYAIuxFR/89S44Je5maTMkcExZpVTm1D1Zc8EmlHQ+WPjrakZSWFx2TS +o8wUd/mCwCTLRG3S2t3eUZiEi+G9gI8bE/w7ABxNCFAlHbo0SETy7T+9XeznoFbZ +0FyvVLvXQVZhKPVTF0pYkfuHo3ofbotKbTEM62EurroU1dviRJ7Seg== +-----END RSA PRIVATE KEY----- diff --git a/libraries/bot-connector/src/test/resources/bot-connector.pkcs12 b/libraries/bot-connector/src/test/resources/bot-connector.pkcs12 new file mode 100644 index 0000000000000000000000000000000000000000..e9854a3813522f39a933e66a648ddb1de3458cd1 GIT binary patch literal 2702 zcmZXWc{CJ`7RP6%!5AcK!iYrKGIq-7hsj_lYZPT0%VZn-QpCuRE$i4KvPSkbO_3;B z#+IGYpll@>*+*}F=e+lR@0@q;x#xcG_ug~={G1ENL*qce6L37#gqc;EpikK40I~oJ z@K9|q9*SVt8gM+g<)0CA0Ulh=u!=!I0HfsnGXcVdnW6u_z|OdM5)2uWX(Yj;p6G!< z5I_VTj9U(YS60mB6ZIo$!(|J0X%E>fjjUmkyH(&tj>ZK#8zn*Lyu)92`>Vjg*4az~ z9esq0)?eOY{t~y@v(CTw;q?XddDo$`TN383o+U`$kXxJ2a(}(=n|IJmCZ=p_(OHh6 zRyp6>1AV#%7TEZzgnW{8J*9@ueg$#m-zxf3mRu~WUMr^##qJR!z& zNj`U6Sy38K;N~#~P+ex;j#1(;@ff1GArSGNv)UoQpT*$2k081P>zw491@@0D?EL+9 zaERj$r`LOv4|+k0kF}Siy1 zh*O{)Z%ol^1BTw)cG$qmfQoHXwB?QwQA?^@zQZo*R*gyQY0LGxK&A^1*CxNamBy!# zDsRo8vIVut<6h|PaqG~g$}I;n87!eLANqz0T@g%mR$~=Z>=1lBmH1nntKT^18K-&& z+0XxZpmi7eB9^4>)%ANvG`+5aXv~UleD$V`seG zSD*E;ppTmIQ7_lLm?J#XLeWkeZhj>4t4Gy7qu}$4*H@epJ;+Xc+%D2-y^z*x2K}HG zVtw-r#ur$B95!G$*xGa4Z);32qRr^0IkVq^5mTq@li2048m~9qelSbYBsGY~nt7oQ zw%&l;-Ott#dB}XUZ`_D;u8P6a^O-tqSM`=lrws&d7go744e?Bi7;H*g^B%T9tH%+^ zLH0SW&1A)4B|>M6Xd4z{{7s(!NbI5ox*-={j`?E>d4CmmbiJbMcUYqB3oZTUaJA6H z;e>QU?pO+*Blm@kxJ*kc@eYR}b`u}X9`GF(MkdA66vo#jO8N%jtvm(Vm_PVJdmb|>h~<%uO{`+pETw{i9W=>o?y zh5iqq3h+$sU_6s6!**h%9W&d1a0LZ|7+l+60B!C6DJBjld*%05KK`3xU_7`-0@Y>T zNIf-LoN4|s$}f<6^si>tsKwE?3X&ZD5| z0r>i9n`_r;95HWJ&cLEEqKAaOBb6|L^=?udna~`vR7@Z1(%jeVl9Hi=%BYLN_ui?P zy*C}Qd=;x5GoSRhc0`uaXO+C7L;BewcCTPKlSF|Wwb#`6>Q7-4Hda;*Vj>Tc+0{>M z-&m04-eLMYl(U+IQvJ*DKuwuW4Xv`7bZM`S-diWDgUx7#k5k?aAK(FoW3|`k8`~zz zC4x*+B6Ta{j@||j93rLq4hWfNGQ2|(EF*4tzf&dNaim`d@XKOsZ2GPI1qUei)l&t# z)4FLYtliI_FF@|HKPr+=7OlyiQR)qc4J^<`E+Qb`Ab*BV*NEomZ3^`l$emla>%{fz z-a|Jg=g%mElwVdglc|HHXXhT%>}za(lZaYSzjWg&vOp2PN#RsZ`3A)m3Mu<_K{U zS9_Sf!2SgVV&SwU%a8AdrBG_1<#7Em|H&8xWHsrR#akwR7izowf~2C;L_RouN=#Y8 zj2g!wVmU~yc0#Mphp~XkY;{cHrAbG^#1zq8vpv^z8~}#K1TN;m^1Rl_ca0e9i89Xqz;n+ZqXVm;}7t-JO0f;S@tqkIBb5x zFS~*KrS0_FsI8avJLBTTA&m_gbwHc#@ufq#n#)#49kpsss{$0g=1pAc6zVdijmJ?x zbE8|vcg`)8D-(E7oFSApRI%|)h2%KD5HLS~2z|s!CmqN3W;u?CKqF)^l}e<<8-=jr zu2;Lg`aj0)G>S~~=@0m=3wXsqFWvnsF7ZnLShGs7zhXP|;cMWvD56J(+$vAtS7{dW zsR6&6(JdR@qd^lrGC6{loc8)FH|`*t&QCYkX$i%gB`qG>kHi;n)$!bjk5?W0={A|r z>3-~1lDWwzCk2c~^7~S5oe#6cDbM_@DD&ZQBn~)c-za?tVN?6+LdC}a<=~6$_7(R2 z>ZYn5hbp8$xrvKQ(|nZqAlQEVq{3ae_mF{&3MoEgfWp?w)14&ct{`~V67MYsZ749?2TBy;iv rNRSBt=4BcFA`tUH!SVd|-K?ieH`5eZq8Lx2Cv^(2Z0(2s8?XNY3?>01 literal 0 HcmV?d00001 From 3b23c785a83de7ac44977d2ff8bc08113ebdd4ad Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Wed, 10 Mar 2021 11:32:07 -0300 Subject: [PATCH 093/221] [Samples] Add 13-core.bot sample (#1034) * Add main pom for core-bot sample * Add deploymentTemplates folder * Add cognitiveModels folder * Add main documentation of the sample * Add dialogs classes * Add resources folder * Add webapp folder * Add bot for sample * Add BookingDetails model * Add recognizer for LUIS model * Add Startup file * Add package-info * Add empty test for sample --- samples/13.core-bot/LICENSE | 21 + samples/13.core-bot/README-LUIS.md | 216 +++++++++ samples/13.core-bot/README.md | 67 +++ .../cognitiveModels/FlightBooking.json | 339 ++++++++++++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/13.core-bot/pom.xml | 253 +++++++++++ .../bot/sample/core/Application.java | 114 +++++ .../bot/sample/core/BookingDetails.java | 62 +++ .../bot/sample/core/BookingDialog.java | 128 ++++++ .../bot/sample/core/CancelAndHelpDialog.java | 75 ++++ .../bot/sample/core/DateResolverDialog.java | 114 +++++ .../bot/sample/core/DialogAndWelcomeBot.java | 84 ++++ .../microsoft/bot/sample/core/DialogBot.java | 120 +++++ .../sample/core/FlightBookingRecognizer.java | 142 ++++++ .../microsoft/bot/sample/core/MainDialog.java | 206 +++++++++ .../bot/sample/core/package-info.java | 8 + .../src/main/resources/application.properties | 6 + .../src/main/resources/cards/welcomeCard.json | 46 ++ .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../13.core-bot/src/main/webapp/index.html | 417 ++++++++++++++++++ .../bot/sample/core/ApplicationTest.java | 2 + 24 files changed, 3003 insertions(+) create mode 100644 samples/13.core-bot/LICENSE create mode 100644 samples/13.core-bot/README-LUIS.md create mode 100644 samples/13.core-bot/README.md create mode 100644 samples/13.core-bot/cognitiveModels/FlightBooking.json create mode 100644 samples/13.core-bot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/13.core-bot/pom.xml create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java create mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java create mode 100644 samples/13.core-bot/src/main/resources/application.properties create mode 100644 samples/13.core-bot/src/main/resources/cards/welcomeCard.json create mode 100644 samples/13.core-bot/src/main/resources/log4j2.json create mode 100644 samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/13.core-bot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/13.core-bot/src/main/webapp/index.html create mode 100644 samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java diff --git a/samples/13.core-bot/LICENSE b/samples/13.core-bot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/13.core-bot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/13.core-bot/README-LUIS.md b/samples/13.core-bot/README-LUIS.md new file mode 100644 index 000000000..12bc78ed0 --- /dev/null +++ b/samples/13.core-bot/README-LUIS.md @@ -0,0 +1,216 @@ +# Setting up LUIS via CLI: + +This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. + +> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ +> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ +> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ + + [Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app + [Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app + +## Table of Contents: + +- [Prerequisites](#Prerequisites) +- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) +- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) + +___ + +## [Prerequisites](#Table-of-Contents): + +#### Install Azure CLI >=2.0.61: + +Visit the following page to find the correct installer for your OS: +- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest + +#### Install LUIS CLI >=2.4.0: + +Open a CLI of your choice and type the following: + +```bash +npm i -g luis-apis@^2.4.0 +``` + +#### LUIS portal account: + +You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. + +After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. + + [LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] + [LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key + +___ + +## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) + +### 1. Import the local LUIS application to luis.ai + +```bash +luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" +``` + +Outputs the following JSON: + +```json +{ + "id": "########-####-####-####-############", + "name": "FlightBooking", + "description": "A LUIS model that uses intent and entities.", + "culture": "en-us", + "usageScenario": "", + "domain": "", + "versionsCount": 1, + "createdDateTime": "2019-03-29T18:32:02Z", + "endpoints": {}, + "endpointHitsCount": 0, + "activeVersion": "0.1", + "ownerEmail": "bot@contoso.com", + "tokenizerVersion": "1.0.0" +} +``` + +For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. + +### 2. Train the LUIS Application + +```bash +luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait +``` + +### 3. Publish the LUIS Application + +```bash +luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" +``` + +> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
+> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
+> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
+> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. + + [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 + +Outputs the following: + +```json + { + "versionId": "0.1", + "isStaging": false, + "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", + "region": "westus", + "assignedEndpointKey": null, + "endpointRegion": "westus", + "failedRegions": "", + "publishedDateTime": "2019-03-29T18:40:32Z", + "directVersionPublish": false +} +``` + +To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. + + [README-LUIS]: ./README-LUIS.md + +___ + +## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) + +### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI + +> _Note:_
+> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ +> ```bash +> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" +> ``` +> _To see a list of valid locations, use `az account list-locations`_ + + +```bash +# Use Azure CLI to create the LUIS Key resource on Azure +az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +The command will output a response similar to the JSON below: + +```json +{ + "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", + "etag": "\"########-####-####-####-############\"", + "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", + "internalId": "################################", + "kind": "luis", + "location": "westus", + "name": "NewLuisResourceName", + "provisioningState": "Succeeded", + "resourceGroup": "ResourceGroupName", + "sku": { + "name": "S0", + "tier": null + }, + "tags": null, + "type": "Microsoft.CognitiveServices/accounts" +} +``` + + + +Take the output from the previous command and create a JSON file in the following format: + +```json +{ + "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", + "resourceGroup": "ResourceGroupName", + "accountName": "NewLuisResourceName" +} +``` + +### 2. Retrieve ARM access token via Azure CLI + +```bash +az account get-access-token --subscription "AzureSubscriptionGuid" +``` + +This will return an object that looks like this: + +```json +{ + "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", + "expiresOn": "2200-12-31 23:59:59.999999", + "subscription": "AzureSubscriptionGuid", + "tenant": "tenant-guid", + "tokenType": "Bearer" +} +``` + +The value needed for the next step is the `"accessToken"`. + +### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application + +```bash +luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" +``` + +If successful, it should yield a response like this: + +```json +{ + "code": "Success", + "message": "Operation Successful" +} +``` + +### 4. See the LUIS Cognitive Services' keys + +```bash +az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +This will return an object that looks like this: + +```json +{ + "key1": "9a69####dc8f####8eb4####399f####", + "key2": "####f99e####4b1a####fb3b####6b9f" +} +``` diff --git a/samples/13.core-bot/README.md b/samples/13.core-bot/README.md new file mode 100644 index 000000000..5eb579ae9 --- /dev/null +++ b/samples/13.core-bot/README.md @@ -0,0 +1,67 @@ +# CoreBot + +Bot Framework v4 core bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: + +- Use [LUIS](https://www.luis.ai) to implement core AI capabilities +- Implement a multi-turn conversation using Dialogs +- Handle user interruptions for such things as `Help` or `Cancel` +- Prompt for and validate requests for information from the user + +## Prerequisites + +This sample **requires** prerequisites in order to run. + +### Overview + +This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. + +### Create a LUIS Application to enable language understanding + +The LUIS model for this example can be found under `cognitiveModels/FlightBooking.json` and the LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). + +Once you created the LUIS model, update `application.properties` with your `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName`. + +``` + LuisAppId="Your LUIS App Id" + LuisAPIKey="Your LUIS Subscription key here" + LuisAPIHostName="Your LUIS App region here (i.e: westus.api.cognitive.microsoft.com)" +``` + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-core-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/samples/13.core-bot/cognitiveModels/FlightBooking.json b/samples/13.core-bot/cognitiveModels/FlightBooking.json new file mode 100644 index 000000000..89aad31ae --- /dev/null +++ b/samples/13.core-bot/cognitiveModels/FlightBooking.json @@ -0,0 +1,339 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "FlightBooking", + "desc": "Luis Model for CoreBot", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "BookFlight" + }, + { + "name": "Cancel" + }, + { + "name": "GetWeather" + }, + { + "name": "None" + } + ], + "entities": [], + "composites": [ + { + "name": "From", + "children": [ + "Airport" + ], + "roles": [] + }, + { + "name": "To", + "children": [ + "Airport" + ], + "roles": [] + } + ], + "closedLists": [ + { + "name": "Airport", + "subLists": [ + { + "canonicalForm": "Paris", + "list": [ + "paris", + "cdg" + ] + }, + { + "canonicalForm": "London", + "list": [ + "london", + "lhr" + ] + }, + { + "canonicalForm": "Berlin", + "list": [ + "berlin", + "txl" + ] + }, + { + "canonicalForm": "New York", + "list": [ + "new york", + "jfk" + ] + }, + { + "canonicalForm": "Seattle", + "list": [ + "seattle", + "sea" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "datetimeV2", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [], + "utterances": [ + { + "text": "book a flight", + "intent": "BookFlight", + "entities": [] + }, + { + "text": "book a flight from new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 26 + } + ] + }, + { + "text": "book a flight from seattle", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 25 + } + ] + }, + { + "text": "book a hotel in new york", + "intent": "None", + "entities": [] + }, + { + "text": "book a restaurant", + "intent": "None", + "entities": [] + }, + { + "text": "book flight from london to paris on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 17, + "endPos": 22 + }, + { + "entity": "To", + "startPos": 27, + "endPos": 31 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 22, + "endPos": 27 + }, + { + "entity": "To", + "startPos": 32, + "endPos": 36 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "find an airport near me", + "intent": "None", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + }, + { + "entity": "From", + "startPos": 21, + "endPos": 26 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 9, + "endPos": 14 + }, + { + "entity": "To", + "startPos": 19, + "endPos": 23 + } + ] + }, + { + "text": "go to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 6, + "endPos": 10 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 11, + "endPos": 15 + }, + { + "entity": "To", + "startPos": 20, + "endPos": 25 + } + ] + }, + { + "text": "i'd like to rent a car", + "intent": "None", + "entities": [] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel from new york to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 12, + "endPos": 19 + }, + { + "entity": "To", + "startPos": 24, + "endPos": 28 + } + ] + }, + { + "text": "travel to new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 17 + } + ] + }, + { + "text": "travel to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "what's the forecast for this friday?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like for tomorrow", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like in new york", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "winter is coming", + "intent": "None", + "entities": [] + } + ], + "settings": [] +} diff --git a/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json b/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/13.core-bot/pom.xml b/samples/13.core-bot/pom.xml new file mode 100644 index 000000000..a75393538 --- /dev/null +++ b/samples/13.core-bot/pom.xml @@ -0,0 +1,253 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-core + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Core Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.core.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-qna + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.core.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java new file mode 100644 index 000000000..3b73661df --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + /** + * The start method. + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ) { + Storage storage = this.getStorage(); + UserState userState = this.getUserState(storage); + ConversationState conversationState = this.getConversationState(storage); + Dialog rootDialog = this.getRootDialog(); + return new DialogAndWelcomeBot(conversationState, userState, rootDialog); + } + + /** + * Returns a FlightBookingRecognizer object. + * @return The FlightBookingRecognizer. + */ + @Bean + public FlightBookingRecognizer getFlightBookingRecognizer() { + Configuration configuration = this.getConfiguration(); + return new FlightBookingRecognizer(configuration); + } + + /** + * Returns a BookingDialog object. + * @return The BookingDialog. + */ + @Bean + public BookingDialog getBookingDialog() { + return new BookingDialog(); + } + + /** + * Returns the starting Dialog for this application. + * + *

+ * The @Component annotation could be used on the Dialog class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Dialog implementation for this application. + */ + @Bean + public Dialog getRootDialog() { + FlightBookingRecognizer flightBookingRecognizer = this.getFlightBookingRecognizer(); + BookingDialog bookingDialog = this.getBookingDialog(); + return new MainDialog(flightBookingRecognizer, bookingDialog); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} + diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java new file mode 100644 index 000000000..4e95a5807 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +/** + * The model class to retrieve the information of the booking. + */ +public class BookingDetails { + private String destination; + private String origin; + private String travelDate; + + /** + * Gets the destination of the booking. + * @return The destination. + */ + public String getDestination() { + return destination; + } + + + /** + * Sets the destination of the booking. + * @param withDestination The new destination. + */ + public void setDestination(String withDestination) { + this.destination = withDestination; + } + + /** + * Gets the origin of the booking. + * @return The origin. + */ + public String getOrigin() { + return origin; + } + + /** + * Sets the origin of the booking. + * @param withOrigin The new origin. + */ + public void setOrigin(String withOrigin) { + this.origin = withOrigin; + } + + /** + * Gets the travel date of the booking. + * @return The travel date. + */ + public String getTravelDate() { + return travelDate; + } + + /** + * Sets the travel date of the booking. + * @param withTravelDate The new travel date. + */ + public void setTravelDate(String withTravelDate) { + this.travelDate = withTravelDate; + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java new file mode 100644 index 000000000..5af80feb0 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java @@ -0,0 +1,128 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the booking dialogs. + */ +public class BookingDialog extends CancelAndHelpDialog { + private final String destinationStepMsgText = "Where would you like to travel to?"; + private final String originStepMsgText = "Where are you traveling from?"; + + /** + * The constructor of the Booking Dialog class. + */ + public BookingDialog() { + super("BookingDialog"); + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new ConfirmPrompt("ConfirmPrompt")); + addDialog(new DateResolverDialog(null)); + WaterfallStep[] waterfallSteps = { + this::destinationStep, + this::originStep, + this::travelDateStep, + this::confirmStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + + private CompletableFuture destinationStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + if (bookingDetails.getDestination().isEmpty()) { + Activity promptMessage = + MessageFactory.text(destinationStepMsgText, destinationStepMsgText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getDestination()); + } + + + private CompletableFuture originStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setDestination(stepContext.getResult().toString()); + + if (bookingDetails.getOrigin().isEmpty()) { + Activity promptMessage = + MessageFactory.text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getOrigin()); + } + + + private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setOrigin(stepContext.getResult().toString()); + + if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { + return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); + } + + return stepContext.next(bookingDetails.getTravelDate()); + } + + + private CompletableFuture confirmStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setTravelDate(stepContext.getResult().toString()); + + String messageText = + String.format("Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", + bookingDetails.getDestination(), bookingDetails.getOrigin(), bookingDetails.getTravelDate()); + Activity promptMessage = MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT); + + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + + return stepContext.prompt("ConfirmPrompt", promptOptions); + } + + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + if ((Boolean) stepContext.getResult()) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + return stepContext.endDialog(bookingDetails); + } + + return stepContext.endDialog(null); + } + + private static boolean isAmbiguous(String timex) { + TimexProperty timexProperty = new TimexProperty(timex); + return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java new file mode 100644 index 000000000..393c04c5b --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of the dialog interruptions. + */ +public class CancelAndHelpDialog extends ComponentDialog { + private final String helpMsgText = "Show help here"; + private final String cancelMsgText = "Cancelling..."; + + /** + * The constructor of the CancelAndHelpDialog class. + * @param id The dialog's Id. + */ + public CancelAndHelpDialog(String id) { + super(id); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. + * @return A {@link CompletableFuture} representing the asynchronous operation. + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. + */ + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return interrupt(innerDc).thenCompose(result -> { + if (result != null) { + return CompletableFuture.completedFuture(result); + } + return super.onContinueDialog(innerDc); + }); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + switch (text) { + case "help": + case "?": + Activity helpMessage = MessageFactory.text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); + return innerDc.getContext().sendActivity(helpMessage) + .thenCompose(sendResult -> + CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); + case "cancel": + case "quit": + Activity cancelMessage = MessageFactory + .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); + return innerDc.getContext() + .sendActivity(cancelMessage).thenCompose(sendResult -> innerDc.cancelAllDialogs()); + default: + break; + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java new file mode 100644 index 000000000..bbdbf8c0e --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.DateTimeResolution; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the date resolver dialogs. + */ +public class DateResolverDialog extends CancelAndHelpDialog { + private final String promptMsgText = "When would you like to travel?"; + private final String repromptMsgText = + "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; + + + /** + * The constructor of the DateResolverDialog class. + * @param id The dialog's id. + */ + public DateResolverDialog(@Nullable String id) { + super(id != null ? id : "DateResolverDialog"); + + + addDialog(new DateTimePrompt("DateTimePrompt", + DateResolverDialog::dateTimePromptValidator, null)); + WaterfallStep[] waterfallSteps = { + this::initialStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture initialStep(WaterfallStepContext stepContext) { + String timex = (String) stepContext.getOptions(); + + Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); + Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); + + if (timex == null) { + // We were not given any date at all so prompt the user. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + promptOptions.setRetryPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + // We have a Date we just need to check it is unambiguous. + TimexProperty timexProperty = new TimexProperty(timex); + if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { + // This is essentially a "reprompt" of the data we were given up front. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + DateTimeResolution dateTimeResolution = new DateTimeResolution() { + { + setTimex(timex); + } + }; + List dateTimeResolutions = new ArrayList() { + { + add(dateTimeResolution); + } + }; + return stepContext.next(dateTimeResolutions); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); + return stepContext.endDialog(timex); + } + + private static CompletableFuture dateTimePromptValidator(PromptValidatorContext> + promptContext) { + if (promptContext.getRecognized().getSucceeded()) { + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the + // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. + // e.g. missing a Year. + String timex = ((List) promptContext.getRecognized().getValue()) + .get(0).getTimex().split("T")[0]; + + // If this is a definite Date including year, month and day we are good otherwise reprompt. + // A better solution might be to let the user know what part is actually missing. + Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); + + return CompletableFuture.completedFuture(isDefinite); + } + + return CompletableFuture.completedFuture(false); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java new file mode 100644 index 000000000..5d15bf417 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; +import com.microsoft.applicationinsights.core.dependencies.apachecommons.lang3.StringUtils; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Serialization; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the welcome dialog. + * @param is a Dialog. + */ +public class DialogAndWelcomeBot extends DialogBot { + /** + * Creates a DialogBot. + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogAndWelcomeBot(ConversationState withConversationState, UserState withUserState, T withDialog) { + super(withConversationState, withUserState, withDialog); + } + + /** + * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a + * conversation update activity that indicates one or more users other than the + * bot are joining the conversation, it calls this method. + * @param membersAdded A list of all the members added to the conversation, + * as described by the conversation update activity + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. + Attachment welcomeCard = createAdaptiveCardAttachment(); + Activity response = MessageFactory.attachment(welcomeCard, null, "Welcome to Bot Framework!", null); + + return turnContext.sendActivity(response).thenApply(sendResult -> { + return Dialog.run(getDialog(), turnContext, getConversationState().createProperty("DialogState")); + }); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + // Load attachment from embedded resource. + private Attachment createAdaptiveCardAttachment() { + try (InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream("cards/welcomeCard.json")) { + String adaptiveCardJson = IOUtils.toString(inputStream, StandardCharsets.UTF_8.toString()); + + return new Attachment() {{ + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(Serialization.jsonToTree(adaptiveCardJson)); + }}; + + } catch (IOException e) { + e.printStackTrace(); + return new Attachment(); + } + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java new file mode 100644 index 000000000..3826b7f54 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java @@ -0,0 +1,120 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow multiple + * different bots to be run at different endpoints within the same project. This can be achieved by defining + * distinct Controller types each with dependency on distinct Bot types. The ConversationState is used by + * the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, + * and the requirement is that all BotState objects are saved at the end of a turn. + * + * @param parameter of a type inheriting from Dialog + */ +public class DialogBot extends ActivityHandler { + private Dialog dialog; + private BotState conversationState; + private BotState userState; + + /** + * Gets the dialog in use. + * + * @return instance of dialog + */ + protected Dialog getDialog() { + return dialog; + } + + /** + * Gets the conversation state. + * + * @return instance of conversationState + */ + protected BotState getConversationState() { + return conversationState; + } + + /** + * Gets the user state. + * + * @return instance of userState + */ + protected BotState getUserState() { + return userState; + } + + /** + * Sets the dialog in use. + * + * @param withDialog the dialog (of Dialog type) to be set + */ + protected void setDialog(Dialog withDialog) { + dialog = withDialog; + } + + /** + * Sets the conversation state. + * + * @param withConversationState the conversationState (of BotState type) to be set + */ + protected void setConversationState(BotState withConversationState) { + conversationState = withConversationState; + } + + /** + * Sets the user state. + * + * @param withUserState the userState (of BotState type) to be set + */ + protected void setUserState(BotState withUserState) { + userState = withUserState; + } + + /** + * Creates a DialogBot. + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogBot(ConversationState withConversationState, UserState withUserState, T withDialog) { + this.conversationState = withConversationState; + this.userState = withUserState; + this.dialog = withDialog; + } + + /** + * Saves the BotState objects at the end of each turn. + * @param turnContext + * @return + */ + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return super.onTurn(turnContext) + .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) + .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); + } + + /** + * This method is executed when the turnContext receives a message activity. + * @param turnContext + * @return + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java new file mode 100644 index 000000000..da2533aac --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.builder.Recognizer; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.integration.Configuration; +import org.apache.commons.lang3.StringUtils; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of recognizing the booking information. + */ +public class FlightBookingRecognizer implements Recognizer { + private LuisRecognizer recognizer; + + /** + * The constructor of the FlightBookingRecognizer class. + * @param configuration The Configuration object to use. + */ + public FlightBookingRecognizer(Configuration configuration) { + Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); + if (luisIsConfigured) { + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + String.format("https://%s", configuration.getProperty("LuisAPIHostName"))); + // Set the recognizer options depending on which endpoint version you want to use. + // More details can be found in + // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication) { + { + setIncludeInstanceData(true); + } + }; + + this.recognizer = new LuisRecognizer(recognizerOptions); + } + } + + /** + * Verify if the recognizer is configured. + * @return True if it's configured, False if it's not. + */ + public Boolean isConfigured() { + return this.recognizer != null; + } + + /** + * Return an object with preformatted LUIS results for the bot's dialogs to consume. + * @param context A {link TurnContext} + * @return A {link RecognizerResult} + */ + public CompletableFuture executeLuisQuery(TurnContext context) { + // Returns true if luis is configured in the application.properties and initialized. + return this.recognizer.recognize(context); + } + + /** + * Gets the From data from the entities which is part of the result. + * @param result The recognizer result. + * @return The object node representing the From data. + */ + public ObjectNode getFromEntities(RecognizerResult result) { + String fromValue = "", fromAirportValue = ""; + if (result.getEntities().get("$instance").get("From") != null) { + fromValue = result.getEntities().get("$instance").get("From").get(0).get("text").asText(); + } + if (!fromValue.isEmpty() && result.getEntities().get("From").get(0).get("Airport") != null) { + fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0).asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("from", fromValue); + entitiesNode.put("airport", fromAirportValue); + return entitiesNode; + } + + /** + * Gets the To data from the entities which is part of the result. + * @param result The recognizer result. + * @return The object node representing the To data. + */ + public ObjectNode getToEntities(RecognizerResult result) { + String toValue = "", toAirportValue = ""; + if (result.getEntities().get("$instance").get("To") != null) { + toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); + } + if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { + toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0).asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("to", toValue); + entitiesNode.put("airport", toAirportValue); + return entitiesNode; + } + + /** + * This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part. + * TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year. + * @param result A {link RecognizerResult} + * @return The Timex value without the Time model + */ + public String getTravelDate(RecognizerResult result) { + JsonNode datetimeEntity = result.getEntities().get("datetime"); + if (datetimeEntity == null || datetimeEntity.get(0) == null) { + return null; + } + + JsonNode timex = datetimeEntity.get(0).get("timex"); + if (timex == null || timex.get(0) == null) { + return null; + } + + String datetime = timex.get(0).asText().split("T")[0]; + return datetime; + } + + /** + * Runs an utterance through a recognizer and returns a generic recognizer result. + * @param turnContext Turn context. + * @return Analysis of utterance. + */ + @Override + public CompletableFuture recognize(TurnContext turnContext) { + return this.recognizer.recognize(turnContext); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java new file mode 100644 index 000000000..51a3bbe56 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; + +/** + * The class containing the main dialog for the sample. + */ +public class MainDialog extends ComponentDialog { + private final FlightBookingRecognizer luisRecognizer; + private final Integer plusDayValue = 7; + + /** + * The constructor of the Main Dialog class. + * @param withLuisRecognizer The FlightBookingRecognizer object. + * @param bookingDialog The BookingDialog object with booking dialogs. + */ + public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog bookingDialog) { + super("MainDialog"); + + luisRecognizer = withLuisRecognizer; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(bookingDialog); + WaterfallStep[] waterfallSteps = { + this::introStep, + this::actStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + /** + * First step in the waterfall dialog. Prompts the user for a command. + * Currently, this expects a booking request, like "book me a flight from Paris to Berlin on march 22" + * Note that the sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture introStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + Activity text = MessageFactory.text("NOTE: LUIS is not configured. " + + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " + + "to the appsettings.json file.", null, InputHints.IGNORING_INPUT); + return stepContext.getContext().sendActivity(text) + .thenCompose(sendResult -> stepContext.next(null)); + } + + // Use the text provided in FinalStepAsync or the default if it is the first time. + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); + String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); + String messageText = stepContext.getOptions() != null + ? stepContext.getOptions().toString() + : String.format("What can I help you with today?\n" + + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); + Activity promptMessage = MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + /** + * Second step in the waterfall. This will use LUIS to attempt to extract the origin, destination and travel dates. + * Then, it hands off to the bookingDialog child dialog to collect any remaining details. + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture actStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. + return stepContext.beginDialog("BookingDialog", new BookingDetails()); + } + + // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) + return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { + switch (luisResult.getTopScoringIntent().intent) { + case "BookFlight": + // Extract the values for the composite entities from the LUIS result. + ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); + ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); + + // Show a warning for Origin and Destination if we can't resolve them. + return showWarningForUnsupportedCities(stepContext.getContext(), fromEntities, toEntities) + .thenCompose(showResult -> { + // Initialize BookingDetails with any entities we may have found in the response. + + BookingDetails bookingDetails = new BookingDetails(); + bookingDetails.setDestination(toEntities.get("airport").asText()); + bookingDetails.setOrigin(fromEntities.get("airport").asText()); + bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); + // Run the BookingDialog giving it whatever details we have from the LUIS call, + // it will fill out the remainder. + return stepContext.beginDialog("BookingDialog", bookingDetails); + } + ); + case "GetWeather": + // We haven't implemented the GetWeatherDialog so we just display a TODO message. + String getWeatherMessageText = "TODO: get weather flow here"; + Activity getWeatherMessage = MessageFactory + .text(getWeatherMessageText, getWeatherMessageText, InputHints.IGNORING_INPUT); + stepContext.getContext().sendActivity(getWeatherMessage); + break; + default: + // Catch all for unhandled intents + String didntUnderstandMessageText = String.format("Sorry, I didn't get that. Please " + + " try asking in a different way (intent was %s)", luisResult.getTopScoringIntent().intent); + Activity didntUnderstandMessage = MessageFactory + .text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IGNORING_INPUT); + stepContext.getContext().sendActivity(didntUnderstandMessage); + break; + } + return stepContext.next(null); + }); + } + + /** + * Shows a warning if the requested From or To cities are recognized as entities + * but they are not in the Airport entity list. + * In some cases LUIS will recognize the From and To composite entities as a valid cities + * but the From and To Airport values + * will be empty if those entity values can't be mapped to a canonical item in the Airport. + * @param turnContext A {@link WaterfallStepContext} + * @param fromEntities An ObjectNode with the entities of From object + * @param toEntities An ObjectNode with the entities of To object + * @return A task + */ + private static CompletableFuture showWarningForUnsupportedCities(TurnContext turnContext, + ObjectNode fromEntities, + ObjectNode toEntities) { + List unsupportedCities = new ArrayList(); + + if (StringUtils.isNotBlank(fromEntities.get("from").asText()) + && StringUtils.isBlank(fromEntities.get("airport").asText())) { + unsupportedCities.add(fromEntities.get("from").asText()); + } + + if (StringUtils.isNotBlank(toEntities.get("to").asText()) + && StringUtils.isBlank(toEntities.get("airport").asText())) { + unsupportedCities.add(toEntities.get("to").asText()); + } + + if (!unsupportedCities.isEmpty()) { + String messageText = String.format("Sorry but the following airports are not supported: %s", + String.join(", ", unsupportedCities)); + Activity message = MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT); + turnContext.sendActivity(message).thenApply(sendResult -> null); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * This is the final step in the main waterfall dialog. + * It wraps up the sample "book a flight" interaction with a simple confirmation. + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + // If the child dialog ("BookingDialog") was cancelled, + // the user failed to confirm or if the intent wasn't BookFlight + // the Result here will be null. + if (stepContext.getResult() instanceof BookingDetails) { + // Now we have all the booking details call the booking service. + + // If the call to the booking service was successful tell the user. + + BookingDetails result = (BookingDetails) stepContext.getResult(); + TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); + String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); + String messageText = String.format("I have you booked to %s from %s on %s", + result.getDestination(), result.getOrigin(), travelDateMsg); + Activity message = MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT); + stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); + } + + // Restart the main dialog with a different message the second time around + String promptMessage = "What else can I do for you?"; + return stepContext.replaceDialog(getInitialDialogId(), promptMessage); + } +} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java new file mode 100644 index 000000000..c08ec99b7 --- /dev/null +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the core-bot sample. + */ +package com.microsoft.bot.sample.core; diff --git a/samples/13.core-bot/src/main/resources/application.properties b/samples/13.core-bot/src/main/resources/application.properties new file mode 100644 index 000000000..255d7cd56 --- /dev/null +++ b/samples/13.core-bot/src/main/resources/application.properties @@ -0,0 +1,6 @@ +MicrosoftAppId= +MicrosoftAppPassword= +LuisAppId= +LuisAPIKey= +LuisAPIHostName= +server.port=3978 diff --git a/samples/13.core-bot/src/main/resources/cards/welcomeCard.json b/samples/13.core-bot/src/main/resources/cards/welcomeCard.json new file mode 100644 index 000000000..9b6389e39 --- /dev/null +++ b/samples/13.core-bot/src/main/resources/cards/welcomeCard.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "Welcome to Bot Framework!", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": true, + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} diff --git a/samples/13.core-bot/src/main/resources/log4j2.json b/samples/13.core-bot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/13.core-bot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml b/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/13.core-bot/src/main/webapp/index.html b/samples/13.core-bot/src/main/webapp/index.html new file mode 100644 index 000000000..750c0f776 --- /dev/null +++ b/samples/13.core-bot/src/main/webapp/index.html @@ -0,0 +1,417 @@ + + + + + + + Core Bot Sample + + + + + +
+
+
+
Core Bot Sample
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java b/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java new file mode 100644 index 000000000..d194bed47 --- /dev/null +++ b/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java @@ -0,0 +1,2 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. From 2e577c2383a3138ccb0504aa1d3ea647a02f88da Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 10 Mar 2021 09:21:38 -0600 Subject: [PATCH 094/221] Core Bot formatting (#1045) * Core Bot formatting * Simplified Core Bot Appliation DI --- samples/13.core-bot/pom.xml | 5 - .../bot/sample/core/Application.java | 55 ++------ .../bot/sample/core/BookingDetails.java | 7 + .../bot/sample/core/BookingDialog.java | 19 ++- .../bot/sample/core/CancelAndHelpDialog.java | 23 ++-- .../bot/sample/core/DateResolverDialog.java | 21 ++- .../bot/sample/core/DialogAndWelcomeBot.java | 48 ++++--- .../microsoft/bot/sample/core/DialogBot.java | 23 ++-- .../sample/core/FlightBookingRecognizer.java | 31 +++-- .../microsoft/bot/sample/core/MainDialog.java | 128 +++++++++++------- 10 files changed, 199 insertions(+), 161 deletions(-) diff --git a/samples/13.core-bot/pom.xml b/samples/13.core-bot/pom.xml index a75393538..a59ab13e3 100644 --- a/samples/13.core-bot/pom.xml +++ b/samples/13.core-bot/pom.xml @@ -89,11 +89,6 @@ bot-dialogs 4.6.0-preview9
- - com.microsoft.bot - bot-ai-qna - 4.6.0-preview9 - com.microsoft.bot bot-ai-luis-v3 diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java index 3b73661df..8ae95d1f7 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java @@ -5,9 +5,7 @@ import com.microsoft.bot.builder.Bot; import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.Storage; import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.integration.AdapterWithErrorHandler; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; @@ -36,8 +34,10 @@ * override methods in order to provide custom implementations. */ public class Application extends BotDependencyConfiguration { + /** * The start method. + * * @param args The args. */ public static void main(String[] args) { @@ -48,56 +48,21 @@ public static void main(String[] args) { * Returns the Bot for this application. * *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. + * The @Component annotation could be used on the Bot class instead of this method with the + * @Bean annotation. *

* * @return The Bot implementation for this application. */ @Bean public Bot getBot( + Configuration configuration, + UserState userState, + ConversationState conversationState ) { - Storage storage = this.getStorage(); - UserState userState = this.getUserState(storage); - ConversationState conversationState = this.getConversationState(storage); - Dialog rootDialog = this.getRootDialog(); - return new DialogAndWelcomeBot(conversationState, userState, rootDialog); - } - - /** - * Returns a FlightBookingRecognizer object. - * @return The FlightBookingRecognizer. - */ - @Bean - public FlightBookingRecognizer getFlightBookingRecognizer() { - Configuration configuration = this.getConfiguration(); - return new FlightBookingRecognizer(configuration); - } - - /** - * Returns a BookingDialog object. - * @return The BookingDialog. - */ - @Bean - public BookingDialog getBookingDialog() { - return new BookingDialog(); - } - - /** - * Returns the starting Dialog for this application. - * - *

- * The @Component annotation could be used on the Dialog class instead of this method - * with the @Bean annotation. - *

- * - * @return The Dialog implementation for this application. - */ - @Bean - public Dialog getRootDialog() { - FlightBookingRecognizer flightBookingRecognizer = this.getFlightBookingRecognizer(); - BookingDialog bookingDialog = this.getBookingDialog(); - return new MainDialog(flightBookingRecognizer, bookingDialog); + FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration); + MainDialog dialog = new MainDialog(recognizer, new BookingDialog()); + return new DialogAndWelcomeBot<>(conversationState, userState, dialog); } /** diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java index 4e95a5807..330740a57 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java @@ -7,12 +7,14 @@ * The model class to retrieve the information of the booking. */ public class BookingDetails { + private String destination; private String origin; private String travelDate; /** * Gets the destination of the booking. + * * @return The destination. */ public String getDestination() { @@ -22,6 +24,7 @@ public String getDestination() { /** * Sets the destination of the booking. + * * @param withDestination The new destination. */ public void setDestination(String withDestination) { @@ -30,6 +33,7 @@ public void setDestination(String withDestination) { /** * Gets the origin of the booking. + * * @return The origin. */ public String getOrigin() { @@ -38,6 +42,7 @@ public String getOrigin() { /** * Sets the origin of the booking. + * * @param withOrigin The new origin. */ public void setOrigin(String withOrigin) { @@ -46,6 +51,7 @@ public void setOrigin(String withOrigin) { /** * Gets the travel date of the booking. + * * @return The travel date. */ public String getTravelDate() { @@ -54,6 +60,7 @@ public String getTravelDate() { /** * Sets the travel date of the booking. + * * @param withTravelDate The new travel date. */ public void setTravelDate(String withTravelDate) { diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java index 5af80feb0..4a5e45721 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java @@ -23,6 +23,7 @@ * The class containing the booking dialogs. */ public class BookingDialog extends CancelAndHelpDialog { + private final String destinationStepMsgText = "Where would you like to travel to?"; private final String originStepMsgText = "Where are you traveling from?"; @@ -54,7 +55,9 @@ private CompletableFuture destinationStep(WaterfallStepContext if (bookingDetails.getDestination().isEmpty()) { Activity promptMessage = - MessageFactory.text(destinationStepMsgText, destinationStepMsgText, InputHints.EXPECTING_INPUT); + MessageFactory.text(destinationStepMsgText, destinationStepMsgText, + InputHints.EXPECTING_INPUT + ); PromptOptions promptOptions = new PromptOptions(); promptOptions.setPrompt(promptMessage); return stepContext.prompt("TextPrompt", promptOptions); @@ -71,7 +74,8 @@ private CompletableFuture originStep(WaterfallStepContext step if (bookingDetails.getOrigin().isEmpty()) { Activity promptMessage = - MessageFactory.text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); + MessageFactory + .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); PromptOptions promptOptions = new PromptOptions(); promptOptions.setPrompt(promptMessage); return stepContext.prompt("TextPrompt", promptOptions); @@ -100,9 +104,13 @@ private CompletableFuture confirmStep(WaterfallStepContext ste bookingDetails.setTravelDate(stepContext.getResult().toString()); String messageText = - String.format("Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", - bookingDetails.getDestination(), bookingDetails.getOrigin(), bookingDetails.getTravelDate()); - Activity promptMessage = MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT); + String.format( + "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", + bookingDetails.getDestination(), bookingDetails.getOrigin(), + bookingDetails.getTravelDate() + ); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); PromptOptions promptOptions = new PromptOptions(); promptOptions.setPrompt(promptMessage); @@ -114,7 +122,6 @@ private CompletableFuture confirmStep(WaterfallStepContext ste private CompletableFuture finalStep(WaterfallStepContext stepContext) { if ((Boolean) stepContext.getResult()) { BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - return stepContext.endDialog(bookingDetails); } diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java index 393c04c5b..570fb3eaf 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java @@ -18,11 +18,13 @@ * The class in charge of the dialog interruptions. */ public class CancelAndHelpDialog extends ComponentDialog { + private final String helpMsgText = "Show help here"; private final String cancelMsgText = "Cancelling..."; /** * The constructor of the CancelAndHelpDialog class. + * * @param id The dialog's Id. */ public CancelAndHelpDialog(String id) { @@ -30,13 +32,13 @@ public CancelAndHelpDialog(String id) { } /** - * Called when the dialog is _continued_, where it is the active dialog and the - * user replies with a new activity. + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. + * * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. - * @return A {@link CompletableFuture} representing the asynchronous operation. - * If the task is successful, the result indicates whether the dialog is - * still active after the turn has been processed by the dialog. The - * result may also contain a return value. + * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is + * successful, the result indicates whether the dialog is still active after the turn has been + * processed by the dialog. The result may also contain a return value. */ @Override protected CompletableFuture onContinueDialog(DialogContext innerDc) { @@ -55,16 +57,19 @@ private CompletableFuture interrupt(DialogContext innerDc) { switch (text) { case "help": case "?": - Activity helpMessage = MessageFactory.text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); + Activity helpMessage = MessageFactory + .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); return innerDc.getContext().sendActivity(helpMessage) .thenCompose(sendResult -> - CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); + CompletableFuture + .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); case "cancel": case "quit": Activity cancelMessage = MessageFactory .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); return innerDc.getContext() - .sendActivity(cancelMessage).thenCompose(sendResult -> innerDc.cancelAllDialogs()); + .sendActivity(cancelMessage) + .thenCompose(sendResult -> innerDc.cancelAllDialogs()); default: break; } diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java index bbdbf8c0e..635ae92b3 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java @@ -75,16 +75,12 @@ private CompletableFuture initialStep(WaterfallStepContext ste return stepContext.prompt("DateTimePrompt", promptOptions); } - DateTimeResolution dateTimeResolution = new DateTimeResolution() { - { - setTimex(timex); - } - }; - List dateTimeResolutions = new ArrayList() { - { - add(dateTimeResolution); - } - }; + DateTimeResolution dateTimeResolution = new DateTimeResolution() {{ + setTimex(timex); + }}; + List dateTimeResolutions = new ArrayList() {{ + add(dateTimeResolution); + }}; return stepContext.next(dateTimeResolutions); } @@ -93,8 +89,9 @@ private CompletableFuture finalStep(WaterfallStepContext stepC return stepContext.endDialog(timex); } - private static CompletableFuture dateTimePromptValidator(PromptValidatorContext> - promptContext) { + private static CompletableFuture dateTimePromptValidator( + PromptValidatorContext> promptContext + ) { if (promptContext.getRecognized().getSucceeded()) { // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java index 5d15bf417..48228d02f 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java @@ -24,30 +24,38 @@ /** * The class containing the welcome dialog. + * * @param is a Dialog. */ public class DialogAndWelcomeBot extends DialogBot { + /** * Creates a DialogBot. + * * @param withConversationState ConversationState to use in the bot * @param withUserState UserState to use * @param withDialog Param inheriting from Dialog class */ - public DialogAndWelcomeBot(ConversationState withConversationState, UserState withUserState, T withDialog) { + public DialogAndWelcomeBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { super(withConversationState, withUserState, withDialog); } /** - * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a - * conversation update activity that indicates one or more users other than the - * bot are joining the conversation, it calls this method. - * @param membersAdded A list of all the members added to the conversation, - * as described by the conversation update activity - * @param turnContext The context object for this turn. + * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation + * update activity that indicates one or more users other than the bot are joining the + * conversation, it calls this method. + * + * @param membersAdded A list of all the members added to the conversation, as described by the + * conversation update activity + * @param turnContext The context object for this turn. * @return A task that represents the work queued to execute. */ @Override - protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { return turnContext.getActivity().getMembersAdded().stream() .filter(member -> !StringUtils .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) @@ -55,10 +63,13 @@ protected CompletableFuture onMembersAdded(List membersAdd // Greet anyone that was not the target (recipient) of this message. // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. Attachment welcomeCard = createAdaptiveCardAttachment(); - Activity response = MessageFactory.attachment(welcomeCard, null, "Welcome to Bot Framework!", null); + Activity response = MessageFactory + .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); return turnContext.sendActivity(response).thenApply(sendResult -> { - return Dialog.run(getDialog(), turnContext, getConversationState().createProperty("DialogState")); + return Dialog.run(getDialog(), turnContext, + getConversationState().createProperty("DialogState") + ); }); }) .collect(CompletableFutures.toFutureList()) @@ -67,14 +78,17 @@ protected CompletableFuture onMembersAdded(List membersAdd // Load attachment from embedded resource. private Attachment createAdaptiveCardAttachment() { - try (InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream("cards/welcomeCard.json")) { - String adaptiveCardJson = IOUtils.toString(inputStream, StandardCharsets.UTF_8.toString()); + try ( + InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") + ) { + String adaptiveCardJson = IOUtils + .toString(inputStream, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + return new Attachment() {{ + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(Serialization.jsonToTree(adaptiveCardJson)); + }}; } catch (IOException e) { e.printStackTrace(); diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java index 3826b7f54..18df7b20f 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java @@ -14,15 +14,17 @@ import java.util.concurrent.CompletableFuture; /** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow multiple - * different bots to be run at different endpoints within the same project. This can be achieved by defining - * distinct Controller types each with dependency on distinct Bot types. The ConversationState is used by - * the Dialog system. The UserState isn't, however, it might have been used in a Dialog implementation, - * and the requirement is that all BotState objects are saved at the end of a turn. + * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow + * multiple different bots to be run at different endpoints within the same project. This can be + * achieved by defining distinct Controller types each with dependency on distinct Bot types. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been + * used in a Dialog implementation, and the requirement is that all BotState objects are saved at + * the end of a turn. * * @param parameter of a type inheriting from Dialog */ public class DialogBot extends ActivityHandler { + private Dialog dialog; private BotState conversationState; private BotState userState; @@ -83,11 +85,14 @@ protected void setUserState(BotState withUserState) { /** * Creates a DialogBot. + * * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class */ - public DialogBot(ConversationState withConversationState, UserState withUserState, T withDialog) { + public DialogBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { this.conversationState = withConversationState; this.userState = withUserState; this.dialog = withDialog; @@ -95,6 +100,7 @@ public DialogBot(ConversationState withConversationState, UserState withUserStat /** * Saves the BotState objects at the end of each turn. + * * @param turnContext * @return */ @@ -107,6 +113,7 @@ public CompletableFuture onTurn(TurnContext turnContext) { /** * This method is executed when the turnContext receives a message activity. + * * @param turnContext * @return */ diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java index da2533aac..9693b8cf0 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java @@ -21,10 +21,12 @@ * The class in charge of recognizing the booking information. */ public class FlightBookingRecognizer implements Recognizer { + private LuisRecognizer recognizer; /** * The constructor of the FlightBookingRecognizer class. + * * @param configuration The Configuration object to use. */ public FlightBookingRecognizer(Configuration configuration) { @@ -35,11 +37,13 @@ public FlightBookingRecognizer(Configuration configuration) { LuisApplication luisApplication = new LuisApplication( configuration.getProperty("LuisAppId"), configuration.getProperty("LuisAPIKey"), - String.format("https://%s", configuration.getProperty("LuisAPIHostName"))); + String.format("https://%s", configuration.getProperty("LuisAPIHostName")) + ); // Set the recognizer options depending on which endpoint version you want to use. // More details can be found in // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication) { + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3( + luisApplication) { { setIncludeInstanceData(true); } @@ -51,6 +55,7 @@ public FlightBookingRecognizer(Configuration configuration) { /** * Verify if the recognizer is configured. + * * @return True if it's configured, False if it's not. */ public Boolean isConfigured() { @@ -59,6 +64,7 @@ public Boolean isConfigured() { /** * Return an object with preformatted LUIS results for the bot's dialogs to consume. + * * @param context A {link TurnContext} * @return A {link RecognizerResult} */ @@ -69,16 +75,20 @@ public CompletableFuture executeLuisQuery(TurnContext context) /** * Gets the From data from the entities which is part of the result. + * * @param result The recognizer result. * @return The object node representing the From data. */ public ObjectNode getFromEntities(RecognizerResult result) { String fromValue = "", fromAirportValue = ""; if (result.getEntities().get("$instance").get("From") != null) { - fromValue = result.getEntities().get("$instance").get("From").get(0).get("text").asText(); + fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") + .asText(); } - if (!fromValue.isEmpty() && result.getEntities().get("From").get(0).get("Airport") != null) { - fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0).asText(); + if (!fromValue.isEmpty() + && result.getEntities().get("From").get(0).get("Airport") != null) { + fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) + .asText(); } ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); @@ -90,6 +100,7 @@ public ObjectNode getFromEntities(RecognizerResult result) { /** * Gets the To data from the entities which is part of the result. + * * @param result The recognizer result. * @return The object node representing the To data. */ @@ -99,7 +110,8 @@ public ObjectNode getToEntities(RecognizerResult result) { toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); } if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { - toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0).asText(); + toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) + .asText(); } ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); @@ -110,8 +122,10 @@ public ObjectNode getToEntities(RecognizerResult result) { } /** - * This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the Time part. - * TIMEX is a format that represents DateTime expressions that include some ambiguity. e.g. missing a Year. + * This value will be a TIMEX. And we are only interested in a Date so grab the first result and + * drop the Time part. TIMEX is a format that represents DateTime expressions that include some + * ambiguity. e.g. missing a Year. + * * @param result A {link RecognizerResult} * @return The Timex value without the Time model */ @@ -132,6 +146,7 @@ public String getTravelDate(RecognizerResult result) { /** * Runs an utterance through a recognizer and returns a generic recognizer result. + * * @param turnContext Turn context. * @return Analysis of utterance. */ diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java index 51a3bbe56..6f913a559 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java @@ -30,13 +30,15 @@ * The class containing the main dialog for the sample. */ public class MainDialog extends ComponentDialog { + private final FlightBookingRecognizer luisRecognizer; private final Integer plusDayValue = 7; /** * The constructor of the Main Dialog class. + * * @param withLuisRecognizer The FlightBookingRecognizer object. - * @param bookingDialog The BookingDialog object with booking dialogs. + * @param bookingDialog The BookingDialog object with booking dialogs. */ public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog bookingDialog) { super("MainDialog"); @@ -57,9 +59,10 @@ public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog book } /** - * First step in the waterfall dialog. Prompts the user for a command. - * Currently, this expects a booking request, like "book me a flight from Paris to Berlin on march 22" - * Note that the sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. + * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a + * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the + * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. + * * @param stepContext A {@link WaterfallStepContext} * @return A {@link DialogTurnResult} */ @@ -78,16 +81,19 @@ private CompletableFuture introStep(WaterfallStepContext stepC String messageText = stepContext.getOptions() != null ? stepContext.getOptions().toString() : String.format("What can I help you with today?\n" - + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); - Activity promptMessage = MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT); + + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); PromptOptions promptOptions = new PromptOptions(); promptOptions.setPrompt(promptMessage); return stepContext.prompt("TextPrompt", promptOptions); } /** - * Second step in the waterfall. This will use LUIS to attempt to extract the origin, destination and travel dates. - * Then, it hands off to the bookingDialog child dialog to collect any remaining details. + * Second step in the waterfall. This will use LUIS to attempt to extract the origin, + * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect + * any remaining details. + * * @param stepContext A {@link WaterfallStepContext} * @return A {@link DialogTurnResult} */ @@ -106,53 +112,65 @@ private CompletableFuture actStep(WaterfallStepContext stepCon ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); // Show a warning for Origin and Destination if we can't resolve them. - return showWarningForUnsupportedCities(stepContext.getContext(), fromEntities, toEntities) + return showWarningForUnsupportedCities( + stepContext.getContext(), fromEntities, toEntities) .thenCompose(showResult -> { - // Initialize BookingDetails with any entities we may have found in the response. - - BookingDetails bookingDetails = new BookingDetails(); - bookingDetails.setDestination(toEntities.get("airport").asText()); - bookingDetails.setOrigin(fromEntities.get("airport").asText()); - bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); - // Run the BookingDialog giving it whatever details we have from the LUIS call, - // it will fill out the remainder. - return stepContext.beginDialog("BookingDialog", bookingDetails); - } - ); + // Initialize BookingDetails with any entities we may have found in the response. + + BookingDetails bookingDetails = new BookingDetails(); + bookingDetails.setDestination(toEntities.get("airport").asText()); + bookingDetails.setOrigin(fromEntities.get("airport").asText()); + bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); + // Run the BookingDialog giving it whatever details we have from the LUIS call, + // it will fill out the remainder. + return stepContext.beginDialog("BookingDialog", bookingDetails); + } + ); case "GetWeather": // We haven't implemented the GetWeatherDialog so we just display a TODO message. String getWeatherMessageText = "TODO: get weather flow here"; Activity getWeatherMessage = MessageFactory - .text(getWeatherMessageText, getWeatherMessageText, InputHints.IGNORING_INPUT); - stepContext.getContext().sendActivity(getWeatherMessage); - break; + .text( + getWeatherMessageText, getWeatherMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(getWeatherMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); + default: // Catch all for unhandled intents - String didntUnderstandMessageText = String.format("Sorry, I didn't get that. Please " - + " try asking in a different way (intent was %s)", luisResult.getTopScoringIntent().intent); + String didntUnderstandMessageText = String.format( + "Sorry, I didn't get that. Please " + + " try asking in a different way (intent was %s)", + luisResult.getTopScoringIntent().intent + ); Activity didntUnderstandMessage = MessageFactory - .text(didntUnderstandMessageText, didntUnderstandMessageText, InputHints.IGNORING_INPUT); - stepContext.getContext().sendActivity(didntUnderstandMessage); - break; + .text( + didntUnderstandMessageText, didntUnderstandMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(didntUnderstandMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); } - return stepContext.next(null); }); } /** - * Shows a warning if the requested From or To cities are recognized as entities - * but they are not in the Airport entity list. - * In some cases LUIS will recognize the From and To composite entities as a valid cities - * but the From and To Airport values - * will be empty if those entity values can't be mapped to a canonical item in the Airport. - * @param turnContext A {@link WaterfallStepContext} + * Shows a warning if the requested From or To cities are recognized as entities but they are + * not in the Airport entity list. In some cases LUIS will recognize the From and To composite + * entities as a valid cities but the From and To Airport values will be empty if those entity + * values can't be mapped to a canonical item in the Airport. + * + * @param turnContext A {@link WaterfallStepContext} * @param fromEntities An ObjectNode with the entities of From object - * @param toEntities An ObjectNode with the entities of To object + * @param toEntities An ObjectNode with the entities of To object * @return A task */ - private static CompletableFuture showWarningForUnsupportedCities(TurnContext turnContext, - ObjectNode fromEntities, - ObjectNode toEntities) { + private static CompletableFuture showWarningForUnsupportedCities( + TurnContext turnContext, + ObjectNode fromEntities, + ObjectNode toEntities + ) { List unsupportedCities = new ArrayList(); if (StringUtils.isNotBlank(fromEntities.get("from").asText()) @@ -166,41 +184,49 @@ private static CompletableFuture showWarningForUnsupportedCities(TurnConte } if (!unsupportedCities.isEmpty()) { - String messageText = String.format("Sorry but the following airports are not supported: %s", - String.join(", ", unsupportedCities)); - Activity message = MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT); - turnContext.sendActivity(message).thenApply(sendResult -> null); + String messageText = String.format( + "Sorry but the following airports are not supported: %s", + String.join(", ", unsupportedCities) + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(message) + .thenApply(sendResult -> null); } return CompletableFuture.completedFuture(null); } /** - * This is the final step in the main waterfall dialog. - * It wraps up the sample "book a flight" interaction with a simple confirmation. + * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" + * interaction with a simple confirmation. + * * @param stepContext A {@link WaterfallStepContext} * @return A {@link DialogTurnResult} */ private CompletableFuture finalStep(WaterfallStepContext stepContext) { + CompletableFuture stepResult = CompletableFuture.completedFuture(null); + // If the child dialog ("BookingDialog") was cancelled, // the user failed to confirm or if the intent wasn't BookFlight // the Result here will be null. if (stepContext.getResult() instanceof BookingDetails) { // Now we have all the booking details call the booking service. - // If the call to the booking service was successful tell the user. - BookingDetails result = (BookingDetails) stepContext.getResult(); TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); String messageText = String.format("I have you booked to %s from %s on %s", - result.getDestination(), result.getOrigin(), travelDateMsg); - Activity message = MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT); - stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); + result.getDestination(), result.getOrigin(), travelDateMsg + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); } // Restart the main dialog with a different message the second time around String promptMessage = "What else can I do for you?"; - return stepContext.replaceDialog(getInitialDialogId(), promptMessage); + return stepResult + .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); } } From b16bdec770280a171584e316ecd275da81fe6196 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 11 Mar 2021 10:34:34 -0600 Subject: [PATCH 095/221] Added sample 15.handling-attachments (#1047) * Added sample 15.handling-attachments * Corrected package name in 15.handling-attachments --- .../microsoft/bot/connector/Attachments.java | 17 + .../bot/connector/rest/RestAttachments.java | 28 ++ samples/15.handling-attachments/LICENSE | 21 + samples/15.handling-attachments/README.md | 97 ++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/15.handling-attachments/pom.xml | 243 ++++++++++ .../bot/sample/attachments/Application.java | 65 +++ .../sample/attachments/AttachmentsBot.java | 246 +++++++++++ .../src/main/resources/application.properties | 3 + .../main/resources/architecture-resize.png | Bin 0 -> 137666 bytes .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../sample/attachments/ApplicationTest.java | 19 + 16 files changed, 1740 insertions(+) create mode 100644 samples/15.handling-attachments/LICENSE create mode 100644 samples/15.handling-attachments/README.md create mode 100644 samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/15.handling-attachments/pom.xml create mode 100644 samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java create mode 100644 samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java create mode 100644 samples/15.handling-attachments/src/main/resources/application.properties create mode 100644 samples/15.handling-attachments/src/main/resources/architecture-resize.png create mode 100644 samples/15.handling-attachments/src/main/resources/log4j2.json create mode 100644 samples/15.handling-attachments/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/15.handling-attachments/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/15.handling-attachments/src/main/webapp/index.html create mode 100644 samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Attachments.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Attachments.java index bab9b293b..2bdc3915b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Attachments.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Attachments.java @@ -35,4 +35,21 @@ public interface Attachments { * @throws IllegalArgumentException thrown if parameters fail the validation */ CompletableFuture getAttachment(String attachmentId, String viewId); + + /** + * Get the URI of an attachment view. + * @param attachmentId id of the attachment. + * @param viewId default is "original". + * @return URI of the attachment. + */ + String getAttachmentUri(String attachmentId, String viewId); + + /** + * Get the URI of an attachment view. + * @param attachmentId id of the attachment. + * @return URI of the attachment. + */ + default String getAttachmentUri(String attachmentId) { + return getAttachmentUri(attachmentId, "original"); + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java index 0ff583ac9..e8705ac53 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java @@ -7,6 +7,8 @@ package com.microsoft.bot.connector.rest; import com.microsoft.bot.connector.Async; +import java.net.URLEncoder; +import org.apache.commons.lang3.StringUtils; import retrofit2.Retrofit; import com.microsoft.bot.connector.Attachments; import com.google.common.reflect.TypeToken; @@ -165,4 +167,30 @@ private ServiceResponse getAttachmentDelegate( .registerError(ErrorResponseException.class) .build(response); } + + /** + * Get the URI of an attachment view. + * @param attachmentId id of the attachment. + * @param viewId default is "original". + * @return URI of the attachment. + */ + @Override + public String getAttachmentUri(String attachmentId, String viewId) { + if (StringUtils.isEmpty(attachmentId)) { + throw new IllegalArgumentException("Must provide an attachmentId"); + } + + if (StringUtils.isEmpty(viewId)) { + viewId = "original"; + } + + String baseUrl = client.baseUrl(); + String uri = baseUrl + + (baseUrl.endsWith("/") ? "" : "/") + + "v3/attachments/{attachmentId}/views/{viewId}"; + uri = uri.replace("{attachmentId}", URLEncoder.encode(attachmentId)); + uri = uri.replace("{viewId}", URLEncoder.encode(viewId)); + + return uri; + } } diff --git a/samples/15.handling-attachments/LICENSE b/samples/15.handling-attachments/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/15.handling-attachments/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/15.handling-attachments/README.md b/samples/15.handling-attachments/README.md new file mode 100644 index 000000000..442890d96 --- /dev/null +++ b/samples/15.handling-attachments/README.md @@ -0,0 +1,97 @@ +# Handling Attachments + +Bot Framework v4 handling attachments bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to send outgoing attachments and how to save attachments to disk. + +> **NOTE: A specific example for Microsoft Teams, demonstrating how to +upload files to Teams from a bot and how to receive a file sent to a bot as an attachment, can be found [here](../56.teams-file-upload)** + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-attachments-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot + +A message exchange between user and bot may contain cards and media attachments, such as images, video, audio, and files. +The types of attachments that may be sent and received varies by channel. Additionally, a bot may also receive file attachments. + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Attachments](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-receive-attachments?view=azure-bot-service-4.0) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json b/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json b/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/15.handling-attachments/pom.xml b/samples/15.handling-attachments/pom.xml new file mode 100644 index 000000000..bce856172 --- /dev/null +++ b/samples/15.handling-attachments/pom.xml @@ -0,0 +1,243 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-attachments + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Bot Echo sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.attachments.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + commons-io + commons-io + 2.8.0 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.attachments.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java new file mode 100644 index 000000000..c74fc3a9b --- /dev/null +++ b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.attachments; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new AttachmentsBot(); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java new file mode 100644 index 000000000..ec02e0aee --- /dev/null +++ b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.attachments; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotFrameworkAdapter; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.Attachments; +import com.microsoft.bot.connector.ConnectorClient; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.AttachmentData; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.HeroCard; +import java.io.FileOutputStream; +import java.io.InputStream; +import java.net.URL; +import java.nio.channels.Channels; +import java.nio.channels.ReadableByteChannel; +import java.util.Base64; +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + * + *

+ * This is where application specific logic for interacting with the users would be added. For this + * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link + * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. + *

+ */ +public class AttachmentsBot extends ActivityHandler { + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + return processInput(turnContext) + .thenCompose(turnContext::sendActivity) + .thenCompose(resourceResponse -> displayOptions(turnContext)); + } + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, + TurnContext turnContext + ) { + return sendWelcomeMessage(turnContext); + } + + // Greet the user and give them instructions on how to interact with the bot. + private CompletableFuture sendWelcomeMessage(TurnContext turnContext) { + return turnContext.getActivity().getMembersAdded().stream() + .filter( + member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) + ).map(member -> { + String msg = String.format( + "Welcome to AttachmentsBot %s. This bot will " + + "introduce you to Attachments. Please select an option", + member.getName() + ); + return turnContext.sendActivity(MessageFactory.text(msg)) + .thenCompose(resourceResponse -> displayOptions(turnContext)); + }) + .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); + } + + private CompletableFuture displayOptions(TurnContext turnContext) { + // Create a HeroCard with options for the user to interact with the bot. + HeroCard card = new HeroCard() {{ + setText("You can upload an image or select one of the following choices"); + + // Note that some channels require different values to be used in order to get buttons to display text. + // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may + // need to provide a value for other parameters like 'text' or 'displayText'. + setButtons( + new CardAction(ActionTypes.IM_BACK, "1. Inline Attachment", "1"), + new CardAction(ActionTypes.IM_BACK, "2. Internet Attachment", "2"), + new CardAction(ActionTypes.IM_BACK, "3. Uploaded Attachment", "3") + ); + }}; + + Activity reply = MessageFactory.attachment(card.toAttachment()); + return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); + } + + // Given the input from the message, create the response. + private CompletableFuture processInput(TurnContext turnContext) { + Activity activity = turnContext.getActivity(); + + if (activity.getAttachments() != null && !activity.getAttachments().isEmpty()) { + // We know the user is sending an attachment as there is at least one item + // in the Attachments list. + return CompletableFuture.completedFuture(handleIncomingAttachment(activity)); + } + + return handleOutgoingAttachment(turnContext, activity); + } + + private CompletableFuture handleOutgoingAttachment(TurnContext turnContext, Activity activity) { + CompletableFuture result; + + if (activity.getText().startsWith("1")) { + result = getInlineAttachment() + .thenApply(attachment -> { + Activity reply = MessageFactory.text("This is an inline attachment."); + reply.setAttachment(attachment); + return reply; + }); + } else if (activity.getText().startsWith("2")) { + result = getInternetAttachment() + .thenApply(attachment -> { + Activity reply = MessageFactory.text("This is an attachment from a HTTP URL."); + reply.setAttachment(attachment); + return reply; + }); + } else if (activity.getText().startsWith("3")) { + // Get the uploaded attachment. + result = getUploadedAttachment( + turnContext, activity.getServiceUrl(), activity.getConversation().getId()) + .thenApply(attachment -> { + Activity reply = MessageFactory.text("This is an uploaded attachment."); + reply.setAttachment(attachment); + return reply; + }); + } else { + result = CompletableFuture.completedFuture( + MessageFactory.text("Your input was not recognized please try again.") + ); + } + + return result + .exceptionally(ex -> MessageFactory.text( + "There was an error handling the attachment: " + ex.getMessage()) + ); + } + + // Handle attachments uploaded by users. The bot receives an Attachment in an Activity. + // The activity has a "List" of attachments. + // Not all channels allow users to upload files. Some channels have restrictions + // on file type, size, and other attributes. Consult the documentation for the channel for + // more information. For example Skype's limits are here + // . + private Activity handleIncomingAttachment(Activity activity) { + String replyText = ""; + for (Attachment file : activity.getAttachments()) { + try { + // Determine where the file is hosted. + URL remoteFileUrl = new URL(file.getContentUrl()); + + // Save the attachment to the local system + String localFileName = file.getName(); + + // Download the actual attachment + ReadableByteChannel remoteChannel = Channels.newChannel(remoteFileUrl.openStream()); + FileOutputStream fos = new FileOutputStream(localFileName); + + fos.getChannel().transferFrom(remoteChannel, 0, Long.MAX_VALUE); + } catch (Throwable t) { + replyText += "Attachment \"" + file.getName() + "\" failed to download.\r\n"; + } + } + + return MessageFactory.text(replyText); + } + + // Creates an inline attachment sent from the bot to the user using a base64 string. + // Using a base64 string to send an attachment will not work on all channels. + // Additionally, some channels will only allow certain file types to be sent this way. + // For example a .png file may work but a .pdf file may not on some channels. + // Please consult the channel documentation for specifics. + private CompletableFuture getInlineAttachment() { + return getEncodedFileData("architecture-resize.png") + .thenApply(encodedFileData -> { + Attachment attachment = new Attachment(); + attachment.setName("architecture-resize.png"); + attachment.setContentType("image/png"); + attachment.setContentUrl("data:image/png;base64," + encodedFileData); + return attachment; + }); + } + + // Creates an Attachment to be sent from the bot to the user from a HTTP URL. + private CompletableFuture getInternetAttachment() { + return CompletableFuture.completedFuture(new Attachment() {{ + setName("architecture-resize.png"); + setContentType("image/png"); + setContentUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png"); + }}); + } + + private CompletableFuture getUploadedAttachment(TurnContext turnContext, String serviceUrl, String conversationId) { + if (StringUtils.isEmpty(serviceUrl)) { + return Async.completeExceptionally(new IllegalArgumentException("serviceUrl")); + } + if (StringUtils.isEmpty(conversationId)) { + return Async.completeExceptionally(new IllegalArgumentException("conversationId")); + } + + ConnectorClient connector = turnContext.getTurnState() + .get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY); + Attachments attachments = connector.getAttachments(); + + return getFileData("architecture-resize.png") + .thenCompose(fileData -> { + AttachmentData attachmentData = new AttachmentData(); + attachmentData.setName("architecture-resize.png"); + attachmentData.setType("image/png"); + attachmentData.setOriginalBase64(fileData); + + return connector.getConversations().uploadAttachment(conversationId, attachmentData) + .thenApply(response -> { + String attachmentUri = attachments.getAttachmentUri(response.getId()); + + Attachment attachment = new Attachment(); + attachment.setName("architecture-resize.png"); + attachment.setContentType("image/png"); + attachment.setContentUrl(attachmentUri); + + return attachment; + }); + }); + } + + private CompletableFuture getEncodedFileData(String filename) { + return getFileData(filename) + .thenApply(fileData -> Base64.getEncoder().encodeToString(fileData)); + } + + private CompletableFuture getFileData(String filename) { + return Async.wrapBlock(() -> { + InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream(filename); + return IOUtils.toByteArray(inputStream); + }); + } +} diff --git a/samples/15.handling-attachments/src/main/resources/application.properties b/samples/15.handling-attachments/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/15.handling-attachments/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/15.handling-attachments/src/main/resources/architecture-resize.png b/samples/15.handling-attachments/src/main/resources/architecture-resize.png new file mode 100644 index 0000000000000000000000000000000000000000..e65f0f7332b79a3f65cd5a4ea6c33c7b5f979f25 GIT binary patch literal 137666 zcmeEtx_YOAr-8-0O1UTp?w5DGhp&J+% zRT=Sj)f2>jp*tTe#T3Qfy{n5wdN%$D-A8o%s_pXb9cu5t7Yq)a+>dwf-il-;#ne3w zPO{;Rum|u4yKj^bI`21p&O7DQ3VCo~K4Pn=KkF(N(ve7hux6Mklud*l6GE56obj1_ z0rPmk%OD7ezQ3NO_GoBmZ}4dcY_wck41&l-3#P3&38SU|ZE8psio5^&{lAUZ_*9_s z|2b};)<{10pD$-DVlyFd{pZ{JTvABl|NAokb*kd@|3CHL(EY!M>4J-rn;SQkSzp}q zdm8=TJUKrp|^wcl=P%YSByd^AQPmGxWS$<_!i`vhb8=&)avq-|2guGj$A^fQg(J zFO1kvd=85*UBs`{ulh}&hGNKuI=rsg9H+>?JRGB`RX3v44CUwxD1vjyT3%o;9#aiX zwu8fYowj*V$@rt{L|zJy#_*)#5PDIFxVQgw(RO=qX5fwAWw%Ri`bJpK7Mq-J2rab$ z$hzLP;C;Do)bzT~%t5!_bzY;Im4@wuH{;Of@}=2fJ%&V}nTmk{lvFN@%7mbn7DECK zY_R2jz8uOe#-@tIW%=3AK*jHIW+NvHxfmY}4o`GEnqyM~js9eUt-+~TeYD{mi9^pN zPxeFB;j|3|`$H6o3#{?4MoO-HzwgjuU`?yU7iYzWCO2`t8nogIG&`^t38q;&u@@_5 z#sp|F%T%O{vwB^bJwF}0-nO`RKJofL@2&JFZO3-w8ts7s6rcl8p;^;2&|o>$X^S`Z zY)?}>+VpJm@D@j<#oSSfU)=lgYAdx&?c&sfN`anG@o2aIFNev_AmwLXbVI*e>SD$0 z;+*u!Zv2=J+UIV!5o|lQgBfnetd@6QW_hR_s+sXQyg$<@W_;3G#KW?W)2w^c{I8io zASAeXOcth?$&=^&SU;RXMa%4E(6@`YGhHvG)@ZY?xrc-ltDGl26hqQCU!k>iXcX#- zJN|6K%ccVRC?f~{UE1AGbtH#7lc_8x1iHa0ZIk#h&ZikAYgBiRsbI|l zS*8!6A;!xy)TJs_44~v1^1CTS=tTWtXheQ@vJuS#_Qjf*m?&OrB#K|6o}8kutH{aG z+GL(Ah>VQX!NVj3&RQz%T;v7SlgZR6YVSVYOD$CTTAa05W)8T&@&QOQxa`A>bJ0TE z+uOb2IKrYU^($z~wQ3N3e0fwA3lBv6A~)O**oTOFo87N ztFm~?bw%ATeVMFS+}!a~OO9nqXnf(RHk8N_^g3@43BQUvPd>O0ThhC$ySw?RU1m4pRc9U(69WRyp4C-C?$3idgM=cmnUT+RH-^a}tf>Ob!Uj!pS#i#ex|VGY zDS0+}-2rbfe4)s#d#6EFsyr%xEJstulXjQ`SPUY9(-7KHa{I(478e$9AFmH2+Uobt zeBRs!X0cYA0YiK8sr?2+cSS_)y!bh|hwj zYT8OQ=Ub>>%y2Eb@Add%sY*d}#FKNahv?7ukJI^5NV{Xn$1&f~F$n10jaZbjcq#nn zsWGIORnNhl0eu6^GqIzYJS+~7M@AyAt-wn!C0gzT&qyXgnn~HaljZYEt|dChi$7vs z7ItRc(qJ0i&)AUubAB5CIcOXX04>^va5vBew!I$N>&-?e_wobW@d*YT_#>H6amk!x zlahrU(y|P3>s(TX+PA$lavJnO8xN3mToIm@D=n+zi5wxuJ4hTqczYmn>&{+;8t$=U zwu@$PXnhA3L!|p1aa=w0Nn$Q`R!5>Zwhu|e!NG;U`N*9(D|s4RV+PppFb~%?SWdbR z;rS;|`61-OE&?{hTdLQmYQ^k*Uv51fta~LZ4V&IK96ZN#Jbv2lFPlx{cVnoIL~pEb z77q*)Au1b#biXm{v{={N#gmHzrZ!`xBzcW?V@1k1oeziEd?B}551tqAIPKQn{}dBD zoIN0!!;JS(VyGIWe9vy?IFuW}^g|8va>whI9}eZ|-R;prO6UP)$3R^e7K|AH5J@W3 zUSK*jQJnP$G;ezW86&U}BKryq-JeLt>Jui3Frk*;xo^9!v%92p60e-gbQ6xFj=&pi z0sGOS&2{F7eu!MczkIppWs(ku>^xo?zgVB(@3MTy$741a+#N|QOz5`q{)3hL<7B>o zapP?5G^WAj^vNxXl#4L6-)MPdncgx_Iy!@wZbCCRXML*pMYq_O=VJbX1p|MxCYU3{ z_0CTFa(vOSKs0X`a{mGDqv=iBG%vNeS<$yX6HD6?kZQi96Aoz#oq5LPszRlHr%u18 z;OaDkMMtht%iZ=l$Mq0OmRp?W{l=XCMN#8qy4%!BBSq~vFR70&_Ipy-^Z)%76Ohq zquSom=>n&RO_)a}?K@tLSHNypg?2^Eia~+bvL4RXT$Kwg@*#xzx9hpJ+(&7UOTGP$ z_j7~Y$C+?ts?ZqXfh2mh&0|0L<=ocR?@sP(7iR)~Qi&wTL|bfc0@Vo*O1pZuX^CJ9@d`%jELmUU+SnvrBGTVnaQ{36;YE z?KBwY_D}^~nu9ak-i!xX)TWN2xyudBBt2W5o3D1qe#)BQ&9)I4O1}vD*Dp==m1?&$ zr@S<%cP@p;TV~$91-wC?l$JVTdF2ZNh=VvvzA;RX*3!en3;42~sHvA}s_ju+zdmC{ zz8TPQGmDJ{FFDt_y};i{RCjz#syaXtfW=HEX>4j5{ANOSjYAg|xjm(r!K+O?)WBdS zLP4Rju=>mPpk=y9*JHT>=Y65W(M%au2Dg2A|FMSCegqC<6aj|?0})^(%?YdS42*+6 zbuQ$3lH`kx5|H}0{&Xcks~j8}94fP!ExbNmRB?H4De{LR2tH3?xt`bi`Llz!K2BX0 zmJrwrtsvk*<>dTbBWGt4H3XG3;pryeaJ5}v#p5I}rxWtabIbSPd9+56&vTsCXsEf0 zp>T=(ZF#f~>~~MnFK<`?|FyF>d*cX6Y_n1#B(M{2xu#r7G%PR zS1Hx%Ce7fkj0MO<#aV7Sbmk_hSx$^_2*3$}&mp11!~1lsmf)>4`l#*k`5z&} zp*_DNyE%{5c&i3>cjwj%5zLefU2NqRWG%J4aXSq1yb;A*io}XV>$uIkn;qo%nWzc( z4WGY;{zf6(=~xQE0Q*OhV;D6GV0%7FS@~11?vEsXv*$RE{dk336ZbOL`@6{&gP1rN zAhonmsrK#UvONp|2`hI*WhQ94)L7s5b>I!1{`Fu|OKF$%Ze|Xljk9t9lZ@!%ttYi=ZX{hVFZis9342*I-Ppk=1lZ zneqAx7ts9_%&CCSBV8mjG)TDN-QG~x#C5XPHBX)#wKbDm0YnofHbWoUaJ;#oNM>Q9 z=$SeXf&sD!LnQrpsA8h?ysr0wC~s9NU@3IM6^}!sQniv8vS#q>4 z9YFYj28+a~W)+It@d8&A8P5Xx;W?mnZ^zbZy|^mR?N}4YsKs378ycI-s$OBxcX25b zglw=Vs6pUyvcfcEZ+-Zn=`#{<6c!W1mc{P|Qf_xA=>sF}6FkGdNABwCQgtY6PBh*< ztOF9xR}kScKg^=zU|6?$jjm3$7zXsk*z3+c<#dAC8m*?sYW>#<-2|)OT$k|b6}kEr zn26L#um0j0657m{n}2V&XriE`T&s1u#+9gAX+j>);Nq&(YnI%@I2fAhv;+k4(!x1~ zsjvLyI6$^vZQ^h}UeWgZ76Y~z=jODYS*kTJxbz(Qmc!oYy@S#hOV!O2_DkJbKDCN< zuEG#}al6=uVr)zS94@8nE9bIB(J0sCLMFNkr=Xxv+9MJ$&d*DE&dKC?E%8TU+Xmk6 z`eyRjnzXu~)`K{_j#z;FO}2#nDH+=_?IqNg+Vxim~2*}r9SuRMqkGw;x$eqMuxZ!x!kC9AE$v$|Y zIn=UyUtxbZTT-$$QzQ?Bg-0uO1HW7pP<#UF3_b zIa!D%k9m;Yj^;U{Al6a81158XY#;n4^dt&s6p`gqzi{rc<99f$ak~Cp7@sOPN2-f0 zndfj)Z1!t+!ILPEMo}l+68`OWZvEhOg9b+i0QB7+8-P%0huY8Yd0cTJn1q;Sa=dGm7f#xMIwm#Uk6^rFJ-EJ%+c$!8%?bu{JL`wz*hdrC z9h3m(hgIb=XPmyMziJ9?$iQ4$1(oKUgL;p1hdOy<;Iv3AhdK_V!S3(6%j;1l00lbx zIEh8vC6{x5{2-S|Jrgo(-v>I@>6%?HoC3YCvI3s|q6c+@?Iq023XRu-N8m8pKo)DQ z)*TLgKz};~)K8piN#~a)ncAT!4;K;=eZX530S`M4Nhv9!=R#(Ji!GzNZ*Is$Je6t? z%k{ZNZ5KXi{XQ*2KWpBA*DPajm!FNk>O@wzk=0sbS^cTVGpqPZimY~{U0a@ikT=k< zSSbY%4y?U!tjnA2uTJJTS#OZvLqz_>?Rq{vs|+2?KA9kR!if=o)Cm3Swo^;1#VtjN zw|lV0<5*NC>&TS4-7B;~N3LXRA3Yaw?&ldy?%jBSM@eSXV2^q@BXWy|_E*SQG>Yuc z-zGwd^lKlW~e66 zs^_4sv_czJd?jL$@|;;B9sq&&jn~kWsBfC73|hJGjZ4Oei%z?eecv2?Vv=E#f1k7j zmKUkd?MeQXUh1h1^n;7&zg;}n4T2(WX6q>UpmWLbWQi>(S5Q~2_#027!#v3GvX?J8R(Q2|T-xx<_mefc zO9~!`?<}wSQ4cKfz`q<81m)rDy4`1n$wOMtNff!hCWp+zIW_l+mlOaHpG>)%gz)3s z)P45G5yvH#&v*TtX^*EH?kph}o#oXXS|x&&=5r$}aCOxZKT9&;G$1pnoA&+M#bkoi z5rJW|b8=q&gO*4vNzqk@+~K0uWOt(4t+R`Vmy;7~XT2Et_j%_(EOPG5|d4L`i5 zqd7GIKhpkWzX|1T)nBLcgO%ebY^bZb|v1)Taa(wu~lp}k2*BKoIxyyIn`{dc~qQ%QEKp}Hy zm&W#jH&?Aob^xtD%dm!~3Ve7t?H9yJVvj@7-rl1sF)y_m7p%nAuz@|e z4By|~5^-sQN0S&3orXVmcN2QWJl`&Y=~U@NfB|g8)zyDDcMroBm@*1vuTyxvwmJLL zC>Ur^SGfVIi|JKApPBIeFPCeL!y@5h$b`!8aIo;;Z5fH?1BbzlU-JU3l)K|A+ujH< zFMk?QE#4pG4wk3z1HbsPn}vOnxV`D9yb3e!6Om8N*IQOSoQLQq4^2#l|EY&Nk|3ropAH$==CDD*lrLj?|&2D;VnM=vZ`QB0~ zSJRqsTwaFMquB_sQm>eE`-(5<$qHSRkS^=k7#MyqLClVuPcPcsunc`K%a(>PkJ5(| z8B3Nrd#=crjBdN%%6*}Ws>)PHlx*fbHr3?z(R_u9 zw4F;g3|To$=gGe=!kL@IpqBhm#x6=*~5!DacTgejg&akvQ)U=k-dQ_Aj)T+_M6U zBkea@-pD5+NDikv8ExjPkz{1x%@Tg^MsDTGcOfs;PTQGQ&gpspZ|ZOBzsBdIZEA+0%g{4Wyf~<<{CYZjihfmDRgRiok-mWrj zpOR~hUkV`kHk{w&?CcsJP&647u8pUdH|}oZExpm@Qt3-_X0!O}O&?PGj2Ydn_9Bu8 zqv~tuF5kRekAtB_RN-@ZaWqjG-3LQMk!0#rJFO~dloJ6*^u=m@4Qu~xJhruZoq4&h zw39DC`$7j|1!7|1(8Tj72h?+BsO7iETiQ2I9+57(%p1Q2S0%qdx=VC6Kq*WW;(UF* zlp5H{ot@LLF zxdpqonR1MuW36?JXk_NS3Tsl4YSGm;F@9eGX_T03w2A7YPA_Oyn)t_$KjgRBe(=`^ zqevy6`~2`y*=fUKafq?YyVR7Y;*GIh`Y!G+#XKcH*vywY$hDR4bqdI(vPbZ6 zI8;{;lLIL~e-ZaXLqtl1XT^X5^%~&u-H~kF@UOt@xNAIS`VzoFb?1XrKdQul|I4iw z@mjkU;?NZOju#Dewl2T-;g!KI!Zl8 z4LaJ1-F27C-~MVL0qZxH15pGXqpy?O10X9nI6_8*==~z9!Kg{yYno`*fLF33ZOB$A z+5Bh%nM`y~!T^$bbH(-_&%$r9`BHMF=N4Cy?_8bSB;rQS1|rEfIV^A!(1IpD<|c+Z zT`w@r!*`gKwZCMH*9Pc)H$%>+ih>1Cx${K?sVet}60F#)NQvAE5|N-qkSt(@Bm{SI z_~uY#^hd@-WWks0q_ddPSjdQ!4@Z0T3E_*-F zUbm(6yks&*BiL`ukc#2>TQ9P0_^X#(4SGV_~`D>WP@1^Gl#k7$FVV_l6a@c}z#+I*-KCTZc>Lzuvt!6hsd-rZ8%3e=F z^S=JrGL378Lw;*$_jMG&dva}@Mm_nEJesOt8^s!Rdsw%V#$m~9F`g0fc)eeScbak+ zp`RLs0(&n!D$D0{hU~mILhb%g-AOHlgeRFFHdKqg(=gh}`ngLo+~*kej?0YqOFLYr zoR!!EahY}}5R-<>c8;}(^;JNjKehAdT^2Uue7OgJIz=5%r)#fvSM2Xdy!xL@dn{xV zv)!|AaUqloJ>s9!wt|Ls@W-e1N!`qZhq1-cGR^{DAP-a2`jhahgDFFt{w1ZH_?WxK z9a;}FOW%7zX&3f_E#_^@mTZPyYlrJ4En#C2crJ3^L)iC0Y&VH{l*Rr72YtY;s{t32 zQk~MNqo9EH&&{hBa}LF1mb7+qDHa=}h`%!>5+QY^@f8a_lbd!-4wN|R0H&lB;w zKG3ETh+^lw$6l1!DVsLhgk4mv9&uGbeTq$dn#)5a1$hdBPp zjv_tVW~Bw&q?5x(L`Nn+t)%co(BCrGsp4Je)C%*YhDOePbw=fyeuVL(;L5Q4=?B@~ zeMCX*TJ_E$1}!E*QeYXhdv%lIrH_I>BC4M2icbb!S6+QDG z`-DgsGfjBdXNdwb#fQ&(}5FeLWCtrsV zkL6juYA3SDX@}P6aEfB7-7=M5)6;XwuUYr>_EjGYdr-6Ia!fv1b(Q$#S0|TnUkZea zPQU@N2Bk*cm>(6cO-%@5D+>9EFzndwTD! z_49T#Koncn!FzP|zDN0n;0#NdB5$i6`~l;Ol8NY#o3p=L2Oa_6sb#iz;!hHJHD$q- zG$93#%gcO!MvpV-_Y9o($LLL1h`WUENfS#zJcq;|lMA(&erdM4&*Rq{4H{8veFqtJ zeg60fTe4Aa)ZJdEAmHuwVKWGAZl=aiq~rd~=RRuRW@M$wR=G`_ukrW-moh(Jxy?js zEQ7{YX#xSfo$FnDuVtV*@L49g>=D-SI9Zo<;4GGL#B#< zu*GZNaLiXAeT2IEBZ|diebE`CKsO$f_jx9J^uBpwnj|sb%Jp7C5byP%To#q{jMrwV zYl@_#BvGGyHj{zmv;l2h+ei-rTELsA9S5JxNRLYRW5Vd-eG2^^b5{R`Gy^;ld?L9| z7$TSOvc)mYci+bT6VvHe$I$r-4RH}9B2nFk4IjUFMh}CshpKt2eBq6a!D!NV9_csU zW|JBE(Pv&SgPnI-EUzX7l=G3TK3<+Lam23pL|it%B)Q-r`Hn8`&SHa6q_QUo)76XI z%S{5G)oA%5P4x;an6miX;7Y#b{^@~*S8kHS;c_-{o-#HYb#^o&-`+V#L)k&UOXsu6 zqcEsG_PI|AZ*Eb;gGwssznu{;$1(d!pNKdr`xe7PJ}nO{MukTXoURH(XD|MsB{IgkxAjd?ObU(?hg5f{j5ow^(gHwb2PVnvc#aCg3Ozjm#u! zwgbZi{k6v(7+tC}HEZW>qZwRn`?@9W=;(}HQ2*VpWmgO7N`y{$S$p z;cj&}^?oBRP`v)!bVzA?cnh`|dne1xRj zWX#AU;W9p>mTtbQUq5=@ z|EhM(W2Zj2o={s00S`lAES$O^S=}J_la&xr3|p@&e3>7nr9Pf!oSK#2tGhC_Z_gPd zrGoR6NKA*x&+YGwjmZQh5>na=Ic;Y?oRJb-3_XiDJx%}Mi=S^kLD`fo;wY0qQ%PPLtrf{b(^sb!K zS=R0mil&ax?zkxVI9=~|=GVD~Z-W-9b*xX$9V}*x6>YG!Tts?k>u|FAriqu0E!KbE z%Ei?IXz9P~2{~dM@5r97#4;N`SgLNGRgw;X>aX(>XrTdK_~$4U%ZEL9&- z<#oFwjv?hYliNG@&_63OuT6$jOI~g}ck89vS`oiYA)k6y(%VQB$XfFfvdJ{$3Odyj znlC9>S>anurbqU{--BGfYe&QF`Eu}DQdP~097o^B*g{W6B_r^RQr2B|w` zY%4cdFwGMF!afa*7Q&^j6R@M+r8`LZd@FDm;ABJ0L7bSc7ZSmMDc%GG22agJ0<@-* zV3)eje|(Z?!p*b?a)+wPuKgvve#a}7z8IakT~NhSgs=T@4?bGjV@%>H>M%I;Ldd16 zc}<_h`vrv^koi4=XVWiD3(Js(8}?IKIW3 z8nnBkR4i5w45A!EF=$0TcRbdlGUYtVr7jovg=+24imZgq=Z_fqvyPW1)*aUlEb7&E z*jB{KA*;EGpRQ*HA`{LzK}pOgnVohnqiG%bt0jTVRG_i3dXDQ(3)0 zW?TWJ|Ha*_{;xh#;Gdya%J6wl=W2;;Ko{J1+6NRxBNEBMo_lAqK) z{@d;N0?I;c#)|CkjHM^iF&zwexqrg_QV0wIw>rT(fV-;p@v}I!qny=S30sSlw`1R9!S_y30x4OZh_I#DwF$XBA!~jy{3(k?3+mK6{>fBH zI3D?#mlu09l?e;}&$nCnoh%pm6y{_(KN#n|u^8w-)myJXrruj~V;m@UM5SM8q`smZ&X>qW&8L3lsKfY;mS^8~_Bq)XLu+oSzU!mSn5?e*Uk3x)Ke4iuqoyMG(<}OT) z@%i$=@Jer7B1M7JYN?gJ-fWy^@{Y!w*}z2 z+Tq#yv5FrS`VqQ{u-|1w?4A#{XFj`28K_8#y0V) zJmc8{XiYZi5-O38#)&(LdAY`qSLWr+d5SMPvE8+%L)@HzjSu?2T@#5lDKD^7b2evYOvO9wrB$2%Rs90{73C>eR*oV|8@D?+;P0$yt4yDR&dyT`VDe! z+FLGTtd39Sr>iYN4Uw5EDX=G<5Cg-L)dnmv6J7V=wo1L%Si+snXr7fa`X=63`o7NP zQ19i4O zGFwj7U&8s6a}G4Vjin2a=?=)8E_Whjgt16XM$E=r| z%g?hsYKS5A9v~dl?GQ62Gipg&M2@%|FI1zTIsMjF0e84bgh)1Xk7HO8%<6sbmnp8) z{%rUopf;SVFo? znI))TvB_ro{dvbVYL;TqaBTsz6e8sA;|I8ii%ShEMgNPfchvl2%D&h*Y`2xdHk zyXTi!(3S~1Ves{fQzH)m0QeC3hlx@Abd;rA@>f__0(AtKz_G|C8FVq6CVM)mhD=f8 zhU@fWr3Ju(n(rh`EWceSNSs6oRT`KMy;kbLLzC~%NXd67KnGr8vWr5ug#;0g4@Y0N@#s~K{c5`LWT=(Soz(=>E1+^-e zmVQy^8MBG@gPX08-QAkzOZi?!2tx&Ek)IxDTMa7mh_=D2mwPd-`{xwGY@h(v>cM}$URA{XUF`4^wzVcn_Ot2OR!U9k6 zsyzJJ(>T$UfK}%AYF(X&!05%mbplT^bzItH zDht}|O@jhiM0{2K8UrD0X1(v7p5d4jgDK*l2GHRBJrBByg`83$FN0N?CIZg`=MgfC z%-I+I;8%|m!oGUHPCuxcx5~#r`Y}A)727aRcGkk<$Ty}{)@cyoAI1v4vo zV3*$7eq}Kp(CmJ`vco=Ey%(Bq70a|IB`#JEPuL!%W&ooDCEbL0r*I}kTn3{`bxzOz z02MPijWqihb?_%~M9lz0==IFJ}5igzOzLx)@ zEe?ob%}S8aZYc0|rpO^a493A10JBd9{W{!PaAif1QE6DSoH<7e@?yI0Qx!e|T99r~ zxR!sP*xmHH;P%d9Pm7hc99?@{N*L;*LB>e#-fwBQGvC>Lm5l{36S3ubIP2j~&*?|? z)aH>DD6Y0Mg-uiDgT1er)T@<083qWaZ;q3hxef5k@2`c)%mCw)Iy07;)?Y%sa`#_8j}N{2xF@9CtTCOENmTG2 zYN)n~TstK+i0#)?dkBp!UW=`=n?)Cpi!h@#Xr%~#AzKtW^kD51^u;JcuK9SmeRxGk zy`FL~Y)|T%v+FckJzeDe!l*uU+#?o_i7M4N#ysH zZ0LV6>kIP|V3vjVrSOd(hiRZAU;aJv?5p`WrfuS{Ic{z#oUrCH(jiECf8BZjvL`h+ zouSl0x8W2wa-`evJyzTWr4T@S3DLaX5U15ivI?!5o`rEDHs@WBQY|nqZOngJ7oeVl z>+)Y8^Q28p&R=1_Uo0BhI56DNZV`E)p?Tc=T_>6^cR7lKZxQ^Df5939+G}TgcKnrt zKe1;CsCf$ij3P7jKZgx)pk9YU?YaI*#KA5m;-BB{SO0S?2N`;%HLgKwNMBnSR8cr{*5G7G2W@2&?`IZh{*QRyK$uo3doUOg%W-{lHtEW0N!px` z!A3_iSp=oI$r>tvX-EsjS;))(-xr&q^nZnEbvwot4-8VIuYv2HOb=qp6!5>zW-f4JO$x6)!8 z36+iKYaA~?*)O7Iz^Vl(-W?GYWvJ%`GTot#wL6?Xl=SIjxt$E)=1XM1+9+0{ls#3h z!RYGUPStF`UbrySPyJ{0ASFYWi>a3a0T%YX)FBWbYX<$_GyCr!WC`OsUNBvo6ro_J zM1FzXL#cJOi|M#;M(8cRTfHB{A|tu>6f#f#5q9B-rE1;P^dG-Ng?GO9>n*i)a+2r5 zo%4KX#JmW9XDt8h%Brc8+S)QMtt=J)onBw21chJE68_&m_9Lv+DvC%1_3upE=5fti zD6{_We=dT2WAj zU)vjpABwm``%A0FfKIoXc*FZxQ6Wc=;6)h!*HyZcYpqrQKwev1oOEpk7YnNZM1;Nx zfRaN9d?Fk_e~LL9+#l>TMMV70oD?A^U-nJ8NYf*gbav)uWmV3OrAUtP%7e!9KMx5# zayC7xuT*GAz~3Zoegnej{adr0eWoy6w)w}?7M0%$d}`$wJX&~jl`2^7?ss8P5j{5q z9Fl^!(y>ra#NV>uxsjbAg|?r+HjR95riZ?FLm{^;Av__UJ9RI3WKS|EF|6+O`rt$H zbblPmn92-g#@tUfg8UQwZ!TcC;6OKqmcPE8w-0%rcX~LT9Ac8c z*1nl4*q%8*M$kej?uh;I%<^mU%oe+l*cKkB*#v3XWnb0Wg6=hg)4}EmZfSLOXDrgZ>qaJjw#rzQs*d2?=G8mniruyS} zv2)YNGV}_EFxKc>(Vr|ypt=IN6y_kDx9WeKpJLmOPGX>c9v5^E z*DGjsn(adwa~3y!jtR@Psv_3BuD8427Aow&nz>@TpD%|nBr{O@R14Cjv7o&gT;M(3 z^N?2ftu`M{SEQcwVJ&ua%yC1-(qe%WFtEbU=;os-QYA3}yv`T?Y50wP&3AhEE1Y4@x00&j( zBI~!iY3=P93Azz+_LsRFY@G+JrpbONN*>PAYpai>%Zh?SA=+-plTazM;etl{ryjAv zT4hFC(;t?X`5_nAt%z1Lg@}VuPZ-+u7JV>vlb@%J{v=S#8(Z4{`GAqeWxF80&fj#o zlU9d-{Hdon`mM}&#Od|4eIA)QD+5iSSFgySpE;kZ{`C9-HA+n+gM^rqfN|(x9q&1}D$gXRrAg=y?YE4{Jn_ z2O-~TegRzpUS57mUS3HlQoRmOm7+SF@s*=$2q_j(q($^GmBjv+V(eeO>Fh@=kV-b> zChzeKUau!&-%dcOYjbhPhmXJ9v-ywOtCpzY;rUXQs7NDOeusT$qOZC9J|M|TF-=J3 z|M1iM5eLikB-M)V-PmO!?Uvs)9JFyVmt!*#wjAw>9ueCz2Utd*>9?n={!kzgiWAb+ ze0T(-T)R5fhBif4dH1;Ao$%?UT>Wlu&x9*%^ReH?+LqF5{vk7{0KK`=;cNi&wW0Z$7KpG za7jp!n&yyoOMwXo6@LhiqnNMI4C*Yvaz9%s>jJFT<3R06V&RyBVust)=jYv%Dq9KQ zfH$GRShCoFRF>|8@5ziweSgHr-&u8-_>fDYkpvWr}d;d`piN&D)n}>65V*I98qQ5I0{;-Q>1Dop>VZ zc0yR%VXF^Z7DdN~?3NJ+l%6$#NSJ5R>2*8Fhh$D<@u_{-0HpDFFweA?NHi0B=t?9) zLD3^F8`I_b5-Ek>DlFlhv;9Tr4v>4y8ZQUVQ%j3 zG{L!=A86q3O&!E`jX06%&5_KMHJv*iDR)|3Q6J34Qk$aMd*6{3FRCBo5!||;n+^Z ze~KDUs4T17UC(LSxnlz zD!9YK75OvDaj5hDHhk2?4v%dHOA`TpBw~fs;XoVMyz239A}_&prb8fhxWp1Au~YOz(dIc(j#y1zludsbpf*(I`@#b_UC6e7dq} zk#N7D2R&YAhu*ZGcd4N!LswBjq!ZX-O);R$6*ZQjFab$b5=Kx;S_p+M@-pGbg7_yD zXt{ngRjIAkPfKI+cP3!>vCBq|3zHBx#t(8=I@x}IBHe7K%?2eFA2Uf%V^E=1ye?Y3 zoA|Voq>2WPqq(Y}$kb}1k?jGfx>d9Q7(K^d@3-o{I~=bV&7Z+!%Z*bGa&%`rZf99F zH8Gtt)WXAq$}cfrL>}J7V`Sa+EUmab96CWok9o#Ha7XRbT(^cok{FA_mWIA4C$bQ8u<4#+`qchj#9HsVO)ishkx_BxGLc^hN1eA|XG z2m@k-W2o2%9&K*NDLO;bnR@#ADs?7Ov!u{cdVge#?iaD|pP`|Hwog5UX|WQk(iApl zlh7h%MwqYOLM-$SP%dT&^cBQ@tJZ;5CErZe1Kze^F|;}SUo@Ple|?|czGy# z)NS-W)ltY4#F(HQ{<1oSi4uMvBBdIpJp6saORL*osDFUgWWHQunAx08<>-vA1nSkp z5bz*6UZ`Xvu_Y<~mR=~Cb)vBM==Jo$oQ-Vx#FaCTD>sbmL!I4zFJmq*O`gt#~a zU0vI`YBfB6n4Slx-TR9UXKlqtr<+5zc?74eXZl&qdr}TjUp3S@7pOM?C;vB8)T`Hi z6i&R{P<~s6_^*8ngxW~9`_M$x0TX}eE?Kgv8+OdjLi-#mpx|!BgZn@CyN;DSzUDx! zqP*^P{$`!0-U>IWo`i8Zbdf?4CgJ}8JMhiP)p>AoXRsorjjceshsuxtmY`NFwmMs- zT+d4(gPo)bY!%JdV3DseiW?Sg{>ERxlwX<`U0Y{0q83K3;vXL(3d-c5l}ln~3S=RL zqH{q_(QLgD9kUhipd`jn`tIk5g(_XsyGrm>y}1iUoRf#n`X@a4h@!VjQD|2-w-!(m zLpDI0>l>xlb{%$sI5XntAmC?q`}V67Jp*`%5nP?X@-zN=Xt8 zQWMkB)X+gwi{Uh}@Xa0xVQ9pXu-fdR(dqvFEd2H?-z3asOpSMDAX_}IT0ZAetuR*CH9W2HLqk3wq23Lvz6d!yi_f&wZ%_0c>G;P23oP>O;F z$rR(<_;{7Du&5vKGP??c%>=PHL1XD0eX{-Ku{A90a_M94aa7O@Mi46>5IOzb?6Mvw zjnm=F{zOhluhcn!q7h0Mz(9>IHsqOw2V>XBh=?iIqrQ_!yfOZAu`JnTTnUaM{w;p< zqwQIIE_9b3kUM*v6>D5GVc{;-iJK!{izQV&5*+HGWLv67{6IoX;>xS#m5olCo&@Qc zl5aJy8Hi}H%H~mcbtn5NXE|5bziTz72GX|*JxZ1~tx7xGCzO({GlBhEon9KakFS}o)n8~e!Y{Qvh8Mu`W z^|h4AA!DY5AVIK!KwyUIzp7*plY}VfNP{8o9D?jNU#Q`Vlt-i3TYL{Sw3qdmAAX*% z_+KoYQ(zul+qT=rwr$%s+SqKI#={&Ht|9M`E2w6e24yhnOZ1tUnRw-@Wk!Gj!GAm7$_ztr20M)Le!I(Q z@lM~0_A*d`nES7~;NeK!yv#e&<#`}tqKrq6Ha^r9gRiFbU*eXLB;0~*`HvloNlz7Q zgqsg^-cbSkTLBizbxW9aniP_#h1W;DZuBd)`7L)7V#)r0VQS2Q*8exTsNtxDq=Dqo z2JJ$Nj$0aV6`8{sZ{`;Z8J@EdY5yqqyf*3Q9VwVRm28rV= zIT#WN_IcyGhZ>&Ze`GVbyj326Cx-O>j`HAx<+dG;?Pv-q0v{Xs_#fNg0cZfPAJ&a^m$llMpR56Tn# zXu6>L**7*e7W$rBp0aP9lm-M4(8IID=zx|PkN^Q>ga%po4O^TAX7VVEcuJHE@VXON zj3rgd|Ciz_$VbA6PQkT!-dn1*0c9s(OQSHSq`|!7PpzTUX|YxF({f9%5dYZk!}^-# z&Mg&-e{vgD3R<=t`JTWt%BtU(QL@a^0-iDceKt)1XqIsOU*@bJ6A9Co&Stg)0HL%d zp~>mM#X4YTo_|Rb>p0+Y*;XF3&v)=%xovd$@Zl2>T!xD@uh-o(>34aLWbrh-yz674 z2Yq~eTwPx)ktST_?DaV+lLH%|Mv)cp*^#lbq5`%UO3hmm9d7_?4BJIauiaQ$KAsbb zjus4L5ktTUI4?FJi13yv+P(;U4yoVz@3Dv6j{i|$ybFsg|8=pN)0Wc?`-u-^g~gbm zzV%Z(b~$`*w&>J~JxO%;$mr;WS!xd+?0gj+OgNkw6f&6iKY^`B!27t*j2^hO+Y6i( zdl|u^&QguORBs?ef}oG5%1L6vPGG#9#;=LAJWwTYRgFNPLTR+#>Tu$eNGaDy>@5LU zIpk{B|C%f>uFkt14)Fn_)2M!%;f)TA#$bkW`A&l>z8n%JRk|pRrGW)No3(x>Ap0V>y~i+IP$9v+@_<;$BtDPNoMy>G6E%^xN4oXyt|gM{!)AFe9_f*ViZ zud7%)=enJaYz8;V?CflD)1xts`g1y2mR_s<*W1JCCA38=cA76XzW*R@x;xz`Z6BuX z>evvlv(~Tg^HakZrhi1XAWMpi8=TCSo!Ar|tZX%zfG|TVie{z@zZYCR9`;}YIixDd z*9oLgiV$z;lf&;Ttdk7D+T zg(0SQ;pjj=|9ZMRo-3Ci0}g?srJ7vaFH*q!#5uqeb(;6b5zLTx?NOiBq!&aZo9?!d zVC7!b?iERa>Mh|X9r^;bLWGp?=fNf<%^hr$k%E&ZlVHcWvHSqWz{hoWD8|f^WIm8N zx!!UAM>GMSdZ$mZ@$Y$>nGWA8B~vSm?9SdGC^{*{+qc=ktA(8wkZ({)Nr|WLay{@; zn)u$Kh$x})=BvVP52woFSP>hsjXDx{;7OPqIy}g0%m{)}l6vqPic+c4S z;v%HkbdH#bDmo-0ZaBb_GVgQ#Rz!e&BY55BED#a3c0u@9cv~J~>MJ{%0 z>`T?@L%}u`&hBsxFZwMLSkZ|=Nf161FkZJ)6a`9|cacf5F%dGxZ8eyV@d9Xdw@5M1sm_iAIM7qWG^!5Q!Bn=@Umf6V+3lmj$5yuRO&2u_!8k71jMC%x`wEm;R6XD@ z7^7YAI1R3aWRyC@VJMK>VqIUquXNkx;tm8dd2XI8ql~N555LG|Ug?3lY7^>h)>~(j z*gPhtQubxUd=F&cf%NTWdF@LG>+&COPMxGlOE-|RU?zcMdxRB3AM!)}SGkyd(&Or8 z_0se;M2~-3WG(aOO2F!`Lg990E{UhL?$@>5gFn3-M! zX)#p-S=reJWcmNHru@zcqM4G3e%-fDhtCk}{&keYcz38n@#>HF+T8&MXqe??&Cxnk zhmSK0adCmR&V>$q=Mi(-ASZd3qnWQu)gHa-dw*q8&ddd0usq6b_P;J+yq$(O#USJd zz1R9a5f4br9kWk~49$A4e15<6o+T-$L9`ys~33zK`D{v23A4#R6%fiQrx;2^s zb}Ma zdE0D}1js%ma|M0oJ%QEq^xt0Q1lTG3L@`&KAzW}AX-Elte>=0OtnH`6gt8klR$K^! z7PZ174+agHi3x>BMR;OAd-#cmO7)DcK-^OKMGfM`ZEwU0O0B&lCqX;8I`^~?bw?R2 z?OqUb#Gtu1c@Xb(_GAjCKdY=0QGu^!y z0oH{p6sPZ>mn$z4#uZi|o~&oju!s_3`6$u7Jy&PFp$85glKxOR%bx}0zUAK!aUw*X zj6!lJr%^v|V(!XRN-i>ktS4G?XR4&Q2Er3D(9mL*TQxCGc+9W1N>GD>?e-7fpP^_Y zsDU7qUZ(MEZE^uaC=Smp7n>a2?-x~U?F7DbD08;F_4Ur+f*Y_u*I$jI*#xC=gXIs3 z4~&Rj4|R_+#I-Z>_WHLDBd0;4STN!^{?WY_<)(6_rDxTXKdop1;=khd#NwGiW{ltJ1U@kbBD(Dk zc-`+!gdD3rp+-$0DNw*}?Okrw)gw|uhRB3ssPj%$JJ?0Fzbd7@%QsyUheV2OxUFqS z6M3XUH&Fk29@RPv^K#iwTFYAuln$#yXr;K(&sszioC`lDCsdH|Da zRkvXzAFaPuR-VmpMO2tlJ|}Q;t$8c+QgSPd$Zy~;!YAM@pj6zX0Q3#C=6yf3pIXiy zb)w&&sm@kgkbbtiaqYLl=~SrQg(uA_57c+a8lMtF*d6~XTHpoNmiM;GkpbLtdWVI~ zcd2X&T-kRHW4-CO^$Uv*0YqEvzuq1L;CjPqc7DqdBXHMkp{F8s)XKGA7_)g|;;%F@ z{&~EQ@rYA!=c$g8{)qPY$sI}5O%Mx+#{)=m;d_&CZjounQu5=QgN>|#DD3Ix))4#V z+Q6IZ7pnKSeuHt(Rq#iz=Lw~(&Vw*l0ndE%E_!GL7zuMt@GR9! ziDAq}PpP!Q?@={fsW!I#E348K9S0Hozucc2oFfvQk;Qn0oeK(8O4Um&B0_;3WLt31;L&$TZ&!JG5WQaqGEi;7|AjCbXuK~&NC0Y=AJwH^lx})kzv&~w$Z#_ z8(TrEy&nlVki#V&1C5iTS2>&<8JbL}e0g>>m9h@!8j8k^!A`};ewsyg_G#dWy?8-_ zQ@0H)2OEo-CBP@!-6w#G! ztQW&SB;};UyoQN3&$kC}wgUxPQ_$vXEVTe$P~Uz{{4CU_u+!@d#Lm1sdbjoK__SqO zmUuN-Jc9^taF7h5oQxMR=5;q24U5g@&HvH%yY|P8qkQ6xxk#F%QvRqYcX~+D4~3xG zb#){Dto4EA<&urt8EhDYxPkUuIVI7n5(mHS0|ucoi2X1ZhYq?ga`NXY#+zZ;>)h)# zXAkqE*<&_B>goQ!j;*HwKk=pguYxT%a!4&1r&DgeGs8M4Lfr?J9%#vqY#2ocPD~G#( z1tN@USWQL)c0z8pmJ`>J*`EeL6 z*6!LvH-NkG*F~%@&_k(2#A&`#5x%|oA|N?CyDl!bi3}3{8Qvy?L%e}Ad$v*!B8$fr z{DTCA6uU|l$HFb&*jTk&j7vj`28p2cwY~1jR6EfXka)#aoBu24z+EZ_zmz#dp?R)Y zv^)Yv3j(VkUA^{qNjT}Ex-Xiq??!ry zGu)5G&oAB6dIh4=s?gLr-x5_ZX(G$B_jei=ShC^xYu5X=@)DIw`VW^~jGB`SGeCcRbkrUFSU)QU{MN>n>_Imufk6`O9UG`&$Q-b`1ZC zwN;4U)X-MAStT~A!CDbFJs(zqBvnk}J?Ju8RMjLuoED7oJXTydNu-HWkkLA#!Np!= zg|*23zE5i%u{EXvi(jeH{kyx#SYZD1?bcp|F?7dy(0I!?r7ls36+eFOpl{m-WW>*-$j@3uV)zvjiHAf$#5MVIKz~AiCl|n|rq)y|9OPVGQYJcns zv#_u@)EaG@>X?c$O!dc`hh(idk&rOrCa{niZRidbJ6>q$h=)LJUxNJplc!$IN)hdX z2*S3{1{VFr7i!|G{=h_Bmn*o?^VwMD@U8mD1u63XT;p@^OCRrR~4OC&-D(wrL$HL3FP+ zC#v~m4M?EuO677Bj_$XoTYE7@G@2pDCSUTIpyP&@2{f?I);-`6coBz@60tJXE!o}j zwswWt{sSJXf=44nZ-M&8tT4?~4i|bJ{&J{jQqUwhM$oS`Ns%<5@RBSA7-?Pc?DR+- zPUk9Mj1NX(sh#plb(#5XSLw7Ey**to*BSr6SEC69d7t9P~-2z}W;3MgpZ zCkp*%8W}}Uo7!FE@cxe{i{AqqWzm-UXQTg#mz|9%7@m&V-FrOg8JRb=(b;%SSAQJH zC$x1WoX>RY4~x1ZVI$4;b=}Ppq$_%rJ{8%5qsh+&S+q#oX5^T_4OiK1@Vx%II_E-M$jnc!J zKt9kulg_Sahn>OfAKzBrBY)Ja0~ea_|5&N%|8Xm)bYc@&O$izQXzwQ*>+=C6Qhfi| zi_Nz7$U$B$UoM5t+W7l*(@egn$52Er#BYyf-{~JejZ$%H??sTAQho22$``YWa2BOo z_}xMjg`o%^mJtV>>D`^e(5p{SEsgo=y-u*;;NEtI1`#5Q!dfQ&FA?(5Ivsv0b4G;3kvqXYVK|oW z^nuKJ;6L;3IMbd(@yhob#9sf!rmfGbmAinE?)bOJ@EExpYa~tm3aUA6+p^Ij!jVw| zpRhO&g&aIrs__^2u)KuSU=g4_`PX;|;YbI8SUMbsl zQyY2H3yS)b=rY=mXG@$9a`gX3x+D7S-JXvdaWE1~DxohVfzON+&~8Lt8X@});j$yaOG_+MZtIZi>aC}Lum zyF$N}5E2QUv-;VnS}PN7SIjoG1JJDR%rNJsCjN54bGvo^T(M`!(Uon;GFZ4(^YL@b z#hplS(-~R*A{_;rk8ZM;drWrSCmY_%i}0We7E!*7FN2Mo>DqvUpIob#=AsP*)-K# zRwZwXA4zNYxcO;m6Q$Mjj+v69D$;iDpun<)(JfX1Y^;8hD3H9g-{ets%7*XJoG8TS3oAnX3!^cJM+E(jecDej{2Kh-o? zF0wf_7N4@l>F(!hzsqD#+_v4mW91@;dg=czJH^B}iW}@)8?mFsNIQ#9#ycc2(JxJ% ztPH`snr&f2L7_JqPjF}`#wdf)d#jaBqSZ&grSgrwDS}N?=f@R5+CKgIdOYhOyQ;v% z^YrGB7hXs`9!~)M8;IYVv?f$K7^}vJ#j957ca&6bht@APnP-h;iNfB0?(v37 z@q^gw;S8`nKG^S1#m%Bh^yyZH^By|VxZZ~m8?;&|hd@F?np^eqnCA}`x`-4$=YJ^8 z=*MKKUk&&O{60DmwVOv-ky<0`(8$&dt1W{-M0U^n6S}y8i3BP=uVbfS!h(vM<^95{ zj{6|5Xsw#CFW;z; zKLY!GA(h*g0Xex`HRZOhw{o1(eU~wsr~Vd%e>tZ#zsbpL>Q;Q^=v7OxpEab z{Eli>CKp7*BH?hkAC|#&Qi5i!uh0ol|0*@m&+2x^fozw>7H|cxH?!}r*Fw7iS6oNLgGHNwm3i3=5$!=_v!1x z^871mXv>rdUM@cY5D5f!W@6X6{v2?`Mu?r2V&2-zD1z>E0c(0i#3V67T9Tcz|B(wJ zg*7Pw=@Bo(X;BUyrxpA>F8KJ#ft2cMi+1fscRTV~ z-ypfg!DPai2BN+yRiAf2v$s6&CYAq|An0bBJheEumDy6rPZv*PC!&T~tfHC?`U}&` z)e&DNENGl7{9UNcGi1!m6lA;1(h;{CJ43n{*SY zCOPEo5wpK#)Pe4{)ttq4cvgJR{nwkNBbG2Ar~+=i;n#k#A(K-rzDN`&be=dmjRouk*L=t6iX*Yg_Gw`Yn(TxY)^e|N@(=>SuSQ|2p2 zr;O)qU-+%vXPjvKV=e5V%90qYq}3gs`)H)g$9vZzBoG2F-+VT|m?J|2W>E`J>8j@v zQ9!rlz~zcX@<#I5o>FlJ`FTvIoqL#@m%fZ+!tjn)QrT^Gc$jUw$>a56iZ&p7&tIpf zCi@0_gGlmUFjs~{*Z;3`^IzMThKZ=le0$vWr5N8kg2|Q;{Px>UNz;-= z0eCe|hb{hp?Y)x%Y?cdUQTR9;^Xq|}53%+RE}|*;&x+l81l%iY!U%6hHh>h9wr^ua z{cQu8MuQX?x<$5ardWb3_$i9P*e;bK!hE$)^33e-UHVL=ix+#D3dBRc=Pp{6V!5?eVQM`#RV!ByhtIDhMm2no!OtI; z)<{J9LM9VANOsLuU!kbD(3#XVfBwmOzO8tdl$6Yqj;J5Wq@z<TlzcH>N#QeDdoS=>^o^*$0fZ*F2$b+X4bUFC=+Ul3nw^emomd zFDl@LxfM@|?K29fR%n%8%Dt(Uz{9Fn1R<$2hCR0Fq^-4sRTJ^~pWWC?g@yK9o?x)Q zKgl!_vM4u^>Irybce|SVqsC}Q`mfR(O~53G_=aN{>orN~JHMO$_0}kHhDxDh}>BZ2I_f%bK&rdL3RvxCJ5^;hR3r?$@>&*qaMdVc?aLz2$xNw3&bnT2U1 z!ADS%AGhFC8sXpxr)UdK)ZkI%A2hM{yE)(G$6;Cby$PC~8Nx0Nfm!Dn=0*Y@?RU;o z$#*0j%8&5qXSlwp6^Isj0%Bp3;B_X;T=$TmPw-YM&LG|xJC|np6y>ihh@A}89}XEg3`s~OQ3(o7Zk@u6p~#)_wjy! z_LTD|(J&oaaM{;HDvUa#3BFn@fHiCPERR81mSaf+i4c)pz%bTy98O$gRo;!S(`rP9 z(CTTxltnSUFQAk3RFCH1=>gKC(EHJ1U4qM{@p(m2O|TkAkV!8|m*t)DeK+|7Sg#5N3*ipj4K8dCleraR zEQBa?Y zcdR-dX{^KrvDsTclb&I+Xg+p|Jb6#!c)L8oZldN9;-FRzypA?i)H)4`s~)f8=@YaO zn+irgrds#xi@nyFo|t2ZW@bG5%u;#_TI#K$28sI;#fsye5e3G+OYXiU;}v7zBE$>% z`R*6aps>TWh4u35nhUkoDj$fx%27qYNpbHPeb_c%9xV1y1q>UlP=hyN&ztMfACxTU zy?vq5xyC6xk%UgS&Rt}Ge>NZw*va5uyC_OSK5ASSEWb~u1 zrXI;RHtB@biu)#YXA&D6>~3sTPOn=dIPOcRUd(;1RbVPKG5#(gCF8p)uazdBXCdg@ z5w32m*`Z{9!)=|gA71R>P*3zcA(x(es>VYRhZ)2ax}=9(tfqPhx>Bsf|6utbTqgL8 zc_tF=sG&3KeO&jzs2;(5nX^jm?;VA(i^Q&Ta}gEI!hls}iun63B;xK*G-bC!Eh-(I z0HyN})$nj&Mum#I#7zUKm;&CIe(?{_4*C0--mdf?5w4oP&l8E(!x5MovM^-VH66JC z2iJbfg)z-Po&@V%-gsDUw!$l1ynjTaGRCeHgE?Mzq?T9swr;wzYgu6W(yo}*nt%0o zBNrG%RZ2MK9qCl!|A_H6 z9WS_mBEk{lF)dqp729J}!#r%MPDR^Qm+a4|kd<)$v>{oqHxX_&l1N}|$Aew6f(AvI zIh^jHaG+W(+xTT|G)hiY5wc>@{yI~zU`%>5joQpBx{aUeCx{92g`j$L-r}#if4P@> zWtkhGjwIG?P_1_IY{Vo1%F@-|uS3+D1&I8Qm#oc*+BxR5J9}+bN3U&JP_Jm3(Ud8_ z4EFa-;_z}d&DZzwRPrT_;qiRd6}cL)*_jmj6VHN9El#m7QZo9kfw3$h;trV9tG=6f zAxNKa!|@0-4c_$Q#4Dhuccmeiv)rjHewId>UvU$q+juKa@D}-8;bi$v!&7rQ$puyHfIK86lIs)Q1}wUm=x5dn zpKH&d&|7X!%UUA8X*$}Zof;&souvo6t(F$;(aQ(Qz-m2FReR)o(SC{=i;^sRU!fuk7PG0soG#)^vefQ_`b3Lw4D$hY4ZqFyYeSbbxLzySF|v0xSm zQIW%J7fJ8KC|Xmv5#o;&&+WAxU7zaPOQ#Zk`aa@Na@9mK=Mpy|yh0U^XMjoIv5O?U z$n8JLDYnWRvlDQBLlAmdZ6)IHM1=VDCSa77&ulkHJMy!OHkRQtbAx6-YpGwo+Gh4e zPPM{9nP_Uv-E=|llCFkc7Y_>!0t$-@y?R0VClVus)x1QINdN53X|CbnUilP5)Ae<@ zR;&9Mn!Wr!OI2Ky6feDY2Tu1#+h)?ASz!difmuEtRC7n*&Fqb(GQ|NF8l(V4H`Zpv z8$yU>J3jc94JSc#%c7g~Zp9r&+;S+4h35DyYb$DZTOszT)XM-=a zjdnmntn$^50TgRW=k|rsk+g%m=G#tM1Ja%w+Q$&Yv`(@3Xm7$W`LkD`Aem85iBeQZDDf>!`8>| zAklcZ$D)$78FM|t$_MI2?h??gcI&4`k>^4oLnEq;F;Di|s0)RDix z&AraEG%-P8?dH+vB}8Ivr<8bOm`rFupANqv8Fsap5HTb}Gi?6Bsg5FE{}{*Kt5F6^ zhs6}d?^hW-z8*RXt>U`zG1}hKzz;3>oQx%&*>|n_F+!0bzz^MvwbXL#{~a`JAwP;yXmSBYJkg z+k)jz@gW9&ij!;d=`qRD`&iCTO|~1W1tb=`BFk2veO68dTrdq9xbcCmER=w4=zAD< z^K?B-0ig_oGdAZ3ayp2{-H1P(xYGQ^ zGA0XDn z43?|4ZIP!a}_dk z$`FRQPxx18KHtpX=r$+Aqh=NR@v~SNFC+ukrId1ts@>Aj($8LD6t5k-sO7c}Goph# z^B8a*+m90$az9RBkptm69d@mESfLs+iaYY$W@$_ZM#c8Qwb+Py2YKJyn?&n|a7c6Z ztkb40$2s_==541P(~i|?BhAy}X`EuIs~B*?$J}NvmN73VH0KD1dbcHwWc<`SHP(FL&o%sRnKi!|jUNSeBx?o2#%i$> zo!Tk7`uG=PKbawSUtwZ(fb}e(*r8|aoL+~_*}SOyi)i9#2H7_UE552!R{=^2WgJQ= z&qw1fi;c+eV^)*Bl`6JcGx`mKcC;-;WQKvs{bH?1lG5hAAA4a*g+Pyec|6C}XvD4C zxNk4SKeKcRoteK3@xE5v@bv}2u!{<5X2Ww_ckV;g$ir@nj~Xhy)29w+qE`~gozLiR zwGQ^pjM{S2T1XDwDy3FVM%s$lGa7p;n9spWa<}2|#}9Q1zE9SETmdu{6H>10YQ&PS zktY!m(LLsaslt^_sHs+~eO^XsT6KUJuUnGkk`{k(yL@X?OfodOk|`-i?2bv)G3uaLsnLU~=s*SQ zoS-N&$JDCmku6~$b0X8J`%S&l*aj;h2TK*`W-m{*!w5Tw*DO5yH>UUb-%ozlNaU!% zDEx|#KTOf@Tc{?F^>MNgPI=Pr>ZFc+rV+x<;@GVFqj9V6f+Bet2!*LWrd@C0*M^HTCQsLL1=_rJNU*hU)P@ zC>Eg97WVGz)i1Jg^OnoiNhi=GG7tMhu5y$#=PwkmdzS{%c-T~(FExPm)e{dR> z$x@!_Z!@sioJHFRUSU}WlF&@7*exLBWv8*IEj-BAudeKORS|D0PbHWv3=NG@Pf@x? z+_m3}bfD+7;qqXb|FWFT-Yl`jjd>`Xru+fVs<3dnKF0Yqn!uR8g!*99(6Vf%T6=A$ z+^Ru9xS7OLbUD6SrWTQn0CvFB7N+C;j=`P zo@?tm!|Wp=`(@lWFX8z6raoOO&s0gC~Yu#NQA_CoDwNeno|@2-5`% zy&OziE6kv`ajd{B^*|rw20#z~k0YZ|T-%NWNMbbxFcu0WsNMVC{Ov#6Ws;!TZnx-D zYB6HI#q`C9Qj#h*V~M1p$Ci$|pa;TFdL!TIQ&abYU0}rAMY-dr(3N46yFE-h29KGo z=)q90#Co(LJdO%_Db?q^{?ha1j4I#T;w9wzF;@9NH{41F8Sz#$v&-XG53{`W9Xxm2}dg^$BAdNPSMd zmbY5xNn7Iy>wiRdYC%DM*o{C$^>vLbJTZ}i8_Sjb_*N6VBZU9A7$_;T^CXD*Qe)t+(9$ zs@v^?S1S{tsKoP~lV&)AmXmMrjnL})9k6Nfzh6VFVAcY3T(!wpfW)5LaSs{*)MEY+ z@8Yq;N;3XRF#oW6Ug77Z!E&uLXo0w}`uupe{DXTi_di@jB412$&U~cw;C|aQG+Nz4 z02~S_2Miyl|CRnnveAq`eL8>q6k@A0n=p*N7nBD2R662er#G_Qtir=o-NeCh&gEOy zo}qhjy;jeAnD|yjjpy#G!rxMIK)zoWJiQ5c4J20;_w2ecqI;fQ4i63vN=(w3V}@1! z!a*GS8(I~4MmF^kUdhx3c+=biypu3+a|;i+a@yooVbM1$doqMh)Gd;U>RWI_Kx9_> z_s{~sTj2Vzf|A}NqH+f={eGU`j-v*5`Sy;eH)lE!+-SSv8v5t1eaV zCVIGh=sR0rn`QT7kK}lRH-tG`PB)mCHm+Cuy*5Y@>6kPfTosY^5}2lgJD48YX*0G# ztUSf<;k327nSN3M_>RbM+HFSPpDuyORwovyb7jV9lOLm>+xN{xQOsR=Cr_89>OAjH z{(dG?PX1nmASnrMb3M<%t&N#(kW2(nBq=5)CXUPVx!507Vv-!w1p8nSAY(E?zC%K1 zXP=2dt()=uzcorop!*_rCV!h(`LB5>(~OWX1o|j8nW>c_W~F2=-D+@*5^x1>iKN&9 zwt{`%cH>E`es#RN8hc;K%Os7QGtzQn+7cemmdnE|-wwyg30I^nBv5pM{WHB1E?A|Q zE!2{8wqwfYjwE1Gf#&0L&+dl(>@neu5(E!@pG}zQ_&cMu-s#1KPrx&F`;+GlXZv&aobMH470u^u9nc=u2 zd1JrfVxamrthpIn26opOU*KyU2->fV?#DfIH(9WKvvc1-`Q2EljopR4Xe~hAlwNwB z6xiP_8D{v@I%vM|fG~k4gzSqIXS5obSUZ-Dr=y|J6P#z4ZXMUw?}q@x*HyWOlIDnluX;Gf&0LomrB`0 z(mil`uB9sO+CVqXQ0pIfV35GKRjUedMU)JB-D4@u7^a})M3jot|4lTrne_Q0Q^Ok8+lQDHXpaaQXbpMTES*3gI))Afn<#Gw#35&jdv$| zSt#JuUeP6CySjg(&@)1Gb3#6yG2?EmczVa^u9e-$yj>TuvQ?UP`rs}q^1($Q>v zfHry;VDY<^!1$rft$~=u?}~ruElwjDymgdpbv2SA;=3unPfs;KRX@xwD*zwEK(pP& z?eM{yFTvhNNij~tm6G-7F(+Msgcc0goD2e#;=CCw#AL-d;JcxAO6+Hbh~HmE0*l31 z$$Pz2sf7kCXsP~CgxOXHV|JTWRR&rb>SSgWUh<{ z`1&a0hlWG0w+770l^ZRLFeQxi3Mq&8k7MmL7^QE!>?CF2>q8w15moCwN(9>xC#TX- zglt_>7}4?4e^Z)eu_X1KTE5)hjjP`u&y&V<>mh?HN1sH}SFqzq_Y2L^O5jMuJCjKX ztdz*lNcuV2EQ=Pf{?4zRFRpJK!W!c_A=<$FnqtxM4_JS|KIEG>}X#WA&pda`Ea@DH}K!%Ch z+g=un@KZ(~H4s$!W0xYDj_kO#4S^WO68o3+1xls-Y|u+$Xj`eUQB*F{$QVjIM8ep+;%}?#o<9wTX+&I1Z!qV*gW(e6vI@GTa#qw*M+~G8 z#7WibpVHqx!*vUBfpb%keoWdynb$bAhNIN@%g=E9Mx88RMA~%0M_e4IW+H;N=Nl|p3(rhaMvZoYsJ*kQG5m3Lm2I%T7fCMZr8lgvhPVEZ- z8@D(4ms(fr^8MDCSvQeP-f0Uv_GSW<0QVYFZyGNe*WIl}U5~*;OZ&s;!1Z8eT8d#} z_-#FQ%}MA@7Mtfz~Geq&!Pb}&aqWXWCzHAc$&5C6#Xv`Mp3AN!kv zP+2Da1pzaxygIip8DSM>@Rq+DzAkrSqV_9#t!>KRL?&Vzn{&A+2U`^v`f3G{`EdeBkADa(Edm!5tS$IjMS(cS5XDN1lAeB2yAw8Z02@-Gkm?ek;a|pgdzhL?|MtT zf2NbPqzRHsjXM~-B6IQ+fEh)TJU^18{LjFeu8gnlg(e_=Qm#noUVNU00$n|%$_=Nn zQbO+Jg6R)^{c%g~<1PjWVx4>s zkV6iQyqCz=(W_&q-j_XKDgT(i!EZwdu>q#H5_$XtC?t|91wIs1)OYT=U~rVbsr4g4YTrN+zHP2bNY~ zfZa|E!O21wn(1gdAD1pZ!GKwkW1)X`z38`Cg4HxDrJ2qRTsYZU6IerW)ajaW z?G9|F;Sm`bei$b5Q#CZ}FjM}0cbvuIKEOZm;)4)>!6O$x$!vu3(-K`potjdUur-Cbg~a~ z7(bCjlQ=dG8sit?KmGFY>i~2v@#z~E)K-3=&qz;3)$iu$sC+AbUDLLX-t_r)i}dkw z%Ct{jtM}`a9D?Gu?$Q{rSDz_Up=v^kp)7L$2~I*J)eKvYndV3gt6znnQ59&waZkM* zCCT|#+yxh^?xwsz0UV?UvB>u9K-(zDMkvvLw|?E4Za|QC7IR6 z*wkaqb{=#O9s2f`rOJZ^Ubh~l2$u`B&g6&tpfoLX)n2ce#6p1- z(t=&Yqur04i-X|VZoA$T8?&#ejo0`k+!2l{PA3uFl7Fmc>9L=8&G%)U?2%y+9tcd} zqS`M1tb8{EF(V$=Oi z#{pI=5)A9Hd1c*iH{({X-$W8`Iv4}v=Y9kaVl$f{=(D}G|`g=F!?`sa-tm>=)46GJY zC*ItXW?)fFi;R;A#lbQf$~BC+%bcT&yFQU0Mj2u#YNl4y-r$Q70_R9B7vHZBcGNX_ zD8LiD*is}d5`a=5fIm}vd8D`1PAWwDY`Y&G4tum}gt>OS)UFYbmkSLFNR}jT?~kAb z=&m0cHCksJ zw)&2#ek9YHu8u|(=y&`6xKadHg72_J6ohz@BJv3a3XK(56zD$iRgJ-h5A-g^O@ z@M)I#|CLGJ(gTlqW)?Hek6$vUO1yPdhb%>l!J9qw_f46xJzPoV7TL)3ucz$9MG&lv zIIonIGvl_w`mnJ9X0Uz)F+Phg<&T5!H3K^N^E!=C4oV_V_buHse>~XmL2X}41Ui*E zbTORImoZZ6mPX`jc*q+8j=(W2>~j0?~{= zj_Ft|0?K82EbM-QCBqia*5MfIUmgF2y5Q`A`4Q5veJ<^_IYXg{>uL#zm` z=&tHYy-r*r@2ddVT)jFYh)u6c0qe~U9FXrT!=~S-kN3?e)kz427zK^@a-iqcmWXT? zx6*sgcQL;?Ty!sfs8z7$_#7cWy%^2E-p`Kv^QDe&l-)O@>kEz( zrue(3%eK=sv+vvgqvyF0;cae`-Y z{pNYAzF)9avorV1>C=7nH65(XV1+A*m=A`mN^a_xbg`N>^Si6}xPwOC)yd1ds4*#HM;Tey|%k(Td_WMoiy~a=v8d z``AE7fvV5*2&fTVg14pd2zH+p-NAov!`;~bHWdl`-~&x}m3|YFk&zKTw@rep zA%8wv2dnc3^01gpP%~NsTt|l@VWKc4Q@Eol#xF)B6$|M6 z8`OJ2)CrFR$U8Rt321QK9ZKx(X|}{6|50Q_|HA!#AjX`FJgyf;*>>PlmYP`_pvMec zmcTY)D;;9v#MCpa0^2)<;$ud?E{E-9#bP2xSqN9G?MJF<*#z-yo!z0qN52xi%*om; z|Hi9Y-0yB+Wreb2)C?)|fW{i)7|&qA<~(2u0%|3x7&+m8a#{xGP#~GAaX5cI`Vlg_ z{@}Jn97A@`#d{_U%k+m#ec8Ge8yI(j1{`1Iy-}Og_>>i$A3x*tBM;E^=0ODImYfbWRa)4y20E!BdAGdek! z0d|Fn0Jj$5cwF93S1_+MkHGaz35qaTM9&e@QXi5fR+%O(7^x(&r=7AA>R{Bd2Lx{h{1`5~u7 zkTJUt-UG4%u1%iPbz^`>a8j_k?ylC%IPRJ`GB=mg)UwU(3|0N@rIrFv#J%0DQgMmd z!*u%+Fxb3fUXf5KuTUCTr#0D#sB@@oC zRH3bs#&|OBio-B=(iorETRPdRTT(-1kmwU$jnNPk_RQ&(?=r_=D6BTxR5m#So z*i>Hyh13f!y*|d54nElyz*t^(~jrs(f(O^s6FBbCfAyfx?e6p|531zEk_*Iy^w{qY~B!< zYy8Ia+d&(Ob*aI4DK|cvf8nRdJVkK$siXlS%j3TZw!C$6u}t5BD`3S=0I+UVe`&L% z4%f*xC(z)2N>hDs`HRtvvQ+cM0JU3^B2^n+ToDaP0&Tw!Zka_Q_^R3<8`|$G z@=RJE+GIKq##o#J;0C9#Df`F!`jE&pJ^NKUid4O=FqEH$%vH6lHOH;O;UCY~4?c1G zFryBpfJ`fFSq<1d~lI0lxA;E1t;L_#YW79(+@I6 zk-cZlQLV8s-TPiKX8+HgW~tZ;NN5UU{}%bdHx%h5GmwV$U?@2Awm2^LH8+-6kBeN= zDxU7EUNmSftxU=Cg~x%tH2x7*_U#dA(Hp>Tj*NZA=I|Lc$Pl?@vd`hc_``%Feb4)c zya|KWe;dTYbclD2U#tISKX2K;<)Oh2StosPTWY~yZ4zuan2Hs#qci;rBH?gZG|Ap( zN7MU)GOE@J8&{PTb@F~1Tf!wKoi=$95fdY`R2b^wJmuxL+96=2L>Q)J?i0lx>;3Yq zDP{RXSkKhj998-5nZJ%wd7_`P`o9L?Kbb=qUz%NADp3OtkH5!bKgrXb?G8nMl~Ruz z_5{wek+lo8@|G)=dSNDpCG-spr!$@`B26C^yq z0e@3{9HH^{v6WGRofGevYWMP}D{=JDD%Pn**6aq)yLoWZ3xUog+M2mtF<=n#7$`{8 zdz6#BP^;I39|*is-(Daplkav=!d)=?ZOvFj7}2o7ZEw8f6r##!2b+j#gM+YxmBQMi zX$;6gFSTa3YdkBho&u%QZ!o5FhbL7ifT2#OC?hGP)%PCYBYNHGt3rIJL}h1h7q7Ji z2A^rMnsO8{f%X*1`a^HnSk*&<;wSlc?jBa^h~XJ5n?jgOCjx zp}qT zvq(b=F|>z?#IjmgUFeWJ(`GV0b7 zaj?v5Fg%G+zV0yHu>rkAk>Agtzvc@3=K#?J+?vf9;3;lQWxu3g*hp0v#VHYf?Y@IeZ;SHZbH~vh&8bBk$C)T=w&X(;T6QltY(g?5K2!VU z?R#@Ei-XYO(7T(OHP>v;w)jf}2JLRijHz0!%7OuhB}Jujh<~5S5C^CnRmc38se}CA z8$E2L<@gF?Aw6BuUMd7%?D6Ufw3j*3J3LIN#fYN@1`d}d`#paMUd@Ai2|f;DV8yYk zr9ym_-h%1r5|_G~AXK*~{~mU%5<&I>zGq!iJubxwd1~=kTe?5cX$l2WHkOjA$|%dA zH*9kzT6MD=1m~1lkez7e4HT6*QfQ>PkN#YMIf^q*IVT2tA7ViV^_T2=e5$cO!Enlh z>QQ+`A!}xeUyzNb`gi?m(e`s!wMF0dR_(Me9sRjk4aEmge?)^q0vReZ4#D7p!#Q7K z4k_?}wscrlgBFKk8uJYE6T>im>DUr4+VMXi{mwfjabXJf3PFBA->F2{HL;EW_nFMzPao5@U#}IHy==#kX`_heq3m{&jnp zc%#2zX7b3Z^4+*kvBHwxw;Wab00SQw1jyuA1Av=1ILrU_k8=uB0?bF=Y5a#1c1`-U zk(KuJgb01q^?c(NUdny5AuH-$5k(_E!813U@mwhIT%=dy(KG(_fZWT~E1%7LIn>jw zFS-C%YlWKH_VEU`Q_mbeW&s#zTQPNhcZ@*g%d7pL!Z*bwjtv4q{)KI{P>YJjy;kus zNF}cwXhoAq%8-UjRk*wEkHy_gyXx?FP$k=RX0;4p5M#wEG$BK&|J1dV3ol$H27@qI z%x3kwIAV7`aa4IkEY`KWN+k9;JJu^x%A98>2nEVKAcPe8p<TAALm^lc_6d3ABVc@F7av&qgfs$-j}>E$45R!% zgd|n+@a_yO9!r&e1{nvw-bSX4CDSo+Sgo!-7GLSBE~?}F%jO?3S*p?_TU#5k8$2CX zRYMvDn8TH){ih=Vp+JHjJUcEmOgLLoMHTlrxR=(mn93{DWgSK)pkHQLT_md5En?(? zm?T&skLlZ#M;AK7ey%qk`h^&!YT#kntOedB?&6j8^fB+na`UsV=>X`rx>1o0x25(o zM}YSFX!jqTH*@cXOm0B^$Se!vr4UCkpcJ|&fktr^qWdc(IAmkk!F<->6)_)>ogm0m z(nQ>d)7zw>h~aNe(K$mpyjva4?)i7yX0?sZ(a12Q90GAMgUB#%e3De6o;rpWT9-?zO{@ia1~Tx%zS% zvy~-Y%l=1h6?C|DK>r8t?G^PiO*yTeWYl>ixQPS0wD?rp$j4&_jH%a?No5qUwV)y< zCgxYEssCsqk*f@aj5;+{u;3iN2bB*KgK6T*tG)i%kb1Hv495)kTJW`c|8!r$`A zw7Tu?6|QIeP$EMlVg{t~S6mCLoU1*7fgW9NZ_iFZ@Ler67($?bxNO6HKX5%?dDtNS zvx+yvlbq1x-o@K~a%O!1FZ6*i`A)V#!xq7dUysOu$Qa8CV$M#z!LnWC1)o(SpJQ{c z2hq&hH_qo2D&c#t(P-44uB?Hn7y=dfh(xqrmt@l}!Gx5sq;OI_LX$P@O?k8q0a z19jK@HS+pSB98EKZ=lw?(5@DI{0f0L6m@}LWx5$&PEI!;wHBiad{uE4>O(t1Cr+Co z1Fv5P=6gn?tNywj;U{wp&XSEIS4_QU_giC($>K-Q+9s@~J?lEsa&Wy8#>Sp&H1`LF z0n0uh5PidMP5OL9gZmlB1vu0tfk)#CPTAnhdH;^99F514%K&>Q-Oo2u z1?zS&ZmHniR>zDCV%2mSezXxRc9X5}%o$oK&PC8#6=6^oy)-ZPu_Qq|TMMQ-swLFv zmU8V7GST%$4_R%a7-iwAD$cJB@-~OD_tT_St~HDpeBi&eiiG|_P3)_K25sy zjWq`7&j~000(DjC{hY%0{-$Q}yT1??de1bZezyPd&LRfX17Mc1Zb}I|z$`daIq!|5 zj-5ub%e1Usy{6bxDZ6@^u*VU1g>dQkVa~Kko!MiO5XMO&%zy{mvJj6>H5UScoSseJ z`xIMoapF!}p!ZuFh{nhWh@;G9S$9@`o?aox@{&Ttk4$6+Yi04!W8Sxg~`d%ATlO)E4 z<&@_C(GK|uuRQ~m#rYOvNtGAemk@bC3mHUt-`{_(5^MiEKgViOo3P{+v(su{u@M55Yl*?Xp0`48mP(b^UXdadzCX2i-(O+nZL(nPv$_EmJXi9bOL`!6Ik!1 z*jz4FBxCVAbwASnM?UukI)%LVaX9rHt>q4r@C(-?r3tkNyKD zLnlH+||_TN!$##u7Xx&M~Ad9k(X$a&UfL9fDR?X2N6$5MI=( z^?5AK1qx{m5b{c&_IQbB!xm)fLZ>zYXD9d4*8ic=mLtFjm}-1bKc`f~w1YE8dovK_^Nz}(nQ*R);9oG`1so|T=*K+6K|3FPa`TmT(b1$!PJJ& zJo7NLf2kQ*W$)dcudC3&e;gu%LwoK<1;8w~xWewo82_0_fNsA^?FJzJP7fvJa>G1L zIw5N+Pcag;GP#hFUyJwR{S5g#UqCdhjqgL zdV_zr9C!-)(rIHzcOaj$3PD}Pp`rIom+RKvKgtXcJ&?iJ=FDM^6n<)*XLu4^mV~H- zV+-lA)gdY;ze^SP+q3W&Rp8xwXN+$-XttzVFhm36hy{XxpI{Wfrw$r5^uP8d7rq3X z(nY}M%2#7~(^IN=X^ug>51!qD^Nb0PZ6c@b08HV_ErrEtvA_nzW!y=W_FWzJAK$)- zz2W51zhY-GVpEKF9xv)SVZnq zAZBLGOtHRu5U*>%F1xQkq*0QT8p-!-qkP!jY5Dvy6ZBD*S{T?{X+(-g;dPUg)a~`M z5}T&JeoV#=4q-7qG`~7nU+6veHtdc(;KsvI0!UIl*g;?MeB)tc;VkJ-*PVK*?;zCc zw4rRg$-zgHZFmgd8HCC1@r705cq_~Dfm$no!2wK#DxrOA@K7hN1EQ>iOJR!?7*)l@ z&5`wpa(N>UUtWL7Gccwy#9WGH>~vEZwHC=a?Qtzymj~)_LJl#efCytg;@L)9BUUjn z#*sWM3;4GzGv5Gz+xY^Lgrn?_35X(EDWClc#>K^jiu?2io5835=*y6=MUpZeqsQ3h ztng*I^q1d0EXc(68AoepsvDEiMB(-C4?kiWw;Ii@Rs9!P{u1dSAYNf8oy{b16-CZ7C##;#tOUX zs?#R7AJc=v+(K~aRX~-art8 zKv&FpP!UNSskb;4@6t9(NIAV%O?XNR``y*lh8|8Q6bW~+mqW#15blE@MX(96SHB8nenHVt`b-A!H2F>TGx zl_>JYsqLpP0uVhq)h8-;ARy0!dP;hiXid@!DpeU^qC*@o!*(7L!;|eYcV6RzPlw)h zW?@Hz!u*IBulPwq*u>fn4&=*Zaqk@BrbPbFS9dRV98PU?03MEObwGTnhj~7VoO^Ev zni>Czqxp?@F6H09(J$@LVNOCZUdk1>NUPV2w1{JWh+5Q&Hm$Gj{_!ZY=A}tdd^WP7 zUhDbuK{1fzPL9q9x!-zZVL{__+_cXR=&lYoj#h$ z#Dy`{$4t82=2tQM5iMGsC0L{TGK(_k6U3RHFTYJX3b|H*ghe?<`6w|?WT92{tR61$ zB&0vCTI0-I2f($5M~U}iC@FuP31u-IW0^EJ;X-y@0R=QxEu$TuT;xfa`OUpexEWs)=VHI(hEs!goT;e4+p9S+!f4El~c0myhFHyz@EOMexN zIK2=Ui7~z>DD*iPj6Ke;>b)|#8;_pb*3aW`Pfk6t*|`x%IVa9Y;QhT8VaRktCNPEY zH1Z#|Nd2oqh%HkrO~G4tsR~;<0BoYB9VE;`Y?|_?NMTMQI%<9kiqcYqrT`EB&{rp_ z`Yxa)J|W6%R9);^RIklF6AEs(*a)-_8}NpjeKW@rhSVm!Xsp&pQ5ur2 zsAK`VYi=&Jk_9M8AunwJUp8;MK=JN$+x8Da*q9^pxKw4x<6Mxcbsa|b({czJV7UuZSxAH*OVCH-m>AyY)_5J zaUSUUAZX%Ye`W%o^Bt?yu{H9vVQVfo5lTgDDn`?s;-KN&Od#`f$J?)c8j3c#WGLrUR!89l zMe@%_>^C#Q4fLR>{uF%t5k?NYrE5$dZ)rT|X@fQ~?o42GlxYw5sUNw=c-^rxWnZd4rv16-LW=Bpw7`2%m$&qU`t^p*jlBXv!U z!NH4AOQoQoOprng;3Ue-(lIXJ?ioCK)K&tr48%y-8^w-{_S|@ z<>GiIsW27A=ttAXqudSX;3889GB2h)xjs5>TUsz^e)w2B>-DL^C1oxg48jRh>B)=r z4y@wYqDG^{(2MMhkWD{~0TJJBbDmk##MjHLL51vG}CSSqCo&GXvwr?K9{4 zr5k`uPm`3OqXkmGKx+Vmq+s-icK7pzP}5X0yVsQoPgAJi`=D2dWo=ZQ@U1B>$a1_u z{ujRedE)g3C4Y!dAGu5-%pM8>Cu}f0D(As$F}zWznhZgIE|;~kWjrYyOJ;HxZnvHf z>iP_9)NCW(oP|*`4h;XKxT+L%`{HDn=xt-8U|?k~USt@oREX@Bx0pnS{M_N+ze&4V z)zxSB{gE)bzkTSoPTxfvO24H+A3Pj$@hAJ>nUThfsxx$@EvuT4aJ_J&b; zB>2jdyb0Bt$ccNXK2Y&KQc3DWvy~8%co?J27o$G>d z7rQU@Ss5NSx-~lAZznaFyv}?0@l<*f{l^;{@dtZn%?W_JSJ7k7vJXJS-ju*24ehP? z&+hXz|F*0`wTom>HEWGtBhTJMg4jg~p=jEBYv;>cjbVSf+RW3sqOS;0%#@;ocYX0A zm+k;hp1KjAxD8wP$(s2O{GKPm4dUv{-_9` zC`6%nn>|}y-q=g0GpHQt5l#y?W5Ug!&dgvCC}n-o=@y zd>^O!w(t;mYx|FF-*?IwuoJ`#d^qvflpgzfb-AgxIhD zds;bGM;p&%<6eNiepYg>caWTFzDO%Se=#yzY^FCmkWfafy*+5#*7Ggp5RVt1V|0ZD z)qGIn<5f?hwaHl7%uWY-UT=Qiru?5I1-AVAkRb|?b}DjMX796Gfu^ZxOR#f#6 z-PbJ~zsF%oD9qp_jqoEWLQac_4ALL5P-q)cDI!6_3>|?x_&xah;{YyImsr>f1CTMW zPQOB@<0GNplQl_*L$Uh2S=bc+2oQTC5uxLqrAcc4uE!+e9}nuXBsp?9(=)F)_UyRYJCwlD;{|sH#+=sn;4K^Z zvedNRP=??)UD2noSUq!sOT=~HB5bt)q2OL4M7 zh#H9%Rq&pTmdLy9BY(a?4hKecSIl+#4EYXc z>Y|+HRAFk;m|+P&|DB!Cqt$EhL)EJ%qpRo4(BkriZ2W~oxgYsJO&tz>^@mA|tLsnP z%on=!u2bYX1yMT&hhD}Ss+tKdx`wQG=o}{;F>zw=Ss=B#b0)vnCH|Y!)xK|@@{-^f zuIb(@@PIT6?LdFLNY>lq^NX3Pip&R>LM+@h8t`iI@*xT=&sJbE+QAhP01laH)yVqOJJZr5t?4#?)+A+S~=IH zBQ}?0w$m$qyxjs)5=3RTl-BL`f)ah$@t=IT!afsCxNyi3f3BZpWE<`8ck1r%xD5PG z(^D1l2aIQ?0tI?Af)H)_ACdSyjDc2~*|ZS(diFJbHI>s(54@}z@4ganU5}KKP}UwA z<~)P&4vNi|j>z^l@3%&(Rv~JtU(0Sc!VM{wKvrdDeH*J@&*$H+ZuyWCa}AKXz~xIJ znlg1Z8?8?uKger*U3+|ljQsih4lLNCk?ZQfSlUAVsWkp-h)+}MiJxkg>G}-4Eu*{i z5rucaXT@-_u4Y*hzO>jB3!5NH_OcbnAi#%+;Z$_EQ($JVG>fhOyZGiDB`a2eIJ{FV zWwaP6M%WNp^e4vX)-eUCh>s*|w7Z?)*X!8<9q&lFB!p7(x!L}yOl4>1pEky94S4kde@T%8{+^(1-p{mZ-&Uk$ zuE;qjc_jB-E+g&6gFIyDW}h{q9!S`GO}hR*GVUNkX??&w&W#+u0%I} zCowI&;M%boFl+_-65_o)2q{dA*E>CLdtIzrUoYPUv*X9okFNw<=lKwBvl&N6B}q)a z?@D$G*--?===(jsX)5`?yJa|_6`!pK$;XRDZMRTsN+}0y{ zX^$YBTUw2@oG$*IqkiNFnJ;mm<67kWE@hQbHwL=cKYBC^JhfH(!S29-kz)B7(~~GM z2C>@w+ilZ+fR`G;Zm|Ejn{+IC<-b~IMp3HdkIm3#DY2v7-j$?ZEdH_o#184|Pu@`f z=^-CHAq-{7nJb;z!&N^s^U4-Gyl#cV1D&gG7mOq~KH;3(f+6@@hn5-2un|9gUiaOH zB^E#2b?xr(o^<7rD5uh$@R_{6jh`wzrv&i;s9DA2Bz?y0PiK0a%cH@{)5QA2!LKo| zy`KggXOmkIh)(?e2h{JVDpKz=V(*A-J0kZR!2z#NA2IRC$k;+m)Ya#EXbF#wLeeM3 z$LmZ7;Ek)KC=VP5z@)yZ!oggH4L;9Nl`l_eFXFta;4wcixWUJa+l|tXSUb zXll9OTbkMPod|n|lJ48N;r^P3*Ugx`*WpijFWYeol=Ee5gqG?7r&G_pb|JF9; zgkLAQyv`5gopFjy<{GqA?QkBz9ih$$ExpBKY&$1qdBgkGc~Y$n#M>;71ZXWSZ7e!C z7pqU8(jEu_d~6%({gxMjuyO}Zh=)`#W(e8)mzeOLDJYa!MU*EnPPFHW0?ly1&mSHY zpL=AojMW{nw-RO%!c`B?3=+5rC4ys)X9Y=vpFR4dk8VeBL8!xj%%`d6_z|gQ{su$& z3l@5)bKeNF#}=#g@5aNCqOW1#;)W`+b!l?b9K2C)IL+E=k|3+dfl6-jM#FyXYV#Y~3k#?elG+Zu%UI z5fVtkxmR)EUCe6JcVEb@|3GL+8uQ9XVimql1fu6(wxRzaW|;DGh}H{4_`xs{a^Djt|6%7vY6!+47=Exx@1Nx0U5}UVUG0dx}3FB)v*RiVc5XHKwwABS0IoAQ>3~T?;?nm#3*`Wz?#cN6 zrYT8aT{&?^Qla-1@FvF>tr~C)RxAig{AYW4TuRDsOH}QQPY-roIqv+PwiI1jee>mP z#(#X%{X<;-DF-|p1qUw@oUJeLnwk9BgA?o_2VB*A|GeNcN*_bb*!L08*5@W@0s&9q zdhTVAfyOb58hrsbeL>;9j;%evxOAW7rO&T`*MeDYl-kd~7T_r=zhL3CS1xSm20xtN zZd~hI#U>3S^L-={@~pxqe11Bu1yMyb++R*|&EO0#Uc*uh_@k^Q=r&r#vdK^;$|>ho zRWYZer$47b2QQq+%;1SfAX*i`?5FBqA8nbf$eY-XL~N-}*WDy`kuM7W;F#TKKm;znt&CeA`W1cO3ZgZ8P@Q8($PG z#nB8>t06U>ydhz%PdgqB5|V33A{HJN5{seS#Pkn_QN(Ixgq-nMfC0e%Hy8s83tEK5 z+Tu{6Y$2I_G3u~p6%G!O7cTJ=&79o5BpGpNmSY>{^v0`~-Eevev4EQYmqMSkD}ON> zuYcg|Y+{bu#ZZtD{Tg&03R~^uNcu0o;8E^GG9yC-OZY$@E2CRul(~O2%KOz9#qgOK{ZU^(+g|7z0h2 zes+ZLFUI|7;$-QK}@NzXcR#;NBOZ`=PqC&P31WfcU#D;q1bl zSgD)qO;V&E{|rT^4^*Q|x|gTT-{N+^o~Rgp6RM3Lx$9XnF;IZ2>AI%9xxkN)-wq@S ztx*Ne&v370ns-z&gOZXGvrM{+GGpw&0I32d)YIMf{KE_lDqN5BXG9mB97p%)Wz(sy zYRpnGfxV`GgE!2TZA%Df7n(!~c?>(w>)yDpOKskd{i=|7cMopHR4fi~-`2DGlp+}A z&tGTCp4J%7xj>;N0T>IHUPu}o5vucA73z(04&@ocQUl$RLH?D$<1zNaGQ5!ISuc=a zRD=U$Cd2zbng=Tx^_GX4srjrhV-`Jg~~c+yAZ&GN9R`q960PT6m}W}IitXNugSoyQeVKDndSAR5$ru^zs)>U(_{ zC`!g~bf@Z;6f+d^#5st@eRwJQ`S5yKZ6Q`+iik9HJW<|(~HWt9g@ajO3yUfU!@|}_mqP$ z_!63Lo)>t+)9STC?(&*8?>G7O54mc)-C!kHp^3CCj|*!#z6+5sm1x2$FsdYr>uPcV zI|0LSu6NfZqz9=%2`wQ!R`wPBHAfnu4IrL3NFnf}!dr z7`E8R;XP~FN3(Y#ybz$4uHI&Ahb*9rqj!r?nTg2Do?l0XJ2nET@ATe?c8P@4^ptw8 zQftMB92wi%FB&7c59AB`-loC}ah!~JmR%mZ{|QHdK0aq?A_;Ckt&vnyOMK`84v{FJ zVC->eiBuh939Yv0O;GghVQ(N;+);FVc@A)#ng2@|ziRHx&B|=Hnd=9gN+b1`M$&T> zliTCer3aOzS5%Pi`^6XtU-fGIXb4=98J(KK#XZmdcGWoG+;cd=+Hq#gvR282C=hx! zGh31Bb@HrPwWcO#7B$WU8e-~NjmzRSt0lK<9z=v;CBhF@RF1}R(_MbS64_$#;}$L} zV2j#O@X3wnZms*E-GL2O<4|p{t`ib=&#|y$(khIkO)u&crKTp9>4N7K#N)elUv;3T z-+xh$%um38CAcvoQ8^iUh5l?2jGJiEvM>}9E-O)8?9QL9n%GrY;;WjFr!uiY#*S~= zN9$7i_~bo{f5A1Xp{}W0#h!?PiptR^K4O(MyXP5M+AP?WW+ z_+s@jYUC?=1-Rx1VTM)QG+x?{%F8_S;5du;dUUc8?_8+=0 zMFTMc143s;a-3|*e(6c}NL@=EP-og*{S|=$(GLBNmibFN4&b=PPBj(ry`!e0GUPtp zg|4X-4&8eo+1bvEogTN0#R>V0o!F5VLcZS$cnBh`qDPqGshbeC-Ma5(iqtgimqUPm z8|j+YVidzz$z-R(uKV$j$W*5uV}FTf!{XKE>n??b`&YQFR{?CdWve2^Ld=g~!F`=p zlO}-zIwD$5-B>Qcu-%xuz6i9X{Eb80U>vQi# zOTK$U-;lOSt2?KAy2Ns1r*$4zHZ=c`U5c^#C-g$lg!eX=eRHW!y?rmcx7-%${RMk{ zl`os$_MXydM#3-cif}`80&{){5Gj7ZQvCcFj`yvl{|9=F-47vz5N|4#Ucj;v2b8Vx zA{NDkGnOu^`C+qFKMcR~CkwR6hT9EO2E{%&_utOi{V{73bhc_YgOCc}g3%E`Hh0gngiyNv=@Z_k(JKwy~C_!@*|=-m%luK!SJ3-84#C$8Iq z0|G^tei0^koqLsPn>7zkhN-ni2hSErqVqi`1=ZOCHB!kKA@;p)ZUdG2m2!XKr?cAR ztv*A@YlzKMa(jAQ@*jYRVZHCYBL#OG`oHUP;Gvq66#J4I8^np=NK6>ao(zxIfs6)< zAFc>d`xT-+5Nv*pnEe|@WU=gDgf{PHe85t?_0n^TkdObtYy4{Z()AiPNwR<^(OQf8 z{L85~+2uXeY5dt~>@UBnqFNPK=2&eEt9j&ww^W#$hS>_OA3QTdL&V*bPZH?r{$dKG z;Y1?XH!J_-42&lO2P7NZti3NlBU3V`#BdWLbZ`iZ?4J%wd%61Q?cHTJ*?o3R-DQ)1 z3%30Te!$#QUOKutMa-#unlfqIHc?Q9;=Po|-s=CmtJbO($E=(3=&ohyPU7i9n?m`z zWk+7G+eQ6{$ncZP4-epD<)eAI0K3zcFh#)6ap)g;RCsV3d1p<_Mvx(RQU=6RQpf2* zf0Q;iVURmYV8hY(uBaE%tINKr2!NMQb@>9B0bRoSLIG!!e#Ds`FM$KzVjB5==dK03 zP*nxFh3JJ+GBP-J?VhKRm-+1-k}l921M zFySrl!2`(Q@Al@e6oUec8@g|wm|~85C7*NgNsV5ATIY&;dN#}Hd=Dsn58>iyl7Xw9 zkHF9&l4|$;bo8-keZ--i0aetABG9@|mxTXy!)Wu=p-Pl6b$(r~E$m_FmxgI5vi1s& zCB05@kyH~puSx}brT5C7VCz&FEZ?^_$h1)C2OG2(AXS>d?^0+(>+`!M0(=zpTy;gx z6)$p^!Nyw$+UxtVzpqVqZ@ zB_~Vk+c7zykQsdDJI})nHxa=9NM**52A9w0b?syDTBypL&FuF}h2rr=!Og!W4rconh z!d55)mNHFeDj^p}^P3=?V8QIE!3miP8fHeMNqQ<&L+WSI<3$PJgTtjpX~VW9zh2oq z-V{a5x^#>@=unuWRgZ6$K^Ii{JPd{Wg|=tIb1NYzbGjOgR)bX4P4f|b^7B9`w0^<8 zX9OxYX<7JuQD2yCR6D9oQ+^u%C_W57v<9_h3TubUWB#8pt=Bn z!x4S;^oW2|p@6|@m~ze4>u=wNZf0b~sNicD-nl?p;UP{Rr54WS>_DB$=Es}3uBwM{ zK`MTY#QF^w!bv0D)%0*881h1JDR5}F4||P^TH}Fx1-Y0!MXpkX9M&|4DY=Qk3M*Fl zI-iZQw{@(S4*MgO@p}C9Lb569JorTltBe}Yw~5nqY_t068HVM0L(hG{o>YkhH@uGo zsAkgon86+qL$AZaLsTr3qrTspsTs{yXj*SI$Ewur&5MMrC`Z-i*Mt6EQ9@RkYr-{U zSNT{I{jA7Htb4zJ7DIF^pzu!~Me#35u0c|k5@3~IegK9U>4)8DAlOS`f7LiQMj5;J zatfF^^-ihc6#kOt>W9Fw5?Q~~03bx)s5Va5#d2gIB1*<@*pwIkkweaN@2W;Ti0IqS(Q)hegw{wTeW7bMUxe-xKQWzIr!%1s z?aB4}>$MH2Iwbr^8sc<>Y}Qe<8yEM!{0+&aj}g`ur?4c>sYz=7_)2Wu-eiJY(i|z~ z{jvI^#|1^_bry=>wRPk&_E9u4&Kbqo95155rNa**QDx~%w1UdX0Yv zv1vs@ZqcIy?ueZiz}VjT4`1-$ir0Uj(wOR65GwMG(J}^prLxBM?tt;K_o6l;AP81@ zn;xoegnWo?`5y2d=+bKUiJ$f53?t2v>0Wyv>AQtfLAXe`zH#MB$LJvaftnC zE16bdWBD>2^r)=fc4Gbd(z}gd8G6z!Nw`X+1t*8t#uQ$Z%+&B3>-D|Ld-R;qs*8q+Sg!S-bx$?gTchy(lhy&lDviyboCNFZ|D>3rhio49bLGWK$M>P0 z@+En-oz`>=66a+1)alffex|An#tFHd0lw^FaGA8TQ^>1g)WE$m)^>hJ{4k#Q^yc5{ zl&-5$Q3Avt9wO*pYO_c&X>q50*iZ@JSl#6pO_!Qn=*ykWpDu?UT-+G2_T0VP+?jo5 zkCJkR@O!w)Sj(UrYZO1_d5NT5vJ6o{Tsc~!Ki0-Z;kKWG$r|by7UF7atbvvUZQIQuK5wb+-v3JrAeE{fo*!QA@(g3n(*33xORH#yKRo>_?u?)0wq zhw2|Z0tuGRn@!x`RR{6MFkBcoj-vR9>FZ3vXzismq3*RXI`xcjrjUYeIEtPb;gUpq zRz_QU=he|>wa@1dy}ugX$(u$aZ_MbtDkInWeJwyfB0UJ_WZSW0b`w`XYtOZ~LQ3j< z+apOCpNo!G+~YSL5@FJ6ZET`Z$+=YT=DtLNa4>wNhkX)vjnhcweZl; zO?di)88w!?0SAptOv&ZaT=xCPsv0drvpQld@U#@blofKOc->u zj|Isc&i*~@_Da>g1!kbd>>k05hx6&Ql_b45QVn!7-7<^~E^>UnSi^3kemrW z&PofNd0hs&9Am)!&T1e#p<$h*P#ZtO+f987|Et;B91lgy;;bKpnH(0n(+zh*Po?%a zKk5wjAYN3DKavdrUw9L&q+Sk=q{}X7wR8NAg{qc3(LZx^kD4>}5+XUQVH(Vow3=K? zh))k>Dmc&lrblVp`4Ic{4rBbkBTRU!zhdRugAvhff`nazLOsjNDGcjX5FY%Sw2!}| ziS!wXcqPF~#yR`rktbJy4iQz&->l#R7|`>=ORX1f_l)-&TId@jR$JD;P9xh#MvJkU0o@T#AN{b6^eYF4mIh* zXhiAVnDApElAmmc+5Z%wXRIFQPEWz~ST@8$70BVLK?-_OG0M_Xax);xT?iV_|i*f9cxks_OnHVf*z$tYJk<>lp7tMW-A zX$WxNc^-K0=(&1NmOAwirg4+a+Jp+oo#_@NJhKij9LXWT(;QU*jr@B#a)7t zTxds|C=IPsV(gFzbd1(w{?<}N1u2n0f@(a8!(G`{tW7l|F;syb@dgxNVAS9+OdA!AKT_#JD%?DN4DKHsgNufS#PeHIRL>H=@2AA(&6~wU>Rb?C zc;SUrD%2l$m*e;S+&`jrJcRyZatju&+Yx317j8T<8n2@76%G%|sbJ)0q8u-FQ7TYw z_lT*YIHDGkb_6>AWqm@>F{ppb(w=B_cX8 z#J5IL-z;9dSWG_7`OBr3UV3f~F~FyMk<$&w{v{;Y_I z2n-uGtjZP|O#zikB_^t`e0^TQ=lu*14@cX!ZH0JbFDVy}EDZU#;6UavE41F04Cb!#)yc-D1>uer2&}UfGHS*^V45(n?(TVRr?F zcQoOOo*`%*uEmOjTy|)4vQ#n;)F{4|25Xx*$Jo8f_3MrlIGpsnX zZ4mmADE(|x3DW4ie;%?U)9l6tor4jo_u!X37ICiX-NO^0)F3QnIW!g7RDP>O#lXOU z12JI00N)0ZrU2)+Jl`z8!}S4so;lx$j*b?s5jd}rL57AN*cRfsdbzKCP3@vOZ8AD; zNe-s{k`INBm6$KG9uT_E`?XFHx7y;t{5ukHS-0A6>UKPG(u(oO`JajVz`{pzEc{#9 z$q4FvS`sGDzS0*fsc&SH(PQD-!RF9p)X!5-J$1~yOMDNwk74iLy^)ZRAf6A4#UiFC zS-pC-7>=8_=ikta2YT3%;f}=Djx6Z6>GTvR@JeEFi1s~1l!4n-+yckyA_ zgeS)LE{Tyw#qoJmZEkDIBZAU7 zx!Z*}r_+svcelpY%zR9mQ|_Hcg44iWPEJ2x55wu;TcxLUd>k7EzD}N2%73x|Ag)3- z8EqCchl%O=c@E&~A(4pkt#M6EFLHIFp5KJ}Jw4q7cm1WnO@;l}udN|t$u7mhuywdi9}N03?4jKv_$dOWwOy05)vXtB4tr%GMR)Ykwq~J#M4eYP1N-N zyYACF*i4+V1ox@G#~ZEI;^Q7W@JaWr_@Mh1Jl1wE21n-8R=Jd?!(*Fwr`H;AQ{z9E6ynI0Nm&%l>V+WsQFu7MdX{%>Snp zx6IGOJ4OWW~Wv?L%G?JC3i zbh_{`+TWiB-iALbr9-54iYm; zB}%iCQC3_a`p`vaoH#wK7!S5e!Nj)v(b8B!1%f-Aquz>RaV@Xh8@ zuNU%EjcO8*pZ;BfCA;l(JYQTRfq!SNFyJPoGBmE>|Lu-_R~s&zQm!~+i|U|Fg|G88 z_#j(@4|6n_c~qD|@n7d_kY-njMgZQuC6b0goi-VLd1V16F3J`|Z&$7b5=n4S@Hda) zCMR4Ad7Ls$G0>#~*WuwNG`x>b@QUrY#`h@UC(=df{_VcjIH#S-7h8Ambxpou9=88^NK7>$IWhd3 znak|B`jJ7n{DHx~SV?^$n~WYyb89ln@Nd6+_iiy!Jzsy z%a$z@ecah|cr?7Vhr}bVOG-)-9!8JHEow_ON;eAa8oYg|4Yn4CQF-7#WvvxxDqk;C zxokZZ{KpN{@lFMil9OyQ%K5{}l`9W7DV8)I_*vurgWNBaJ^S@Rb(@Uxvl*uHU=9hr z7gE(YWOj+B=lt66-u}0TKY3ezD&zG^yxqZxvqBuO(zX)GsS=1Lqjf3>XKgOQ^|Ssx z?F64*O~UD-&Ot~FQB!&A!TRJ1tV^?b`z&~i0Js4-<|7(Q87fS`m>ygMWq z=Y5ifJQi(ySQ1^Ao)L}vyJ>LotSporEeHAB$6U7e1!luQsZ`;Hz9Hxtp~Bi^3%=b` zfrKbM#&$M(b)n6Lr8~-SsGMGRdb@kHG@?VW0^jbefRcpZZT-UV_rWr3&UOAv6pHja zfoMI_oue`;o}YXY|084c-}$Z{g<2=my?V@r2PcdH|LCDXgmhyru zs~cU{8!3Veyvj-UD?;9oKmGXzWl>>z6_)m}q6=jyHWrY+Rn>xRO3sWZRNg;8X8kE~ z*k(T-BF)^w!?!FsB?2J`>-4QlBEcuTYn7oF#-P>U^^4>2*y&-orhhp8c%Tg)=t(aZ zd+GRvq6bDiSp3~Zg)@n+9*IU0kScK#!h=aj`i><$gA|l>o-m3Ruzer`ucJ@@6UfW# z-4phMuX$;O8=qgBgtsn;L{~b;yBEdaj4(CM?iPya7sulAZo!yvZZr-)(E)e$3Ifb- zjOZ4EDZ@x8Pe|NXaAEHBwPZ+Z#EU99d6X(8 zY!(MTef~FJjHEGQAp?>J7vWG-{O=Bn3$DrL5iCzcDQ#yqdxwJFx@{W@3-iVI%52%X z1r=qbz9q?t;E_aG1hV+!GJw5*d-v`YzX>mM;4&jCD+@b!?i9b*f8~3;PDN!#PPqqD z4z$7UiXiAn1o9pZw~~qo1s-Ub45RwkDK8o_UZGnvqwC1NHQ2eH@jnAHG|tdi;QWCb zhOH=~KlH9}(s}-Cw)Nt5z`ApS5W?QW{$#J}z41!gOF<@JvE74BWeU;QjQ&?2elO{S zTq(w8wcT!m!_IT7D#*@M*X)=9f`3EKH&iN0M->TSr;A?Uj$lh~rC<>7(h=odzB4~ebOB)o3yVMIZR9gBCAU`Gy5 zK2Lg%w3ma#oz|ej`14{g`^H2Jh*HBwV$b4mBQjKj=SRk1(Tz#CuA@OT>C|ggcxGq> z9vT{n&o7IGXuv57J|!;gZo-TunHV``AKJd1ia(2-&`=WgrI+BWX$R1D@?Lzh+k$s5 zk4KC_fpUij1r|5{M`HHzfx)=q;}opRA(dI#JLvlpW>6xE2Lz~NzM8rk6{R-syn~Wz z!TFWmsKNSWd$Dh8mM>P)*m&`kS1{qR$MD1x6Y=_MuaPh-@U5wuJtQ#7Nnr4RB!0N? zO-@V4V-qIesi&UAxN+mKphXpy;j@ zA=7qBwFX652k_?9S269anJBTb9q+vfM;}vCx74XRNFrgEK%!9ex2ps{N?ceE?;V2s zE{{XcP$d>@E+Z>ki#yMZ!0A!650(2PyNBY2-Xz_WN<2C|24}a>(hKE%5b2r1hgT+H zLcbs!%y(e?IpJa?C#6b-6?e77pa>-v?KR`GTUz4oZbo`hlz47*9G)E-$}NBp;h#2< z7UplSz@t~R!1Qb5q35I9xTaV^qEHEqi8iDs{^_N8u+T9ECaF22kS6n;!m)Y+kBCd#zbmz zD(<-bCcN_MYuI-%8H*Pk5pubCdDyvQ2TDuG;P)~8op-VH$HgcuEfqDM47WTeDJer} zunEsS_bhI{^>%#x$*0IjKLi&U1-z}FqT*ulIcn5Jxa5DAiSI^}kp!lmc#7|G)tfsnV0k^y<}1%(ujb1OGle%>;|U zAAkH&%vbsAufO5|-7j8uGI_3&Otn(C(QDz+|IZG!!OsQJFsWQHk}x!rD9p9Kk-E z9Y+6eEH1nFV#J1M@WqE8V#k3T(u68#bvi0r)S}$c>vf0AGVU|Y`6tKeX&V*CQ+oLw z=f2oltxw2_mtHB3qvCvr;A8Rryq!*`7hO2G;)t(^{U8T+@5HukTe0AqSvZ(o1TCe* zo1ZBV9Av_`Uw?`4MFxeh=4@PjD${Gx(C*=raU8U|BPkPvgBA;CD9nuf2w zVo#ulY;O-l$ye7K(v|1NxQolX*OX0wQA zkM7BcK_PbN?;cMQ{iWZBYYA0ue4O3_3v%MIxg-?RQ(9xc+1rQkR1l(#g_zBQf$fd> z{^lfnba{+e_tqt``0=(D=$1gjkmnI(!6(+xepJq>)e20#Bo51OOTrJgBw}zo+K!CP z(_0$w;Ls2u{Nh42c;Uh*Bv4uV)UYUw>=;hME}ot*55^6T#Do!Hw0}!1yfG0y66l$u zeWqU$gXkc-PY&Sr!C^S7wGP+x3&)5yMobwQja!HK`%a(QwM!&T9(qNzB%HXpq?v3~ zg;FhY7tV|P(}oH;c1GsR+a11~HeB62OgP8jAvuF_=1dq#w zeY8g(*X<`I1nLXepOmQ|5;cT=VjsVb$?(rr75eRm;|B}g>{M7GO0t( zXl0m6%j8ntfvMX!vDgv1w9z zu)oZ!?6&9FNf0U!%FQ!re~ng+0f`1I{HqXO?x}!>o6*qAl2S@())gzcFnZZocVyJpcUj=-qDsKK}eubnDUuJ$m=S=!-_-s;jRSO%wfj7WdEC zx@`yUz3+b9fA2krjfq51NVwRhw8ZPZ<18yLKcDgqmk=j~MTH`s-#{)j|K7Y61$mj6 z{OofW_vm<3Sg4>?tFU(6Mkw5NJU4kVro8wP=6|=4w#z|zxfyzThbL1SPd`JBcI&5q z|Ne*bHU8wlqR<~sCeP#FRpEBxpeq)CIJ-~`_ZXz#n#z-Mml|KBx5RV%J7G;>wAlV6 z4tZR}Gei$Z=NKL4-O>W{uT8|dR0{^Zn+}WFjmy8x!0Kc(*520|vu{X3Vz@?>mjbI3 z&tDLOVeNFdcVP~8<~lIvngr;KO0)^r;@q|*4ylYZQrUKCPZLVY99T+a-R`^!yuG@Z zo=*=(b_&M5XGG$0Dr+57mQ5cMB_^onLEVB#F!H&Ev^Jtoq83YcmtlXg0}Hp7sPOYO&5d9FC7gd z*|(fcaGs|jVb!rqSA>U};IxBT^OEuW(-ZK*D^v05 zCo}Qn#0hxi^>;|HDi9Q;$9G?Sg!g8CMIuo}<&qj@g_(Gn%Bp|%rBk+O$2(JB#+z@w zgJ+(87Vm#B15Z8qINteWE-6*7@N+p`&_%St+5NiUuhlD1P-YWp(`htV^XpPLO$j)o zUvD@a-X{NAN^|Wwl{l}n5iMJra8>UhIDA5jy@>4a;=r{r znWc6dva4`M_Yj0dYBBzt2!v4qcQDsR2UlQ2Y6b58As5sCF2d?;J6!Z|(i7^P6OjMY zQ_MB6I=u>SUL1qY30lPP1i@s`l~_FDJRy2Dx<+cyuYEASzcB$j4wT~eOgn-!3NZu5 z{a>YFn_Yv|w2z@-8*nFITcSn`6MP!eh%@`F(Ta-5{#FO86&)6oF0jn z4jnomE-p@Z1*?y@Vv;?Hp`l^u*g8S{=E7p5ABuBKk!>nynZ1G(0+NkVSuu4?EY53h zz!`6+;H9OxC@i7$Q#qAK0_^1f6T`AgBCpf za~_JQc1EJB+| z5|KKk=xguWT^x%~SCnT*w%LWvnO3Cc_y$js<%mRbTsj0P5pPf;wM>Cqw%W0!z>164 z+pw7I#OpWP!L3MJk}7O(@;WMN1eGanry2XwveCA47es}a;Bh&zZNnc}{?pI+<(FTu zbos9^+uUM6JhokP@(Xd-qm%H%f-liAMvIy6zJs)KE#Cj|bA0jT*Ldf}@%U@S516}T z1=-wsA>P;w4y@2xOV`DiZA#`jBp!;teYL{emsuuYXzW?G9g@xqI*qsLie@!1!% zF#p?cF|MrV2$dnFh|O{Rc;XBQbrndMGgJv;_$A8d{P zwO8YTud`9eBYti!!`- zV_X{~(c6|;=0--56_5W?h$k;jz}`n&l2WkT!O3S19${uGRx@)j2rwEUa@>;0kQs8uQ$j3z{eha)km1&WFb zMIoA-N6!Qux45(nRY``7eKPP#@H_x&Sr zs-?1r#h=SJXQGLRI~3(A8Bn}EEowQA2f^`Q5PgbDP(rU!lK7*qYOfoXBfXXJF+S{*>_0r$J|BU(bzr{CmX5qEh-o(Llvi+1QIP6XgKKpzOJ-s&~ zBBJ0Z&Bd+***JUDXta(Eftkvwo@b6k-?nksw|hIxuA|C3A4keT1qnF^2Ay{i&KcMn zRw_Tw9d!}HO&T0bP8JoRyp79gM^d})XrCB?mA|c|e9VY_+c#iuW+^TlaW32rySQ&k z%5ZWr(vU;;eY^Ha*tul`{#>^ai7i{fT2z3%ydqJyN@|}vzssR|6)x>YMj6*a^3y=i zm)YvX<)0kF8Sfs#@EM2D{f!j7y{$s@=bQF(F8aKcf?+ccq4(R#n7E=$ybPm1O~c}J z2R0li!|1OvP)>#0f;IW*_hvFK`1laIPCtkn7LakKQQ?)P+353T3ae~2k`aaqWfnZQVO^2BClBAhzh2HcSPO{S zD*8#kcxffvY*0xW1va}K?|<+i6_&5zowwh_>HP;lr`2HK;Gvj2c{0BI@=JU)<6Vrp z@B*=Yhj#5Td+s;*g5v+xhmihl-LX@QRQk*_laQKn0Oy@MT=XL>C@jNEFTI49ro2ER zvp{&CtTr2+oALv~Vzt0Q1^Iw}eX(WxZcLr}3Z9tw1c}3RTI<2tLxy1c?){iKb0!vk z_Z>d@_#^KztX3glS}b`oda zK@I|qc!sFFNh@&R!EdrK>eF<@1*x$3)_4R_S@Hpi!It4_4F4z%56;g*Mv(&w5`Nxi z&x7x_l;Q0$(dg6OgqO~b7M_9Kc`hOB2DJ*t1yshoa()!rgpyH4yOlYwNYAC!13jJNp}~>3^{fb7*E5(jD_uQhZDkf0uIgn%=N1O< z1)StPmPnctqGTYu;LQV8w9o^q&kDwwQ7Ty2B~RHl&og*>q#A#n8-f_M8x zRUdi>m1;dAf`X8dnE@*aJ{M&wSKl!XZ@oDk58ZPkf{gV3``%TaUo|2;oZe`saGX?B z6m$1w#79PpN=CQaMcPz_2vZQ1Qx4%|;q0ce-6+0SN-pQ2dYzW+AE#)Yp)m%*q*24- zupHTp@>6JoaL$?iv48t|?A@1)HLKSmuJh^W-YSC1GjDT0E>Epip2J&(zgPW)Z|2Pt zedjmt$w2oWU0@*1P`%1NNlmF2*?nbC5=J^YHT}rm2hD>bs|%TBPGpoiQO>P@XuS|< zREQlYbYM?0KlDJB@DS3c*@X(Xhm0#XGAg_e7S9lxPWx}obHGFU=2_^0Z}YeN zVk8Zn@sEtdu;J&6=?W%3_84xw>1GlC+@wjk@~W#59Tkn|pL-4?&c9H^Uv)CZ=!w2&Ik($!X!FY@4lzuj@xg;i!Z*6 zU=k`9ju?US&-aSVTW`J@-MjTfWLPMsO?!jHOkdo5(+zn2^*0a^6@%!=FueQDyXe}Z z2g1X`#6#_32JLer`0d9fZ^iL$UU}5G0QbAu(t|dYKJ_SCt{f%-h#A zi=nfrc-o!r$l1RQzi-$s24vw8thq};5Pcp#urK>?yIj70i*9K3LD0|!8QD3|lBnYW zW;7%gb4yDQOrlWBc@AyM6_F|uc|3rchr}KCIris9oQJVzkf)H~CZ^`1e3aU32sMU? z$~8aSufE@qbCD3N!lxg;hfRC4FlNjsIDHkQ3~#O18{we*`|8^spmK8xo_+RNOn!a} zCQq7(*igM_MJTC#>O~NCh}PlU_9h{exPTS~B)v=u7FZnkyKh?|*0_m_Kc0hsd;UD? z`?1^l3Sf?>2VETBx2^yC(eQqUkHgz>oPT?N@WnIY`%&i-$M8eq@Rt5zq87F;*#74s zY+88`dV}n1R9o=xtTU*waK?IA&C+wz80gcd7tTBHJQ3Qp@8F9S-Z?HL8IB(|Y#4?NIa7!}KP_7%;ewGP(VmXqwQCm=D<&i* zCZI#dj$(bE-n|eJ=^dP;P3u-9_=byf@@SbMq27@kV`HK*?A&unM4o>*ogLb@LriR( z_%1mX9$bdY694aWEL7I`)gz4`G zDsbM+R54i1Q$OZo*?tn&9KYX;Gv7aicmDKFp+c$leM%$}&L22e2~yB|OAFjpe6LxF zL95IdyS4;N)6FCTE%+hLjH~}F!G(WXu!qE;zoJK{U|)RoTO&A}PV^gm4o>SFj~Q=G z!@^}NkY8AW{Jd=J-n|c2yMxSYI;!{oVb~iO78Q@y31L{VY%%tw<%?DZs~0c8wnIhe z)u#`MLj_`^!jYA609$t*L{ZKmd^LM6ipp)Gy92``V*mcjBK)#u16)okepvWD%AIO- z?9h(V#%>m_dZP1@5Dke=!Wn(KV)MogP>05$U-$N~iSpE&GWIe?C$vOjY%u11{S9&o zN-0}ZAU87we{I|Zm%snLq}HhyLC8EZG{SrK%1`TY;79Oei=qG8_GFL2ROg)kg_t)= zr@}*L`iJeVecpfKCwQowSIGCMR^b6=vQmoh&6{g|F_NYTH;IunY+^{{oq6V&qOYZ2 zNcp|G9F!gxWy2n{YtstR(b2GbR2Utdg?NJ*AEmS<@uY-C=_ccjTZ1TYZ_8w~4Jswe zOHKQW!_^9+0+EBilJRh5oxXECeWyzj;`66n zWk@TrAKv%y{`{S{;ZI*TcYnM`45%Yw#Ni|o$q^i+2uFr;Gjk{9A1WdCN+~~CkgdR7 zn;p1vy&d;&bz)A20u|mDj`9=*!r(w3qg}K5y0sDW=-R9nRG7^o@1+}pq34_hb1@0d{(}$`W};+hiT);c_RVt5p$jP%YkRondd#YW70@)rb!>AeNIq*md9N30en zX8v&}BIAO6v69;2uDk9+N=k~zU%vR_3xtMx=Mt<<_V3@1>C>l+_lcWujvhVQx29ph zBIxeB@5Z4+htRHFJG}hz%Loo`p3ma6Y15FEl_i=^@qJ;mN)NWXTj3Y?X?VN)9;iHS z(N#cC0`RYrP`t9Y3wneXVq%*Eq?x#0o=TFAf)is4-`!qLAnEh(}^m zkx?qT7&e{@66seP&}zjaFy?NCTd5UIgdch25j^m~17aJABoLI8lpr-V6^V(7|Iy;W zi{E$-*C_DW?gBiK5sZ8f+rGXvN^f6Y@$R|3`4LBiEA05VZ8^?s5ds^DN8Y;HZzJub z;)&d~Y82$=AUii73Y7*Ck&%drj)c=rVw28Un4bq-P$*1#ov=yyTpA4zV&yt zC&D7Q)kGXDB;vSvrberQ%VtJ$>LF7>QEKZ+Q^5{JeaaLc^d_D}`6pWw)ZJxC{}|QKBA7 ztJlL$;x#=Z6Be5TVUbaYkBx@Q$peP*1xx-5?pl(bo{rY7Ti4hw;@_d(c=FsxdNL^q zO|X5icz|6Vw2ac=_5oqu$XX^Zy#z}?`v-bCkW8JSJJvhixw?{ zo{S3~Jv1UB0Wd`(QBC5+)DHbDaBdQIoMVhg>Q3WNh^6ro^^N> zu#=%SzU@JbipdeiS7Uy?MJ~l<0uLuj{~MDSlt>yX{v~IsR+<{Hccp!&`)kJchHC8oiCQlgnWjql5;9MxbM-PUz6S zEh0jLVY667vr^8pqGEZ#DiVV}am(l8`Tuyd;x_Htqf6(`h>Hpn&KwqRoJ~0FJeZXZ z9XfVKs}}KMIV>U)CWDqlvPZOi#sf|Ttd zo{u*wCR&I=7o~&KqSopW7au1?2yeq_R%-MjP2IY6L3~V<5QBV7N!?Jdg0Nqb0S^ud z2P){H@x?V6eAdIGLnF~Tyl!0Hi$B?jtV2bjDWjyWP(W|g;P>x$V)LqGUz|iDsSnt& z$jQkOHT*4GwnThF0t}`QoEct(nBZc3la+vMn;u$)M>G#rknp>wWh!(!3+87hVL^5r zLR3z#r;r4rijFrVG@mUmR*>E<0OC9+7Kq+$POwoWcphAjoyx+a44}jDHECN`2{r`T z*sJf#!jrjo5=mo$-zI*Xc_h(x(IFVqJ{p&Hip8ZJ<1oFm62YEQOl+&f|2oBq_%ZEc z&^|Imj6ll!ozmOrdzX0{2p+(N0}DZxe*X8|>2Sd1qL`!Jd+twIc^@y{ZFp1fR7bRq z_GeEii$MP5K9BK9NeB)$h-3459&F9KpSP;XMceZBVwnQxgS;J!L4O<{i{p5mKaQt) z;&a+;{_}d*N$Q4rR}Rz1#33$DCmLcml|n)o0d@~A>luQ32SxZ|YnN?*9Ky4lUpP+h|L6}O3>_tu{%FFEl@hmz(^ad9#7^74F3+(5!3YG=)6^D&mP#bOb`Pf2O1 z?9+Gxc+}CgYuAdtFnm9Gbi8nl16Ral!cJmvW_l}Oh;iLKHxE@S+<2wqZp4x(oSoJh zpJpb}OYMP4P50U3hD{TKYx+gu=0Rad2vK{58Fw5aBg_AD@h9i|BJeUtlEDMJH=Ysk zzHN?%j~P$L9@#wv;YQy%IG)lTq*r2Ow-B_7(1`fT$MGLY#Bn$}p1vF%#~g|GeLo!M zi}!AJ_ckzrPEz~SYac>Ci*}abk}uM^>=m8r8#Cf|(siilI#M2Qhon%AZ%ys;?6q^T z>+cL`baE2<`hX2LyWNTFA3qDDZ|UcYl?0l`-iL6>^5vNG_19$ByD)U<5ZrOcU0Ahd z4L+JNL-hOOc@YN>9)$m0HWo^yO3cYrQe24JZ@&{;ckaU5Z@i8do}Y}^xCGx3y*0VA z522R_AAArC7A&CrSA|P1xdi8&a}Lbh6p5sVQR&9b+k2wWs>cm24`FOw) zFxRfbN9irFsVEXX!wNAnG6y!b3Ac~x3!6tT%BL+k<+%0ZEIcwM7XPGK@f($0VhSQ| zg-UsVfCqIU!>?xx1IBdK;Du!+-mVc08-l!!drB{N0wycjh_oSvEJh_TwZeEb93Nm4wd8m=cGlEHioX{@G&@wx8+Yt z_c7aY9Dn6~INki6_Z8dFb%}Mf>^~13i_3B0>GYq&7tfywBy68PCm46n&7=FmO;~Ar z?^ee}9OWeY5OPHT_al|%VB>}R?{URWeNde~g#QIUv-|||WAA?rnhM@`i$`^q9=wJ^ zAo>tC2tmlawkjXvzs(UtT{q?jOw{aBn0HeW#`dV2iRd@4I*6yQnv1YV94+fqw0AGJI7rVA?#e@kHk&~B) z2OoYI=bbqa509IG&*>Tq3-a*5LyypT$KbBpZp4^N#`=!tMGC?r_ywWCU_c1ngNGh^ z5L$Xx*n@9Sxv-@)1kdg5BDQ^{%XV}MDu>xcqJ-dPq?~=##AeMwb0|7c0nj50r%BEC9sh|e3wMrrQLP7 zb3q=K?q%<<0vC4=M*AoY7HloS{v3x8id@{WAiS9b;j0%#(eV`so3syRE3fe2_xoE3 z@8NrY6l2=5e6&mQPTQo{DluUrLSA84l55z>SJ%X?)qXMgG8y=)at5M*_)52h&&v{!)kyU6%9G!#bA8Qe&MIxnt z4&9eRI@d#IM&OOn5g7AXCZ)%QLOSn>$}M4}oTMORG5F6v{|Ey~(zxK?EG;b!ZQ8UE zl{xi6pafx4#xEPB7;HE+DM83QuqYdE|B??F8=sd;+(fPsb$BrSvRFJgI8wydHsvK2 zOt^R!^0Ugw@bQfoE~z)T$>@k{dg1Pu&iBPg0!d>Rgcpn+g?nzl6{E*oN=x26Zq7H~ z;NXG1cw*vHVtN0;R7{*O4zs@e91Fi+jD36e!cL;EU8^MAbn|Uuo60n1LCA(XH75pLY?izVKXBSV)i&>?NF^9fw(Itx3e#@lczC7#LbC2AENhAmohv z&KL`T zoq~{6YQwFx?R<*s9;?P|+UB*@g?Rgt1eoZs>(lJGbwChqn3IkbdmT`ah+;u_$B=Lg z?`$C5sKmyUGQ6_12!p$Y;FVEf_&vpr!g3F$F3Z83TU#Qf(1ErQ8uU(7q0`h<5}qMg za9t9<*;0hq5Iusm4xC2u*9?lpMIBApnrXvx=SSh<&r|X9K07j>YKK`HiqXHd5j|qm zX!r6#JaTR{hLG4?N+R!(vm$Ww>_ga|>q6?p_V{IA1qw*GjqM(U_31Y3$+O|6(?ZbZ z<^4!0a%0m&t?~CEGt5-}-PAt>iO=oBg+0SC@2UjMqWjY-T#X=|3#Y%8ju~SUa6@ks z-dJ0Nd0UI|cdGN>6Yy7oC|~6$qs;=&-&v5Ib=FzFn7Ty@LP=w!(T_5EG}A}N;;gP@ z$k@4mfhYnR1tO)KUMkcljEJf;G06P*`DSD$7eT|L$4TlPW3Ub@7i`Dg&6&O!iR1_m z-2V^>z;E#o8L_)|Zudr#(5#%_B_$=oU^F5xKVM7${P%{9NZGfiN-@~j;r@}6Cr`%8 zl`DNQfq})lngrg;OdYnHLqwBM#zMkxR7@@|OG+b=t;U z=iMibQc&*1Eg-9+e$D{LdV9;qWsI>tft}DX)Ev49-=fr3dkD@Q44|bBVC-ZYU#m!qZ^T29(BozRku5e-`2MjU~{kH3-+Mz;i+G$SK9uUuEK!`I!hc z=@1;E!Pm6!6F=tR>rEwCoovC7mMTP>R4B68Fz(wdOkG)oz4>J*w|a0@uOKYgT7+-6 zmg288D=wpD9)doj!i8&QXW-g7nedR&ao2(j+&n7-Kkg%O9IHi-cs=^WtMMLP+w6bJ zU~wrhw6%`zFOX4c$Mqkk;M%!aXc?-4%j3dJD+*9-v*NM&SwakoHgyt-4}ZV3`hxRf z8Dwb8p{6t_4+sv;?h~LQ^~91 zz8sPUfwjVpZUbB3*;!ZmVkCj0Da=Edn_qyhzy2C)e_x3iAI!jStA59x-P`fx6Hklv zjK~zxQ&f@TzGa~T*rBP@%WiQ^lrZraVF2nmI2*V>a zn14eO3Q0JXlCV0ftqYxA&A|Dc!|~L4dJK9k3w_(0@XPH<_4$n8az2PJtJH&O_hUCOkPb9DOJ6LwKSQsS{!m z`}`rai8SH&yCMXojZ5Z^Deu6NE#1p zpKxoN-o1OnWHR|;t4D(qgp9-K!OinB@zvTQz@!pYrwvgSid*NRFC+*jUl5Ha&x;nd zymiHtn-^f~+7xJXRH)XnaBhhAzq$nEK|cw&g1}~V;?60YkuOq(OmI`z;vS!HA`|BqC|@ z9(-_3E9}j(;ORwq7}X~fGcSoi;!_9kztbb}>V+nBek}tdJB8qd^Fz`9%|l2C*JIs% zEphp0=~%O^0>RO0;T*KmIam;}AiR5M1V(i+;G*fNn0j3cJUS#CbN(6S(-JH9 z6l2o7eEjxA2W(2s#hmR*%(*5OAFeM!dWi#bHkFYk_Tb>ec6f40CT6cKhaprA9?P~( zgci&0Y=Mqb_KQl#vKL4YPTU0^=yuKxiCD6u5Np$1I555yLZ04-vLZJkqctcfvg3+@ z5qSEn2=sV%FQSr25Iz=%sL82l8)?M)`w|fO%t7&6aL?-#m0wa2HXrI+5Hg&n2FNs) zNa_?GP_3qVph3EoG-?HAU7d(~hDCWJoLpoyEWBdSo2n15jK|Z%>Q)Ty+K`TaR+AXi za=*daIkA$CtD?Vrl`i_{@VFaoa8lq4;p^e+s#WOlJQw6QZ~X~Nh20k^Y3}3|m*TeD zZ^OqQeT1io`Vw>D#c=-z7WcpY`YWD%@=4JbB4Ci%*;*Qg=k|3#xl0AT z(hV(z3i@vI_=-Rw1UZQRUMP>}K7=1$o`@~u+G6%KiFkcwE{Y0)AGep|j?*LX$#scX zchHOis|qU~X^R&|M!`%XPoq*`OSToE20b=C+!_@&J6`^+5MN)Lgk|GeW98i~aYjp> zw?dJjliH}9E3;7==$v0}C`PW?fe-&K6{DCssH`E`h(mdH?9F#!@olZ~@nvza(e@&L zrnDCGAT;#*^E4*h)RoIoZo+wO|0oiJ?a?hphcdedsU+4~hpRE9l>yiF4n^`)tr5b_ zQ@_o{w9!deF|G}EjBkhYJLq7syHQ->;=_t#=g@chsRX5TECoHh2Q#ghy0#QM#$`_c;MwbvZE2NFpGf%7HKM`e(q zv4vA{4mC&*VP!t2g7Kjra!_3CY!D-misF|GFe>O;Q33n$74cN4)yzFxQz}ZWc=hh@ zv145t45q+Ode4H1|K+-hk?PDtjLBF38QIU~i_^en#(}-4Ij7v=cl!UqV8HxJT?E zYv=dcBHEzHOurB5VgGgONkkfG-y$o; z)Bg}E?@IYQox`YB@?f4Q6U!d74c8*8%t4zegr||4pz^-OR4#G87Dj0-BEiNhV?s1y z28k>ZbKC^>so|06m#D@i?`P1psNi|ME4sb0A3IZQ2ntann$l-+c#vJKjM73&Cn_*IMblX=tqfJA499aLq7co)#?>*4Kiz=OURVjeQFFqA^km^n{fa=N(wf%gft77Lu(^xs76d+~42o3*C&Z ze=W>|lCO#C4z6J0jS$UI%FKU$ek8gk5#wR{_KQq|hxz@RB((+-I z=qG7p(|F9L?((HRx;)xLFpoxNVHoLFXS_I^5fZ5O;;duaQE}h1AbC>`;_H0LnLO$y zixr*7Kuy~3TijpCp9%Qsj}{N-{LjTCA8;`dt*9RDZjXu+7q>*^1y<8wtfuB#!);u*;GYQ$Pc>)M@Scp zjer2dCEN>>q_tz2F;4-Zn@Ebjz2BT5*v`C79M+u0vq z584}1@QhCwgxWq>oYQ8|SPaJY2HdaFg`JIDY=k6&qMRiMGita&*{7BmR++P*rbUPx z;&w}hPQ`cvoqED+d4zv38si_90Vg)Vlm{?~jF@$)_lXD0vNth&+dYmb^_S+tWT@a@ zQ}uJc-T7hssAddnT@q&uH`XU@`i}IGb*dvP2XkFeBIqF{t1l5hE_P=7Hpbk=CjlK_ zFLnWoGw+c=G^i1#?Sn7enk60qq8^62@D=+IB1d;uK!h z97nBRN1qYgan}MjYCGIzR8P*m-z;0M#ZzAm-{Oq@O>g8mFY%8#uJkCw#+UtGBPr-e;RBPG9^{C>({l*4$M7)Ji-rgm%=)$FO%OPI+%o1{P%hn4bz)T@L%G9THby zk*rByE8|82N=GH(wL;FkvdYv-XnYUn->g&Swb9)PE5aV_ZxQ~@j;ZHlU)=opAuowjwJdQefE83eUyvDhw%}YLi;a0@k$E;Q+8a3GomBBACQI7l;8sc zaTEeQ=Z05a@uHIhQVJERZzF>+?E*JWJO%X|?La^v^Sm4~(0z^qKz{EX^;O}+{gq_JKFpY31$Jd3%xCIIxsW|#j%p_l$!dS+BQ28{2a!l`O zY)hdEym1RV?8e3%kLSIqEOsDZkzfK=%J^G>l1bLP5MuJ#Bsl7Gt4~wWkG5 zO9Is_Zv{Ph9UVu(Ji6{)_P;w>5ma)CKpaM)N!aC=BtOkAhql+iiFi*mU(4h^SbxSb z8)$RUPE>-Rrf)x7TZp9gI*7|}A>!1BQkxSle&kcfQmKa0{*gher za=7B`j!sfb_G$DYbPJvA&fhy?*ZoDeMw5YftA5yv%2O_B8l^_L+wMA&Ppx);* zzIFnM6JSmor;>}5BQ6bFA?oL-Pd{&meNem=gqP6Ue%=Yd?Q+NI5Kyq23kJ?BcaExZybHi5|lp$y^j&1s1sc{Em2QUk_*nD zHl8(l?a%oJK5Grt$eU_Xr_apHu)FVn>bPI`Tk7;{K5REMno|(=#9;+z8MK=Q+(lSQ z`CYTuyt?#jU$8OBBU|e@48@X!)^XipZ0L7)s&TG_4CI#-La%t8e9eKAqn=eQ(Jr9p zgT0FYM@#F0J_% zcQR)_?ZA~t&p^lEmSz_9=v?Dfxghl!YUorVl@58V+Z#7v(ut{X6mi^p|G0Jd6Kk0E z2K#fm_I~$|96rK3o(!!emyN$Bl$F z5dREtzQah={cy^XcY5!$rL}?HD$pTO1&-WJ20C0;!^m&n@au@-vC^1w-Q3*=>iFY? zI#FhU?A}-sLG4IsQM&J*5*3KO9czmg&PNG{#GV{7%{i=BxOXo}VK~7VFS%kk!WDv7 z2Ntf!eEo-lo5%6j1!^RjeA&%4tafp@aJ)7B8Z})BrN%yJYX-8twyc9GJ7mEu#VE9* z!l<*kKIljBHvSLmSxfKtABEnpKCNEwa^f#_l2r1v`-^_Z5B}yUfe4lXD>etjj8`NV zaAvo$Z;`F$;mC;}79olcR~mJ|&7PS+hsrb2dN~hgSCa4Q7z12VF9ljsKQ9pn&9Ckd z{61HftKK_tT~&m%>gWn@NdG&+1rfxDeGF`Ej~M)ZQgerbw8X`OznR?3D6-uSXp`Yu z1;3!Q23-N5(Z3Mqt$^6*XbHf67_d%smXeiyZn|=#6fdI^=d&NuEzVpl)L5c7oV!D@ zNu^EYcgxp!`a!QjZ|0RMQ8g2F+KdwzS~8MAp|F9W^W{5AC?lk`4#u*H!XFuxNypo2 zt*ZI0&A~M9t2D36lZJ$;3c&n2@R8u9o*yqk^c5S{!#S?9(qqYxvEh>f*cdZG!}t~6 zOUWPcvxhw$b%jh-Sll{=js}TPP3CkZK-_Y=5o~55_S6xx8>6P1+>8+@Sj?<4Yxc>i z<-)lk2rKO~XRndaa19WxqxxQ;uw4mNN<6~n} zGh+B)?;x?Zd_WVvK@UeG3yj4V((70k`6sg2GMk5?Bz4W=$8q12 zrdxYXTbJ8>a7+&ZL&br^h{rsC{}D!vNj?u=RulWXB&iqhEb&uQO%Ij%#f=vCwF_#8 zR#~c>m1V2oy3*j1Q~SLOgYmRG2q|*W63dg?~ySLNK$ zfZY(a4426Ls=G=n3{ggrp(i7bz5`Qs#KsJ5(Qma#%x4PyBB_E~@@VFWM5|@!(iH`s zjOJ`DhMVD!&R{f#`+}+D=#PE^hNMh4)T1|+aXN~)GZc*iEN}XafPC~d_}wT7ZQ^7_ zt>HnovkjE_*A96Rk0UDPQ!_2m$oQ43}gBJ_8MUD-*r9% z@LbKv5BRlG@INuVV=vl^ChljchTc350wjPu_Fhy$;$C94oB zC>>ha9FM{h$;Ymz7M}|=x}ncIor?=qC1FAoN_t*4-Ud(d7Q}0=C!V#9jpKh!iV%Iy z_4%Qj{@JKOv<=0sFPdJM6cH;z z5*czp$rj*nE(gUm&;pMbK=|hE5<`TNR;ABqf$~7*C!pYXR7hYu?*bCa*NG6@QZrM0 z(~)D__)w&fz){!p6`p1y&N}I6<%PO-<>XNGMj}?AR1|me5!$}FB-)lHKHb|es!G$| zbz!QvcAunYCQnDaVA>Z{=u8v^yx?VIyg#AmzBw>g2i!1E7cAL%Cb8eZClp*5k{q1w z#~xbJsya?wtf{?6(A%xiz&bN`5=hE=fmXyb>p%y50&eGj1ry((RuOH!$Q&Zm+91n# zYZ4akr4|$b`u(nkfbn}pOQ`km>h1t}U(bRPZ~AsV{wR2{A{5<4qrMgKPE^w2^+?sM166MjN8{Mf*2 z9rO{)p65JtB)_OnPM-b}sN}wmjE;fzZs#n zC3uc{{H006_ki@JLQlm;v>N)Af9!$El`{b4GkDY9p)Zd==?>_Y~Rxof`+l zFK$&euY-YWIli#4kg?|05)y54yf0Rf8U-b@h+BL&fzkGZi>P?%iiC7GVGfuLJQapO zjd8kUbOD8$Uinr3XZV;!>1O`t^b@A118N9_63DtAx%kV+-1S64a%?6Ejxg6YENE^; z^e#jYrRXL~?4o zSG%zziCz5B@io<-)Jk*oLlhoAn%P&qdP{_>>udY&N0L24yEuh8>N@X%F5TMg=JKF!`oRC)8AydjZNy*+3i$A2HEt*;LRI~}_Vt(3nt-9l zv~<_E!GWlK3QZ=DC6gA{0 zLKq^z_c-F_3sNRq3`y7K6eqxY{^Hj|vHrnWyhkZ{svNW~@+{zX!aAf#| zwV&p^J0s?UB;l2>A(nc>XsX3UkP$&D}%_VZ?d2YgI8fhLQy0Nka~ z7w})-nOgYkfGU+^$<+D?RiKY7Sx`^Di>$Wg>V}2L07JyT{*w0Ffcrj9J<5s!o`_Zn zX#39&QGImLA>oC2pFMg#xbwc>(&q1AdB3i?W_>&e5y8}kp0rs5(3a4B^ zR!Kr@$O$qyNHRf2rTgcTi!~jJp8RXiB#FFuLd@Hn!ATwd&4D;6>FG*y?$#IC^w*)X zKTtmE4-*=X!WV2hs9iYkoyVNR7eB&`JoX5tAT_t=b!sar=HVc?X5ke;L==QL=~aSFgBSP zbQ@G98Nik-+~MP&4}e*E5^yZWC5EWZB+4l(;|8?oy=aAJJhm=3Ij%57N#Gh*={I6( zG(k&XUvXOfTCVlIJ?6^$LnEdk6h`NPStNvY-D#_Zrhwt67Px=VVJgsb7KVhBR^ye?_30vHX%C9wf zp#_Y_X)Xf(F4p9o%cpXA@u5Y>$BP)MQH3JMQCwUT5)mO4O&|ItS0vn^!+sOh+bT|S&G5ajQTiX!zZj?S3TjcmuZ|7#Jsnt`)MI~VrdDj*Iub$=&V?k1Q zbrCkCwNd(mu;r?Nm5H1>xt#B`Vb*0YO($9;aJ6*o-4K=$I=MVPsGLGo^NRyPIjUji zK$=kVs#^?pC;+o8QwfJb>}v{#38_LX>Y@^I$zkg@qTEyb9%UMg%rce{CyioPBDo3J zrHe=0PXA-MMc_fE#n!9Q(I%Ho($EN+E!ry8>|Y@D@NdRCO=G$2wQtT}NrdJ*QS$TP z;0as0SZ}aRIh54UsEPeI7$fcHT`?v>uQ=Af_)T*q-qs+GzU)$_Flj(alg}RD2|_R~ zWN``U>;cz#^!23Z1|AgxbljTc@HuLR{S`?v7f%cGRyD?p9#%0qGJ7c_{IcA^pV2lR zfk{B`U#Z`S!JhL3Jwn%C^i|nRoXF^-a4@m!P2XRP!rC5!C_`Dus{N64do}bcrcGDV z#5O!@e^1`Fyk&56;n6RfY0A{}^o0kXG+?3Hcn{xm+b={uPdu6_A@PRa?XW+Ebv8gJBhIaqHHpF)pmHpp_HHHw7%$nzo{sB+%u>iVV;vC;Q^}%NcyYQzC3zhPg_-7*|{50P)ar#0UO!?XTvNE|yaokT1n>lu-5O{L` zPH{*RGM%&94?q#ZUF230+np=}OF-yb_R_I{%>Yjm(sIb&@wl7wH8hq)#VC?8(8m64 zGgrWPl3d8MVV*ia;s-Y|d}RS0z-A|_zC4(wCSnYBSRWz%lq;)5nKVc8#`}$_bsiCo zV1d~E#E+zf*N7592tX!KXN3!Bp1%8v9~BINcgz(Bh`xW~8C>imd;9BF6xxIWOVrk@ zl|-&mgk)%C+_uz)C8Qs-BvX`zOMYA9Gj0l!Jg1$pY^AFycOJ<64A36cEcRvj8d z9sy8tz^3k(_4pNVo-5(u`pDE5cTei<{8-aRUMcT|S!wWQ)(eI4QD7d`**>7exzkNm z)xfpPchnP8h>@I`0#06agxs@U5bk$+Lv!NT-f&Wzl6c8TMtI*6T!AtM8%Rtp#0i;% zJen&)9PAFOv}YaOLx!-jPjZ?=hzXlEvmb62nsLCI*5km$hCM$&!kD>#g&Q|Zu*rYs z&t(>pOAD%{Bc+cRD3F@b+@dWM^549Um}@bsz!ly9u&hnvnd-w%B@H5Kwsm1a=&x&V zx}arYU9BE!eq^K|=Z2^kYWcWAMxCr7jwmtGy~#X^fVbyC+OXJfQC0^hL-1@7dxRW| zK>EpSiXlSrkT2kiCuW^r9T+E)8Pr$S#M~R7S*BY{KHL3>gK%heFoT!hjn{Te6*AlKu|H2{yKvB`A9%EL-T_ zX8B*0&Dr@b&b-|)paU=Q7z+$T|Cru0=u}C$> z;vFxwq?dle@AzOrO%|m$nQ0c7u2)`IY?c0 zd7WtLAj}5e9sy_ex0d|H_Xk1%rL93J7ShKwZ$>Sq?^$nN20wR-G|SD{@l6@nB+b+i zq{|8xkLU?lnZIAt;IbMZfU*_U)KP%67EOdq=`QE>QP#6tyl+9I7|OIrH%>q$r|}Bx z%I{c#;<{wH88~>j+!?T`KEbKH_7-1&jrcGykdRwUF6<1m>l*XtjwQcRCWfl zoO-L8S3{U;Ita(!mLLJwF5#8;-X!~wxAzk>j*uPcsKs8Rs4Zp zE8yPf|8!JJX-sK*a)DJ{Qn^>DI!ecwi6Zm{;jaG;5wCfMl36Z_G6s&m+nz7oqJd&k$3#>rnMpUO!Nxl(g^j{kHW`KMlm6pZRK|f(_gkYGj771y8vaiFr zThiK$fUAMzKu~CCyPAcF-D_g;TlaX47JqDzM#DWGM;y}c-Anov) z`rCSrSqS!mQd$zFgl3vR7;I$WZn5}9(EWMVx8y#+(>Ha=R$D?khAwZ`J73i<@f&{V z?rGM=xR+=lct1qFBv_^*T-KY6KM(K#(Xd$NCEFU{wI;8ai#~Z>xwxvRq2R7r{R?5K zvIAazxTqjQC$s}!Nnc-%Dzcf4R?GsnXGXd*e`!^G(wf)+pGAmk(IBD}lg#)~TmGf` zK%CsH8|1)OcOnmgD!$TUS1O?XWrul$iQp{X4`;7G+yDge!M8#0NB=CP<8W}9m|u=H z=3pSZn@??Cz_pCJzw1pam6DT_FP1D4%vM`my&twBE~g~XqJZ>k8lf7QqDvRznxNez zS$5#TT$d*`gNU zu%P2z?Vg%2F}09vTqnfA&H2}aAKkGLqy!P?=~Y0mG@Y58Y>~fs>HKCXjLBwbtLV-; z!sO<*`()3idpMQ4Gadgby@q!w2}$ zxTg~pdBWT%OOkD$&qY=Phe+Jit$;l0zSqMgMeQ`3Z4q|gPUFa`(U7t3%6U7qni@8~ zHQE$;LrE>$-7cRKVIjix1dxj(61d!y;_&{{!a-+>+%e>Yp2rJfH1ptd$9=4(WQpcx zdN<>gtq;S~i+*_23d?X~TLE1iPh%;Bj8kNP)@)z_HRYrImv!9cF;M<6U##CY|0jGe zv=AH~|BL+!?}KZf3;MaKqno8xS8kD)CpbgjF}_%mKv+XhA_*V`oQ+)b!Lu`YX>!r}$cmfpBsneT^TVdI1BH zU7&3(M@S^3`#T8ZUtm22(znsz9d9Eo^9oioaU22yhVQ1@%49>=hdC!vg1Hfi%@q~F zR*Y2A`NjOb@m-!WJ;gsdz)C*R+QDg(N)HDerpStOJ$Bcdc@lWWZ$lx1JbtAi2#b&i zz8hjXPH0z$&LXa$9gOcm%f$sr;fvzwuqGxE zU-2khyR_PJ0mh(LScxEMzqfd8yOp!qfFdy%1j|#VY#2LH?*p-Gp0o85QKkT<;o9TS z+#|`P7;}}Fn-rBgCkY?#6cxit2$6Om*DWo-1E_4QCxH40^s}ZDHb;gDnIcL8KV)lb zOSjfE_?xaCWfGT&_f*F1sQMC=7D)pjkF(k#vR))hA zyaNk2o5ZZyr*Fl;h>901fRAbm6*t7?V%bfn3qeb+>_h?2d9Wl$8fU)4UrcFAq|0gr zrBc-W^dM#s710R+yT>V2?`)^oj$Ou2{x7bvK{skbKzZD98_qO<&##Wg69`~fgDzx5 zcVVuMztjUxrcTBQI0|vCn)3Xw5Y#m^0)V*oVve3r@B>^NKPmH%7HKgD2WI_dCzgH+ zKUjWI2RRpGY!VU{De_Vd09h%1?uKSM*U;5HGyr;ajS%-_mSabm+Nc-9?AGd$Dkru1 zAtHhiWwmD~kr6m0}YzO0Q*Ce7Y7wKo-ETZ}qyplb~v&QzV<0YqL>@MXAKd`ZspShE zY65RP_W1mLJ6IMN090J z3Zp|eD)tke*_0c)XtRIwzuNAe)BVYXF$>@#NR#_c#FGFCC9&$=#vLUALacB%I(&<1 z-q@@+q)Q%mddc4Dk7=(u{B#-fv2#0V-R?5T8@qG%JxA#k$%)YpIZk4-`n2vYgN*%g zS>AhREukRcZ`dW;dJKCJtU#!EES#B_ugm!9eUZsu7v@JZ3dGUMT0uj6-!m8dRL_R< z1tNJ4vj|zH{U7@z4AoqYM1UPfm^2g5X86k4K>nFy0ZPn}MS=*TlC*%a_Zh<{?`?$N zPy2qUT71%Z^71>fz?MNILK@3pb9QOmLO~&&+gdaU!od;;h!lem2hM>QIC6G7-x?uG zlj4M`|8-{@;dI8a69ZG6Zjvh^tW>8O#0gkIHk8U8EUp~ZEJRN z?$-ybiY90=j>G27rB`xGca}4LM7wuAjcRt>J!u+pTjLXZw%>n9mH{$$$5R26ft-m+ z(Yu#0G+4vrA?qMZus7!6#Y9K{q zvH&0}2`~A&&6)^CmCGh)>n^W^CtV^!6UjX?4EOKvr&`K-m);brV1~FbVJMlq==%x! z!nJ}j>h!hoJ2y}xun4P2)uyN6kx+iTww~uJc03bSp~7}H*Kh3Y`=HHJifSfTm1;3v z{;el7Ta?aMNtmdF9Pni3YCSKQw=1x;#~>g8sg9oWO>9v1GUywX^|t|N0sa?#fv%t+ zd5eGZISR-Z%$irwV#JevGE`_O%|l;Kbq9TR@(=P3*>hZJdi*a$ErO}7h5rSJ;Dp5G zaaxT8tn&~d@N8NpsmQv^uNSB<>HzYE^g2zFoE@H-==6pgrr%nu6Z4tfK-VmbuHSc* zF?sUp!@oV1XRL;J2jF3PydNGOW@EP6hQvo9nK0%km)@)9<#?sPj(^(Xb9C91SVc4v zvr7RkrPWelY7BQ)D-nN-+U`#yNY@0wX3f2O0v|!2tRDE*-Di9^IB1lh07?YI3CYc8 zbYpB6hN^6qhx9Bm|5K+DCCmIYv!duYrYNZyK3yQg#b$rphnP19S;|yhUA@+APvKHn z_1m~Lkhsj1Z@XtvWztRhe1CqQBMkr@Fxm-_AXXSIOBoMNA9nJkI1ySr)+mqd&Un<{ zNBjM+m#Bwnq?*|w_&0rN3-h+SR$dIQr&V6&CuQ#aJqCeHQ)ezaA&NEqjTvzveS;d7 z#P9j*?zz5yXi)x9I2eYM^((((S+`)n=U{Lt@hMV^BO@7g>-pCpfA-g|@Is{ke)EM{ z(!hA<Ene?AnnDyH%HO$hyy ztE+3=MBgBBX(mehAGuN~N+7pm|MIWb3DL0d>&wP5S*ENU<9}~gg#<#H?`tkh#Km8~ ztI1GQ&x72?A=>WB1?9$KH7ITUFZ{+JfBta4Dk&*JNH8_(eD}{yPR6wS{CTkqbM(j~ znk&x+RR&-|aKGKhZ;%19tWR5-YEyk==!#4SRZx?5|XX(ICtqf)Y7g8CWm~{U7+ajuXVe5*LjSbDH&EsdC5w$P?rPZRFyK0&)eaI?L z>`KwKAAKx954Bs;8W`q$*=v8($IJyT{|R^m1PmNf=6L+KN_kV=>`Sk=T?<$^xR?jA z-hj6j^NBp;cc0gd3L@Q(D>|Kpi-1HqYC1F(mf3nMMGh+@m3i|2;Qzx#7X1eFNoS=b zQt*daKH;z~IWnO?KmIsMNDRQ+99|#(#8<{CDf~_

paC55m_>ryRNvbU22jABe5< zAoZmZLcEk+B|4%I5}5%QxsfiMlQmsW3S4=WN52BNnCrK0|HKQUjTnylcZ1L(kYIp%>=s_m#2~1Xgb-XzN6{~GIG%jBk7@* zvJ&~0EZ}iLAi!b<6JmnsB$QzJ@)ps1+{qC1y&FKoNJgm5JPFl$TkU~(bBu@{LD+Gc7xMZN@{S)^Q}iD* zlup7I9_o|DCHngHruT1yCtl`U(-v=5;-?_469&uk{6UgW6;L`}o0s$$#iO!e`{X3= z-rUXa$uIj z;r;0E&en+76SBwx*9ARKA^~b}AD;X1#RvB5gGoFqSW%1Mkj~=1a1_Z3?J9qcMxZ~V zP#mF3X`B5IN+;|hzu8=y4C)V<^gqNB(~9X0>bstSo-z0#Ra=hF(wIrjThXP0DDWAy z5-GXR)SyZflF0UlEMCH}91@m=Ajr&Wst{S&_bxr877?i1xTL(*?|`1%Vl=ts`N~mg z-dPO)mlpY_5E!KXYk(&=EaCoeu~ldyZ$tr8P+W&35jf=Ig6ibSk_5CU45026YY>9n zpXCT)-0^A#0#qicxi=v-E&b``UtMO0cVPbjoy(|@FoS%^D6u_xAzv^T<^k%z)C7we zg7P+NWx@dc6T%Z#1^7 zg`ZF`GqT9mn4yuMs^XeXJ)8f9NWd&8r=+%>eV>)I@gz#JvsXbnp+Ghq_i1!tz^Iz+ z`&K}5F)ceI>CN@uVg-7kz>pqC44zT!+R}NmmlqdPPdDpO+9KEPZyuz#!_UpJmS^J7 z+oo0;btBpSq=wDcl>f}qy67xUkD zNA0%2JC9HApwM$$dS14`tndHr(2kcQw~dq&Y$llQ{_IB|3!B@xgh$2Cfp|ElY@>CR z`!DDcc;rB;`!Iskku&L_$#^$3h|E;paQ8*eKvX+)e|pGcolt`@K<`dGhXJZK}+FuuzcJVA=3F0$nZc^;(gNe(QPSLJ}w#Pp7*dt zi$GTHxs?Ckh=g#)s+a_!SG70Ls@Yt2-3Fu5$#e4vb7boQcPnvOzL-N|CUgPlqs#J~ z_J6}Mg3%IHre7eTAPsT0{D5+{CWI#K`3oIH2qF^SGTKzna;arU+Bkb7A}ikC&e>&7 zSdpcmMuLhJJ+0EZCNvuz(7pG~fO0?XQaN>!LNi9iMNDOcl5ZC1^A2JYBr6 zYLt2$5_k;|uwWHlENDJ1mermI@{ntftw~$V0C~v&xbsD{|W$ zT2fdvubaJ6vn@B9DqFQ7(GK_A+H)CM{BOW8N2U`giYT@k+nM3ueuI|ua^pmiG&Tz# zcI(xdRrBJ!WgfzE-er{NwpwFNO1o^UQI%i3*)}M=To0Y=KCC575yo~crPMD`$Kl%^@kp9e zO0YlOxOl{j%0@)0H=u~#PO%muM}-^qmvf?5jJ!;u7mbL0YtxMc@AUp{Kom4@u+<{% zBQrnrC(iXfR2Oo)hmp(Y(oE}0K{*AFP4Sn*mk#U|N!Mn6GVnk5)1yNgEeP|qy5H5 z#wqfSYxIoLyO&{%9Qq4CCP*IwElPtMLA>P8=XH&z)P~gHo8>zKL#Q zF42-8I~FjVV$}2ym>3n9vJY#ljfTwni-^ z!_GO?Gm)qiw%ptUJsiS^U7dL4B@!eZ{BB$s;fjP&PA!DI_qXg_Zb7dYMNHKn1L%k_ zLz&PW8ziL(et6+$exKj`-`=u)I$^2k$pb0#x#Q5wG4$uoSr(^ZC!_2HPhdwaeY-m( z+3n#T%)!TV>@_;o7cUYT(N-T4bBl8*s>*6E`ZCFRF%0Fk8KQ<~Hkx(5RglFR0pLrH z0|N2=VOf;X8EkLpZ|s+ywgS5w>5`!j{y#jM7DxgqGYnJbJ)I|j-6)l@qv=?IE|4c3<~C#K)pOqIAq4f^%%tFov+G|Lk+no=B!OD~Ty zOf06GkuJcypV{quKhie}{4tI~VanLbET!A3XuSsP@5wfQ9{eEh28%!Yo{~ACX4UUF z3%#FkKD;%5u{4=}EU}%V{@2gYeacoa)m?Q>2?b)oQQh}{_X62Utl-sFch|?g48Q_) zy$5)bp$<)E@uL0ncdo6fLC?BQ`}81$!o=i=IEWFYFNp=^~Mn1+^hH zq!CRJI4L=JCLt*J%c-)Mv^5_Q={!20cd_qBW@ciC3y^MV`%rgW zQ;dqipbVo3`IOwgxkZzq$Iv#j0Toe-?%xoK;yn8-54deD)qtD@@A3u<%hftBzV?s9 zy8SGHYT}m<&Ry3cbC<%d_b3)-YNPh4 zt^mKAzl-NC{`Y5VW?X~=B5AGyIrw}oP9D}DAzY~}3r^0=XyeSZ~Vsn(aD)aNkp_w$j84c98TcW=yom(_X%YGXZ28M%_rUcWf%k^m_+Fr0Fwu_#^WxqxW z`iv8~vA-`dSV_OdR#bi#;H+9B@{U-(X=P!P10=xnm2Q6h2M)m5W2m^-fv2dj5Zp}t zze%0B*hFR9B1F;0{{p{oG{xJ-O^nDpdcyNkXG=9&$|^NsRYUHj&Wfl_Z#r_(bIcf~ z?);UJ2N@AJl>4HE|L{Ry1K+9ZfTH7if$PSRB*wYx3Def6WaQFCdS6F0`7tM__v7yw_jsL-FctNqU-BFV&mXYaBxiHxkFF*zig+PxekZa zGhWjF-)AM7svW8`9L^{E2YJ$jC^{5S;AWhG~#iz8D*fNm6SLMqt9-o$2wE(yQVc#l_w$C+hfpxyaXUyc2{ zjGiq&s>4PsKT?pmc?`9bY zZh1`-6m@!O=^4^B`n~* zLBYJl=Pi466J0l2`1}0Sf-PswN!Hc14n2krOIPd-AOz{Qd$L$h=EeauA|m}NT)-5w z^!)HW94_^zzf(A8`6$%*e-w8BCJ>VIww!|l)~RU#)t{Pnveu% z?4WaBi%1#zaLo03w#fDKb2jy9C~*wCNHTYFz$<(v5Xrja$qn?x%=NpZvDBYyc^u3_ z$EggBaA2f)-N9Il$O)ow06kx>&Z=x)($N*nRRP54h?OHT8$)};z{hvhq(-O&(Kl#g ze=EW!vSFFX5qhCuL>wc$9OwRljGd6#cC&@gdE_k3@Z}0sZPZF|j}<(U@lK@2cwgVV zMc;m8pDmyYo$x7Q67*C}@c@`qXp&$JFL{cQ2x3M@La55*qJEKb{@B&w z4y%onK!j+7?gW6A=sr3oJ~U3s7K%ahfqe}tW!`YhTY_j-M&BXpPBbeK|yB1(YEsn3JP+z3qE%W z{^DfoyutLnIouLB?lz+sHjaq3AaAeMoAK!unVh6N}Hou8<7Sw z%0enN8`i;*SWbFYo{%|$RDmG;KLOB_sCkkFm)P593NA~hs&);L73w5_iK|443<~<; zUmD|D_t>qvG;|^iMsVghZkV)|#I<(SREs!?jfbyhg*J>pnz}>tpA-0)SaG1zolb0e z_CB&DV}g{VQtMPecBtkcxe*C0GiXNbxFMWYU}(JsDi%nv`}-Pz=SaZUv+vR%NuK=~ z+4zutrZCD9UILfX`fX>SQ}OXFVaKq^AroKeW))XLxesVF?Opxv<0xCj&D=*ELa8R< z?Ts-!tlW|<;Dr{b9Y69OS3keimZAE zk)2agW`V9lMetwDv$!6koEGI)%ptZP)e+>o50AP+>h5|+E#G!BY?xtN7eP{#kIt@( zc#Cc8iE^U=^hxuMzLo!YzK2*RU^NiT)yT}vB`B?yfm3tZaM-~rYsm?}XCl8vn)u zWxibNAF^X8vY4JkCA+;ll2RC`*Wq2)qEpTN60iU~Q~SBU0M-nVt=u%cJV26fFCaaU zdL1;9X;to;ktNCV$_@hw?Xe@l{SkYsGYCSH~nx><8nO4j)e6yOX1<*4E7f zibWECHhF4Zq?eA(1DV&S+ku`~oe=D>JwViyK@0CQ8U>ur84T+W$(DV80pjIQty!O8 z7aq{f7y~HRiojnMS~K~a4cB%@-BcT)_+gjmgKu|w$dr+@t8Y!%j0S9->%ZmX>;4N# zqWRUeKt-QAnFyo$d6~ye6p9sZ*-G;&uZMEi9FL`&0oVn}W1u+x<`2MTrwNqzqN%!7OoH`>37ba#Vvr_v>z(%mUYcZYPR z5=u9Slr$Xb(A}MfL-(PZyZzmnJ9GbWn9*@ybN1fPcRg!;Rgp*q;=?luNpe+)TE8`0w)UdzfxrqwjWZ|-2@;cm7zzlvz@;$3sMM=N zupZA6cXf4b;ZLvwbizBpCTAqeM7j8%QH|*vV6!qTe45IiaP-girx;yS;3jZF@pR&# ztxW)GZ|~bA(}4;rw)|~WWH9m(pvX&h_!8J1R1BE-?o~|1z4M!#K=CYJ09C2QusJov zPwYH%`of*e+Sj-Bh;aw5Nvei%`?So8{8E8zppTu6ZOj9Jn{I(``hgoEe6o$m-p_E1 zFATv|6an+j*S9M+4FOXGWV4n&-`;oj_x8JTYlVmZn(^m~M&QOi!sx7oQcX>GL0X?% z9%}({!I0fB(r_#k=5JWx4g5}{S)SiP^t;#keSTN`bMxlFJ(c%_o05i;d$o6HB1V!6 zhQs+5!!e+c-*NRNcqQn;R@cozZnH6G>h}CvQY%{9j9H=Jbo@m3X`|@2Eba44K*ii{ z?b+Bew9jD+%sV#_SyK4l$UHRmMr`4pRQM$!X$^Q^T<5lSzP^wG@+=kr>fYIjKHV0M zcoSqX+Q||22tV1ErCh2P`1!T#GoltG1C4;a4J$l9yZEr@hb4>wLRTk4cNthrx5uz)r zw^=9gS+0ARJ)+HrVJl17=xyeI2jev}@xhHTL!_!MAH30u`%i$lrfJx0u(;#-*qQ`w zZdPVatP&`Ov{wCr8dXA{(ibuwb!pRws(OChF#cWL>t+y3BZc4nr(r$VHt9zzeJp`@~=L+Elexk zBY{|lV=fg8rStyyoBaMR=P2dacET$qEgh{L^H(2j*!4+V)Qd$Z7s?43M|MQ}P!cf) z*qF(J^aj{Y_~VOb{6z=utMuyV59ccO1Y@MsT_Dz4>Fh@5qaP;nOgaO%a@t7lh6YYz z>2chuk6HScXQa=ygu*F1<7#1w#F-<}gdchNZL<5Q3`AJb+UGF-h*@-_B0A9GkS~l8 z?F}^Aw9qjG%F(iK3y1BXO&s+XP8xmndKU;cX2kw*b8>Xi!Yh2g{oVZT>Oxe-|0UP= zkO^2gfp;#Ql(YDQ{|)x#<>g>!0m@auK{q##)77ONH?unn*Z|PR`+NN%6~4*lg0$bj zL7A;%zT@@94@TaXYFxdFqE;w_4m^wccibG3FR>KH!#h9h`prHw;yP^|`0l356OxW) zKpZXY>s_khjdL&)aw7}&j@m&G2pE%6)PL%6QpA_BbjLi=F()e*-K1k;%~`LxZ>Qng zL}*r|s_TcITXPACx70K=9J-X=)0Gc>^0;5fMiCh^6!ME&!%n%uo6Vu#Dt5;Ni*T0&-eIj^H!n{kWTj6}pWkgCAJb znzXeR{|I-Dhabre#bb&MDCdf@?GCrAx5=S^UB3Xyr2OcyJTtSy>_ z#u$(SLMm1IMCF^k*pXv%xFz zam=PXynHJGlOIke)yC=o@D&9sF~Fnt2LliqAPOw6cY(@GU1v$p?vRiJz1Sv-Wa*E$ zeOqSTGeFnzzYXe++qE;xE-)|!$=`1d@Q?;P`2$maRWl|#Ey_p;)D)Wp4=)TrVw30O zP)tme4F}0c%&tNgajPe zYccT$yg2UTcN2&U*aUURa|Z6cs2m;ts?6defnE zFO9l68Lq|T)St+OOA-*hZ9yPEDC=P_7!&tp@57Ia{tZoV*B@*jBUVjbugo%y4~Jo zwHMWRCsNLzroBpH-RQ!nn)kcICm0w?SM!44uj{&=7IeA z!bhXq0;j|k$U%M=>4YfMo+^t6UfY5y;q)HWP4wxTQJ=18oUVyoTvR0 zvSP;zM#~RGf>wlV0_{r7^*}#_#>$7c8}`Cczw-s{AN&}8e>{na#`Nfa!X$*8M{D4% zqonrZwm)nnwzaKASopp4+I!aye{AKE8ygmnmpPg0ymh!cja8{V)1|%=4JP8; zHd|dUc%zi%IYiezQA=7fvJdQhYkAQQl(&Q2a zg%5*V`uX(mgZY4oWS{IPmLm0Oig^{4;3pA?xJ2cox$MH3O7re} z_r_V+EUq{Leuq7jtt9X6R_zsi<5B`EkdG2o4r`oRoM^NBn|dX999?WPbxHAjuvvb8 zy@Lzr9*O}eR^q!L|RQMlDDno)E|96m%KjT)eXxap_e7-sw5@1x`6? z$4l*xDOKM`S}-_LFL#!Vyo}j)JldT*d_In5qIay^&DX2y#W)=vV-S@q3Cj3cC>1)_ z$6iIGg1%pV=J#gK?9GG2ev>o#J9RSUmlE6G_cJ!TZ}VPxm)B%qOJ?~Sgv~n~=8HU3WG!A;-UR)jOfPv-up!1_oYJgu`58ud3V{3?CrRNDKo2tt`hoe zId*&@A_k2Z2jQ1Ggi3>kPu_A!`VEd4Dwz202x3%*<+O}~9Ktuf7B+Jr;$O2B5&OT< zxm@o;%diQN8|LB0GI=7$9i{SXlTu10xOM^tq0q^44~>rwOB7lKp=u4rz(L_WAS${t z1mk1{teq4MlCT(R`k8Vh+0uZ;{p2Fr+0eO+_(RH-%?vv3fR_f{;b`eUPkLQ#U>6Y3 zi|PhazevfRB50x=%DPDr5fQEaw6FMpgn-NTRYy>Qe&ca9r2+7+DB4(LV29()L6N!8Y z0aOC3i5EC;i$l$i_KW|~P>^F3>%*ypTL0XGWjb~C0CMktLeT|&r>C((erYNbpbfJpdb6b3A!&Y}zsp%R59!3r8}f{VSKACdiz%!GJVUzg=mVqYalj_B`bEC1F{ zcV$$wye1TF3yLqwLF6lmSrMIteADM6AKr`cVLF_Vh3&~=_On0-Hp8Y6Q8$9%DM8oF z9Y&XgZ}$Tm4A{2x*+wU1lqx+lUWRdrJkJ86>}TN<(c1{c0@|@y_>TK#m(KE)E&tPI ze?~%0cj_(ZL(OZ*WLne2Xf%I%dT!EfgLq}wkB`^4z-uc7-NG?)V{ApMz4pw8E2;_& ztNd7tors46>u&`WBA~!PM+=l(*Ux?4cI(tq-X<@3dibfNzemcXTp zAG!+TE=);QQ6a!g)_I5=LB~gF(Bw!ryv#Su{szU)yEgJ$bF6}l7(0+g(u z{NH~mgp3IAVDyB3t7!>vOg9XqaLxvwUj~odt`cIf=1e@#rQI1vU?~kX1 zCVM5>m%VICr@{QwA3iL7@6|`jArry~@b&CF$!=4l7?V>L{k))Lg?;M(!^3_z^d8ma z;ym!_tS`OB>c~?nJTgzFQqY_S3P>t2u;kco^A6U zf2{3d8na#)V_qS7?u!U^F6C$lCQ#26PG|Neds(y=0kl3y>az;+Mt1gjR>blxH?1S# zf~KaMV*C!@>la4LT#_16pY(Ay=35sP1PyIqk+jQWDfda>gpCA=g>iH|Ak9CKIiBM! zWB`#J0X}4!9HPXe41UWj`G}?<{!?;cye6IsHiRg>c5^NQ)@O25(R(Zk6v0dR9BujNB%ZJXV75MUeM>Pof&1Ad+AMoJI~MqMw{1=zRM< z&S2NL$A-l+Vs6X3{F|7g#uynNB#Al2Fgqjaa;(uT0bCaCKc4_0a>V~!-qA3SHZ+!U z(f;Gr!OWgXM9B7hB#@8_7nUH$90V`)>>sAf0>ltzxQ=HygHrX$!Z+XwEJ4$bc|&?R~4&Ov0&OAd0FFmLV^=b5Edb8`e5K>_D^3s7@(9R z^6Jr1OyByRDBAzaWPal1LZ3=Ydlwm+aJc?%*tb==d>|1pl& z+BT{5#mD=}s$1`z&pGVs`nNV<&yh^f8%cg>h&E%iOuhJ*9L?oJeYv;e^aq>C8mx9&!r5Jr!T~PpaiKe(Ic9s^R~B2%Y8k5?*@9aJ|5k6Vz~^fnKqf}$5JkvRkng!D zHZi+iZqR+W)&Z*CBy4o~U-E=YOX-tslzSgc0;#YwB&e(Z>j(_|F;9t&g%@`KgxAKi zg<|XdZ+6nrxfGA^T(4f7gD4y~3HW>V;628Df$xCW`GpRB5!)U(s%*Rox*(VjPF77Y>Y{ z1QjZ;`7SEBbl&$LxwFsHe4xR^#G;{<<_e3MPvjU3 z*3f$5vR$Wd{XF13A))d2*_|RqHPpTj7DYRQB*tY@A>$H9>Az^=Wn%AogleFsD6JlC za1{D`w*%!c#G04t0Gs6kd}@ie%{!6fM)dY5JRp(l#d=4!zifh6hfDA+Oarx^Ju;eA zGh|xk5`DebElYZw-vwFZ#3HH#A5fo9Nu$Qem-D4#34k`XVC4$@4P^?UW>O&x+B~lf z<4H!cN!5UhqvbQTJf!~fNJ^=^CXyg0{lt?;l>&q6byd7#XsOoGRILtW*d2)!@zc$b zEx&M~vq7w{x#PeGSR`zqvAwx)!RUN`sGErczXRdEWmoq0ICWXtU63{<22SiL=;hve z3e0KIkCt8yXt#Qsjw!-OD5w&=t9Ml1EeYYYq{`{kpely+6pE(c-nS{?I(qkQ86)H4 z1Z+7#Mx4;a2-%-v(c2ib~{@dfJNRkXmlp*^zM0(4hUT zJIIO5fGe+=O>pGMA16~NaVylO7SN%8*T;|}p%#d7I9q{E6YCsnTRV< z!_llH39{Cy?P~6m(ApGv-NsM4t^Y>ek}ujU9<{lN0-Wy_ zk+T%|saol)^sjMk=gNX7_MdEiU?}Cj&=+j|-fZ`>j=fvG+V@4A=5~bvwIPrRF+SwZ z4_^;-Yme=i>mtveUg@FV>2lRTu)$KhquY%I3)9y}XOq|9#X2W35jV`J0AQb!z&LE7 zZ`+Es7T(uKn^bDn72pL;#-_ohz>dBeWo6ZSLXJQu4=F8=<9q5rM!Y@i#;gdGk;|hj zQO=HGp0fkPD0wJgA8{)sMWhJTPIvb^7<@>Od4EGMz3%`OD7u`N4OJyq5dlKT4_IV{;>w8M=mGE#nTzv4ppZn?^JN`O@OTV=z)=1rI zD{@!y`R=JO{O%5_!s&+qs3#vg^jj!=L)U$d!PG9~T-rFv==(1ta*LrlB})CV9Kwm! zL5rWL7nJXJl9SmO2RXjg73WZKDSd_5&5$E5e1#8(|AD#0?cEN@-RfqGAfGz#ijlRz zW~-ziU`l?hssz?PU-E(dCV&~}M|jtC<+4zq=y94RPQd<@**DThMj4sD@b=VoFX7cZ zq35r&<=*LUFUObhJ$}}K?rX}XnkGPOO$}iQWtiRbCOQq7fVo!C^(ela+(0jRfcP#| zFGpCPdC#rbpWdf?O0b;7>5WX(^K>zSVC+{)jZ$}}6DT;Z>(h41qCjd!R6eFCV{6~KBDlJ25UH)0h>A8vWKb0Y z896w;H27Ue{1!%JXV5pf#7_`BxKsI#HQh?jsTj9gk=w3;1w_fyPD&=Ga?YQz`P8tH z(9ojd9C6b;F%Zb#v{8SuR?=_kVX4)_4v$bsnzLz|5nsW3-<_IGygOLpze|D5znE8; zQn#lf=QAo|<_o7HNLSY~no3s3OAU%hwLX`-R03RYmMojo>3Qi%fN1)_BqVWbZlT1i zZ<+%;i2Dsa4#5-k=23M=p0Ho^{hqGP)&4s9=U=?Cmfrl!Ui(pV9wON0yif^?7uw8>`8lG&jJ2BilM$HDyC#2q#oGSHjFomBNfZ7{&KKin*sEk-z zlCo`zG>_1ES&wrqaTY`{Jb5T^ksR!7N5Sj0f63-L9fF+0Hmi0qxaLB=Fgs^LA(~cUnYD>-NdpYDcF8PMkdnVFZ_|J^UQ|LHV@>%?}Q(kJ5o8$ zT~mOrz-~6$odkz`0B?8QA8taJ2W4F!@h$alv+3+vR@_UvmFic?<~Bc4T{i93 z(#kURP62vlB22LJwJ0g9y7+_ZY*a%2au5FvEB)~A4_eyP~DuZg|Zv9p%gBz znL~TUx8ykVWhTJ*VbVN>b2dronJn`^-|FwN`_xV)7tHn2xPUcHX=532`W? z`IdHNP_CL9<;I7C^F{c;>#GDDoGA^=Yim(0J@L{&KshO#;lUGmBLqy%s71@cD2CAf zCi7)EZcV5W+BY;_?Z3()Nd!yDLv!f`YiPLopUw{|zF;)szO1)~hOH*rEcY%QmTNLi zmUysSovdKe$Ru>xRV(4YIP%9cE2xyNEJM4^dR)%W%~~<>kuWg2jN1KT54@XMY%2xP zrCJpdv6g;IqxLdn^_Nlxg*CdPMZjY-M705JaqRKh6_JTegub_j(SZSv>MfmNC1d)9 z;_ZU8qop$**ilU-Dy3_$q}^S8HndZG&A<6<;k~=06vhL_kk#bU8lnR$%8?caZwgY^ zzTV$C>%?GzQ6et|nF@~b)RHJ}dwrw2E!h!8F%qXq@Caf(D>G+m@907`R4;gnuCJt5 zRvZUJyf#8}L|O|2Pe|O^7`xcU=Z<(JVoN;CN-ulYDk@d;eKJ>iTh3PY*z0Z7DUA`( zr`2anSsST^_z?T(QU6Y-UPS}H$_%h)LyC)!ex2g8kheMZd?_=bQT~CN*m{Ud#N4XtXRC|VN}Xy* zxShwHVn7inCw8-FK}Un@Dt#D1X?Q%4JI!k|*|o#cq6QJS^2QLP0GYzJ`gERP^-|PK zV$F}IfONv4pSK$NT=TVCBE0h}WGYxs$%MU!(hsk?^9dy+WP)PtwhLmR%juLrGbOJPrBzEN9anPz#+xdlPs z-*;mEmplmQWbcagV@(#GL%cSA#&hB3MC6uIz#)o#<13l?REW^P>3RI_ijos6b;}%U zlki(BZH({g60QlmQAC&pB5bsdV^eDDtCGJhYZ`|^1UzLx2zV7Oj#!9dLiiR$wB&mJ zjQP#pNy+bojg0ZBO4d>yUQnI@SMxtrm-;h7BWt8 zV!mXX_Wn{ECa=>AD!Gt*?}d|go=Z=JoSZ^GM@1mc!6vrm2<~Y_V7|E3Y7OLAp4!)` z5&5=SQCzte9s$R&_ym=&c+5UfzKbnPCPr}BAW^R}bgIjQg}(H)dh;qf(~)j%q;F}+ z`|EH8f9fyP_DBle3gQl`S|To6yh9y~$R*dy&!nyuU=I16JZ_vJ_)YAYJHOC&zuOp0 zhl$Fo#cyV6gBt{{w@gf&j`nv3MOj&7Q^PAwP9_3naZ;{uU278e;Ut&|H2M`~IaU_G z)&N#x%r73hRtaN@cE6jD?#m4tWT7&Wy=2)6Edb&9?bFooGJq7H;`wes?jH~!_iY|$ z0231#pRJ-&zp4M_`4QOJBm_R6Mu?qQH)N=ldXn3C2!=&Qc#(*B{X4 zMZ?MY-_#TrIPvRhjVQ6BR*m%;#rED#GUu@hH4|Ajun@FXpQ*jC?(Frq2bMfN(P1-- zwGOBhLC0M6G-_AFvkUScZPg0D7`;Qhr-^vS-cxCXF3+lW;6s2-5M!g@H&ZfJW3i`f zqp6_DAU=L=HKyMX4jW|KUR_@OZDj{*^O>QaBn9XOMt(Z08?6$%B!u z8C-@*51p4nC6nPiZpY>$n;+uwfvN51iZRH>`uz|aWqiV(8_d>UPtd61K5J->D_NUX5Z52%Jq4;RPHYDe0*ztU0 zFq$i7f`rD)hJZ%&ULS)_2V=s4|71?*eDK`t{07$Z=(aJ?%s?so9I>8k417cA`$VeC;IwB zw%rdP^GG$RM>xS_P-0=dhlilY{O_{bH)HJh82cn)2O={huPPY=%JSF6q%YY;R5Rig zno-!2sFj|0DTkM|Dk=@?N;Xa<=&bLpSTc)*nGy*kzFsRh>NOu(o0{h7{)MVZ-=82i zVO@Osz>1=$+uT%#wQbg03^dKtrm%z_P57O*$i|2deU5#+Od07W|7GwTV~=Gd-oQ-v z*2n#`{)N+xR{|;6G0l%iZ@d zAm&UhEd2CWABisI6(%Gn z0HjUTIF8UZn0B4Nc=nY1@|M#3B1_sm%`A<#l7yBRcAxP;)|%pV#v^6 zA2PuvF-(Wk9E2U$&FJbT$CU`*20Kal$Fs#&c!%XiHwU<`X6h%R#FPKcEc8wXny7 zccJHr+;pZST}+2&VFT3p=Zuz*)c?ffvlGSt`!TCzuo8T1h0SRsV6$PRMxtF=w_;tv z?QP+Au`zL78C({5Gk&JiZWCL<;-RXgjv_jPbxX}(aXapw+=yahR7*WSD`niFX}{E? zumnAcM+vK_!09*IBy>EVVnaHg9_;Y8_ss8VWNYjuj9D|p2&RPf@Wt{LOd0N|``Np?Y%I9+n43*i(KqXde|@r;z| zQZnH?PGe(Zr)tb3iO>+z&vyQ0cO|IcA!B2yDB!RnOX2%P=;YCC#^tFH5_&w=3k_uu z+3nR)JPFUC`9s{#G9!_+r+(A@8Z)Bn#X?KfdMt>7hUG`dJ>g5_cvFLT2%u+=PgV0w7dLlH>S-s;uY4u{ZU z{uKEZ$JCSii?_OEgaMC_F)Ig$%D#Nra(OGo^Q;EZ^`P^+urQ0OGFjM8`iRt`Ft;U{ zr(cU_p@}eP;cS=@rSCVn zAqfch&kxQT{^b3(EhOTDg>gB6%Xw;QikbqD1HPG<($dq%Ob7vRU7R60;12)arTJO6 zm#3h_=t;?+uoz`3d^hO~5xa;U08V>qWomW2X#t%$La+LX3nlI0ki>*8C{1Lc3-k-M z;nz_DmK+*!B4V6xE-KG6%lu9+{)?mZ!Y{pyFuzww;oU}2v~I_-o&QardQk~26&nj; zLxooVWMuXL$5=t4SoiK{QhBTL$zly5_v2;M@40>$>+8l_ei?iwxiQVdT3oiP{KEJ9 z&Uc8B7Ng&3&f)vo?eF>SJl062T77gTDE24I>saP1p#C9A@ypNU1Q~MZwY3J=E=jc} z0;=KvVd5iulwvKX+DH5jQMRxxl_(?2lgaDWqu^~d_AJ$9KE5_9_A6A+Bk9(p@IN9t zcq@?Qh<${wh5xG@?U(G4VP_S&4h>SZrMa*59OH{2g}a;P72&&-i(o%`qm$k%z(>Rk zU_aMa4Wr+)GBShrr&PAq^gh%+Zn})-3X#SV1X4G~6AKYEc^xBle*4e=3O3N( zIkVCAu<_bOm>ZDrDTs13^Oj1Jq!J0b>+S!xq>~FBIuIWmq{{fWZW!mG2mef2rUrn0MXmiu$UC@t4U|ert*>%3{I<@&pxh0Dwb{-*uaR zdLpZP*(9)UN)j>Y$B&o~O!DTL+SNT9292hB3FOTcd)X3bDC`$Gf8+}zGK9R?e>K{~ zH(8I%YzO#da8kIr9>mE5kr^|y=ic>O zZ0JBDbCF$y!O$>@2{zenKK|R7vs8n39k0(<@26Be3YNt8?zR$oTwJ{EQm2v9S2+jm z)BufQ8c69CdTWe2nYRn0N5xiz@l@zUJHX$E6A62NBT_0+*$4zDV)wVBI?}PMC4uWYets_TInytzH-#c-y1k?PCMkqg0Bhg{Kf?D*=6kM-_Vrd$+Hx z!^j}o(IKZO0+%_*8n|-8>ksSGVH}+R1Fpol_4Y@UU?28VcaN5KIPE{W!4luma|17G z&)eq97N?6DqC!{;Vs^8%UzXTR%u`=AySQD_5jj?)u2J+Uj`=r0jPb)eCfau~oBU@@ zDJ0u}Y9=Wk-QG}d41OAoQ>uh4mo1Bdv(d-pEN`CpQ&h4;LTpW@@!TVfW`MQyb6wk}1F)t_NE zK|vMRkJFXrHTmjiWE{v_vax-bXoFR0L0R$m>3s_`$)t{ zG;w=Fx}q$B&?k`Lowtzevn92JB-b$na!%cuv5%yb$r~@F%L%yr0nx*c>OChG&@J5? z_w$2VsbfH`$_lRofa$zbM>d@~KA&IzF_S;7?xrtlA)<73aj166MvA>Z~6rGbqFH}}1C zZR^7P0FbaqHroSj$(@gdCwhFv%mGAvf_~kz9427KRTj~g5lJ(!W#$$TQ?AM=ot5J* z9NG=1Pw{)XMf&dOS`eqmiZJ3=j$4*k;Cc(hbrbfUUlen>|Iw8r`?%gRD@ui0ZSc^* zwR}+AiWVP5ploz(Y;IK2n~v3}gKRyA0s!g9*vBgVP1$q0_5NXmtujI)@lLup>q>^I75(|pn%?zQC%#~W;MX!BM5g+ zs+O-c51VM`3hAX^_C~<`1Y*1G{(m^V>8MAX4f>%)g^x8^hv!b6j~KZe_e7MV*^xUV zI;MRW0Gh&==OTtpHC3)Vs8zF-F3r!V+v!GXt68`|+*>>;1mu=r0k}jp`S2+vS|x zoTzf(2|bY(m>5dSE%xY5A$qs3fhUAlrq#(*;GgRL)Y;ecXo!#!3jnf!Q&JY9N+=}l zks#OrehIKQV@wENqM@Lm7#jRbXt+vGT5OeCe2FtMdXEt6Q-a19y{b`x3i}X_yMbd& z&k7zbF`soX8+4Vo))oq<%=V0-#jPF)U}!^!0C z!P^Zdd{Eua9cgoDgq#`8-y2QTfFIFUw$|p&_8z&S*eUO_*PzX4fFB7sprK}2kM?)( ze>S4)>*p625xKb>^|m?Ibw4%>KK^D}oiZ{=a#$$*TBuLmQB;U{zOqZc@3|qFaX+8= zW4@dyFeqs61+Qt&ywKZYv1Lcaa`$JRXG4Kqy*@43dMFKYInw!c#F#C9rCH`eW&cle z^nNj+pJ6*k0IFN+sQ=oGCv%gWECQjSVr$~s?oD>S6p11`kOlkqq3Pa4{GFao2=I(z z0Sh4#z;5$$_!qPsvXY#4*Y6;&d*w8LnWy%zU%TVDg``n~+H2*)+DMKvJNqs zRnH|w;vl@FC4}j#%`?RXS`r1W`23K{C!nYgfc#14vU)JgILHI2dk4smu*E1@=Z}^D z1wB2n83%gsY8Hp%R@%(k+Uq@&1N#@MIBockBy1?$6Ps{2QBU#Dp_mxmbsS1Pup*^> zk7XRSK^JF5XyeHW(0WTgWfmx*M35P4MxA`Ha{xzPEUrS`U^JkN1e@=8_zA35x`6!V53PLL;lLS|aWU1ex!-89@dhm9gDi}A8~A@0t<;D~jEJ*^>O+Se zgU7053ctiBWxHlgr%OT@)20iw)c^Gc0w-vUK-EFD-m19io&Jq!0(^W3KwIk14SG4a z_+Qk~O7d5s9~+x;A|`lhDwzLbTPz_cm--cBPOi(yzvD3$Ok=s?;0arM-vk$vTskx{ zGeU}?!5jE!fvR`M%>PGv@nS*yc)10giTIwA=!zfYC6qvK`F?SO^yh)KGwaJcygh%F z@r@I!l5Fv(p?3=GB2Es{-e)I#u*dnr<~oz0)rDK-qJcB;NHr)yI94*u`fkad3g*Qv zO033}Jo$Cje7GoTk-3bFI80@4yw{o~P)b|5TkP$Rnx#KT(Pc5Mso?XKRui*E7HO3< z@F=bQI%#5>!%A14R*(37{df1T(;$Hh4Jho!z-cr6vqAX2JAW3uU9syqW=vS;a-2n} zMxJ`vgkV~c{Xab)C7euicQ*nt6@vNVqxzHAVL56iT=XeD3ti0g`}gm)P8*_A3WJzV z{ae4F&VWA++>#tR{3D(bcqb_#7YGCYg*6po>OEi6RUY?KPJ9So?DXc?3c8_4jVNny z-SB;Ob0bz;3ezQ?vGf~uC)J-pn-7aUo8cZCV*?>OLY#K<21w`XG1$N((id^V>2vq0 zfDG<_Xn&H%L#P=>}3qz($lDQe}2Pi03)7OIqokirO%(R z`0lJzx~nT1M4bwSoNT z)X2uuQrwa^Ee=gP;k)-fyZjRWcliu!cFgF?r!V9d z##!-S>-O@`yA=EzAhZ%9F@QZTk}2`JmnKm(`sP+RA^BOzbV}$$%`tVz`I^^y?+uU1 zW;Puur`vpH6xw;1QMpXuuslE|#S_E`B7hU*30?w+sKU2#;ZA&iA7{snR7lwh*GG#) zOb}j=FVyr3#J=tcf=)!^T)uC7%48PxcHc%q^L-F;FDvQqb%CluZ>WewYH;aAxyZO2 z5TQdr#!58yq)&6Zq)idFB5+uRut0C?@8DHLqaOT)~p@B?-of{5!J zUudD{ zFiV?{!aE3JI>)q02hHY`w;d^$R0jVv#U%;v%kO|H*~+ios~uSp-nRGx$lP~;vrd|Y zDxInmesAdTXttcDgANT7(<{8Aku8k%Mfh4lSaSk(e8FQMo@$v5RQx`KTagG(tjAZdalPe+%KxCkyx4Rb;d^&>=nK=N*X*22oHOkU5Jk%KO>!hwGcaN{q>Lp1 zJBjs%;0r101KDUK6BCY6->+{Q8SuJ{GvWt~v!dl_&8A1{u!**%58}Y}I=PYQ%OM|#!+X2QFpfv7`mL4D0bPLOEp(k&oEB~9$~;6q%Zl$)6TCE#0x7P!wa%H2is zD@XqoVXkzQ$!8GnS3EpVmK0oQ{uR!+hD&@`B#@r|heBYp;oTh0C$6MtoN-!`nrWxwl<=5J%fbzh!(UpU_9 zW+IJ^SCm8%5%@Lzh3=uy6|RRNWFnpbD#6)kiP|B5#RbVHuukK@ml_H@YFb7H5zuL? z==GDGmR5?tDZdBPqhc-wAX-39nl?lg(Tv>&-`bMxg$vwDZ4Eq1hUX!{X3S69A!R%A ziS)}=?`fjjw4w#t2G4y<4EWbi4rGs%Llvq1Lb3>B(ODa(!zhWR6?m)duR>o~1Tb*& z?DGz0ZD&zmtu=-J7gE;McOF0p!f`vIyy_WQYsWe$boEsaz~3tc&yb723>j2}{Squ* z7yNw9@7qO@xEaG8LF(n^O)I9gx{7nw!Ud(|Kv$WRr~X^l6NK5gSScGzR_1W;t*&(% zo7n<%&_@jGRGWq;6UquQ{zvbkpbW!^st(rzUdH;=KLXW2^` zb(<`+4jbgx6*AG*(OqVd`gWh7rMv zvF-TN>=o5@2hPmT=G?xh(Zj<{y6m{{ypau{pGqM%3ED|fQItNEfLPmX}vrMITVB9tN`Fri{ti-{Xwg)68~r6*Nm4&E=eqwu z%j<;k;OmSjf&cWm&E0*`MURQYoH6m~J&}5U+=$;DcBsyx&2H`pNz`n;$oa2vpaly~ zr&!kf9BLV3`1ku!_dX}0%zoQLawL63Hj42vS{n)#HTEtJWq4T_|3*0O!qLFDIkb!L zE`{i$Nw_v7S)ixjcfse1l1<}G)_q-FeK|v|%9VH7LhhgTR)o4`RSwks@-_CxZC3I> z=<`*bYgSL`rG%J)9s6nIY!SU~AFPbtV< z9(v7avxa)f0}waMlY!O&p-9 zQnnBlIUt@*K#!0(3Fd9`kkI`mzCCoTTDWHvFxjH0Q~w@&xYF;BSH*mJqTn2h+FB(z zDH%Fh_0<7zWlN~r@}7dcIAz2{(GVKmVq_vmVp0UFIHOUAwln1#4(c$suwVcr0N@qd z7N@{02sGXG{bLP?aGHVNxgRf1HSBB{&afBKJ}uAP_AO*ToqtY&f8Lt4xmIJIPmBU* zI}KPkr>k0);Df(RLi^Hkyf-U8c>guvr`99@wF;sx>9ci!Mle&26J?W|-t4|g=MNn* zz}C+dY|0u;ADr|Sz58RjJwWHr-4enZLTHFyMbK-YsMep62=YBKU-xlBt|C2@5d|P2 zyi=n9CC63SNsv`6RzR=&Q7avn29yRQIGyTGz zQ};9Pw9Ts4Y;b8hy~V|{{T~s-C`x6bsp8f*qO>n)g4`^Fx~@MzFOZ8m>QxDwcAt6@ zN|L3&{8*^1{^6F$G2M46{MF5=6GtIEQ7ES(`u{NX7F<#N-~YCtbPwI#-Q68Zhe(%n z$I#NlEvslG-WO zW>gd(S$zvICT>CZFfQfwO-N$=C0eX>GG>H_c)X+gbk6n0F*SGbwyL`L?*b&%T^_4@VgM9ShW6!MKah+;`=1;yJtVofjta| zND}Ou|8@EPvw*{{jKcupcSWw_w0l(dAlC z7;{?jD|lC5=|CYo>HO)r|D7J z{I@tVL+qfPfi4hhaZ@3nKedSUJG^~HC^~zPy4pM?f_N{`Q8aA{vp{axj&EKzLC-zG zYF^{cBae{0c=^dX|?}FTyxnJwGRzcD-wb(xE zm)HszlycXt?~P0?R1X6iWN=>-Yz7|%W)-c&Q`<2FCmb{bF5gpABq^{b*?_Mwy{|V<&ohf zmA~lO8gNTy-I%aopWDjF=46}--StLFpwnf~;IDK$*x$3cDS&<>D%+#W5}_~WZ8@IhdQ zu!a~7tPgKTL4&in`gSk)xBLoZaTllWeUplKzlDhrTxM1KXJnQ4(7#)zUeC1AU|ndhAkYyvUV6hV2*W~i&@}@|v35$Xq0)6< z`KyBVgGgT;S=B@IL?lcu1f%-~!Bu30s{7I=O<8XxEnzs4)ahw8n1t+~k)Q+C)jbb< zbF2E9dwf&z-1YJFgBpmU~sAj#fQE0oqNX-85y2oO~Z6dP8 zAn%U}eO4lwj)MDC^S_bCr|-2^ef-|*xB2@`meRIdMA$?`20u*us;ZD-h2szuJUm)f z3HwLg|BA^neD0R+81BwWJbYy36T8_n3rAM(L}GX$5M)05EPwCC5CxX_;;m0~8J2Ft$LDQq9pNV(DB%~Qo4DfwY!(v#FrSCDE_Jwco?4(gwZ(y)mi!TtUUco2v8pWxG&-dM?*x_O~|f`%4HAvwz!xmS+{t5jH9`Y*M~*A+xW zv2FoUM3%JrB{3Xu^}u&r`McZ;9m&}@k|H{`$(R)0q?sqIQYoxXi%6qIZNKB!GHs}L zgEP2xYZeVM%>etm=(}}T8PXj|B$bPC=fj#Wr%E9T+!VIBrJ#^S`8)n%q5?$z)8Z?l z1HT6w^}dy}9&4=+VUy7guougEF}H04f9b}M&O5(v$sZkMdzHjJb$(^gUNJFoE%>$- zA?52^_7Srt5u4Lcf6N&2jK2Ib>^Mx=eR;6{xAA7d7LS2oYc)Z5yduCGQ`d6rSHK<6 zVZObd(bIE!Gl>AAk%=*5Ysx>Wolw6JN`~7fZum~LyuVh=H2#yD264=ef)pkTZHmzs z4&$yO`=xI}0e4&gTwHnjIg8}cg6;?|2?-NNQTLr_TjArSnk<;YcEU@FDBTe|6Peer zIoT2nIy!EsP{ez+0h!;$j0UaKrzhBB4%fwM)4Z$6U+tu+o$j6n4Qk0rVipp5UDX3kr;mu^KhQa6VkV4zQ8E09c{!j? zyWRM7imi5x9p8o}Pc>dN1c6Hd79}z&d`FQ&0>qaMYRJLne!oI2R1!Is0-+~i3&6$- zIH!`fc?Vr!PbVdfGBlxw{Y{XLM4Dg3_WtAWd}%DSmLU2r>qTbLG#o(Rk zK6ZdSXty9^7ZD^MYO@*wf8}^UJK`0_^wu6I1fA%Uxw0epBFb_&AD5~65Lv_fE=P2WU9$;TQH~`+LY81x$LtI+Lv&&H z)Bgqb0DNa*$5K7R*3Qi>pfox zG`E$9_|HTe#5)`(bRT9*Vqhv(6?|Ypn>~6ncYm^5ETavu;XgE5Q}d2LB8#1|+*a~@ zFk5XjI4--ASo@7N6*X#R ztdwM3-X*UXtKR$fMQ~EM;D!foL$E8nx2idc?>rpY#sqFho1-kC=qL)@aS!b1n+=Y% zP%V)|XT<$JlLo|98K%y1Fu~wN8l&afcPHpo8PZ2$sI8tzJ|^$ySv+=XK+MQQn8~VKj`KUd{je7j(ND|;YFcrZoj6WKE z0h}*k4m``1b8Pb8I@*wk_Pu`>K-=5&oWdp6BZSZ|u8ShOAsM3XLtrr(=5}!60`gdk zzw6O|^Q}J!J<`?Xn1UeI#s#!(n^HRci#?@o%9h%yL)6B#2`lO!4uWC405+t{ibgv0QGY7ll#Am+jo#Ev%~?1$NSa8?B6_ zZ!;@OgW@R$b@tBINn^0|>55LAaFAyvWgjD|q3BxMiNAvk++4UKgHqs2KO{6L(QQ1Q zy0NLYN>Q6|<-PZ9$DU;f5&c$x^gUmInsY6_TT*>24@Va}#q>jsdhN_mY40#Mk70zJe_T^l zu4IXcc%)GTNt9_-Vp6b3guPeTR&H8SH(}*=uV%)=v{Ce4VVNv=)J2~oDYrO+i;)ot z78a9}l9E!+hb0*gT*;>j#g^(m{nyp89;^!#E3t5O<(jtHhfb7(k&t|t10gYtWw3Z+ zJ$JyXGHu-CV4LT#eDfIqi{b%8u(GpjVJ7LjAF)H{_4 z{^GMr<>=VU!I+qg1$wP88i0iYz;O6C0>meF`%o_kd5LwD(Mzl_n`>a=Jk;OmBArqP z%|g2?heeeh!290fr#?AKUabt;j|9BJoV*0Z{hh{LWR%gXcndaht@tMLw+r3hD5ku3 zy57-kRck_?mp(&hC1`{EdU$MZ(eW^P*5l@ibc>kgBDaMJQc_Ak65NsMXlP3pOfZ(4 zhcO{><4KnLOIcDr`Ifzzi11F4Ea-Opphn7UrWo~SG|}>**AnjI0`GN zl0M{oIF#avk!8pj(c_4De@c^|=@5~@QSnBd9-zg9r9&4HF{@4sUuBdpUN0>Bj@?n` zgBVd7cOitS$W_N11y7MGgE`AZMW9Bo>)m^AmfuI|;8)}zx9LBpz{8Uhb{O_B!o#%P z1SZgBy}$Seew!K=L~RFL$gQuF97JMhQ|MF#zrGy&tWKNKhs86|!m;W6GsqkCtXk<* zI`&zI_xR_!tK7USPxLpDgeM{nUNiwWdj`df3l%#A^aL)gNnd|`B$InM+;VCunW+V4 zi^1~wnbJhJD&?qQxyf&Sr^JZ%3x(nFK(6s5#nKjlU|e!ZhHP`8#2Wnf+oZDe|j$bY3!E~I`{+2fKRD5HunQMZhq#n^Cv@30`O z6*qUggBE-$ek=`PY+s2)2S`RJkSerZgTS<%9a-T7abjVyayDJ~KJ6G%R?!4lKfEb_ zX?ueE+Bh(t`_u-1z879mExGdhs3~@$5}lL@gDH_H92H%-e(#BW`k^IDyr@m|At`ZE zN!=PKG#Y<8^SmIW5amzecKeYOGi`p`Rd?7E8SO{%%brk&S{-uYjm-Yn0$c52(so6M zrt@$d3X#~j284&XWpq=lcAlqDAu%yBO-)URsLWwmH*wyh->!OLefp73hBDnNJHP$U z0oYV!bY`e7)G=>we;<}&fzf-+zOce%44nkX+_v6ZZ6@ICr42$Z;ZLk9rtw8~ZK7FE zb0?o^K`&2hmTtUa?=658FFDuKqZeubbL++NwSs}U|rjx z#iQnAl_7J<7?c;pMaMRC^;8r+mX-^-aee@a!|e{lpaOx_duNLXjjj3fhcd$LH~0MC zAPJikaicBod!Y|s(etL8w#JEZG4q&4?)Y_6n6hPRasM?uUs9nr7L1E34QxP19l}dC2eF4A5;nFNWtiQ&X}(gY?%t z7FzoxZ9GxR_KWbNTG;QbO*tx_G85*Qf(x%P#zMWS?>Gt|6MVEGb7-UfBE#{|ho0yJ z$N$1jGC{IT)fzBm8Xjc;6t2~<4m$=$6a~0^ghiLW|9mkoyrE2sCqy}eH%58B(GA!AN<@kP^Vu-Fjimkdz5T8g`C07zV zm4LaYi{xxl$O(Q^D1%q@Br_bSsbxhM&%=6_Je492ob70+v$bs%iwY6pL=Y0)vy^9% zmJqVE$fHX-ATG#o>L19;i|3cf`Nm}<*Z}4-9w{f}kkZhQC+&n|F!sZ0(vO@`V|eCM zDHFj}^LZwmlX4mNdVF8JtimY3{d!$esJs@L%A`NR$@Fi~@ox&HL(va**VGF-Qr6~` zp6`I)aE7dBjA@z4uzDb>*7S*rWBroPv*p0fys63D@n67{ zb?@uoie=j5-blwn^-29vQO0+EKe>g0$2A=_%N&t3*Agp6dc`(xD=8x37fa0g+F`Frbu*Pl~LdlL|#@R;5%c->Vk2<%J8NdHf9A!X(o{qJtt8 zAaTGaiNiRpLKoiLVw$m=!v)JeJ;{AB;?M>F_Y0&=KX+9^saUVhc+zBr|{?siNUU@0FJ&RPCgJbSX zTL%?*ndvQCnWJYKrA%Q$5CI*!Ccg{TqDI*B`1BWe>KVhA*U+5xxW*-nRbe2^sCf67Heu^cNJT+(A~Zbx1JUcZM>q>vC| zgB||#QQ6S&D^cC_X3a5zEt3wqXi9X1Auf1v)yN14@=z>v z02b!N%v&tL;AVF;0l@_S{#R6)Fw~|XL*S4C1n$n-lRlt}1;xL3^cxb*{7b^{d?awY z(m!W@bu-1!<+$NS#>JAHYpGyc zh4ftlCyZb*5z_fs(&Xh&BBA1Hwz-|sal5^EXAfkn;E+rZ_K!95tsy9Qd7`s%W-pm$ zmZ#thXNqOT3vZ_>5v$k0HenrEnnYox52*hvGO3Nd!LrTS%wirPh-wXJ!8q|=9)}ow`|EZv^pVC$BGqA{ z4LiK+3jWGUT4{(?VC3!PWB7o6gVZ(?R=g=$QYi`6)6XT+*nRaPKXyvT04j8GLVEM_ zVeY#?e}*BJd#M!N@P(tx#@868pZRWN=MhrBS=o@)speFZ+Pe{BJNHQ=>=&SIrP;3P zeD*`}?^mT7CP}VddGJ3m=nRI8$UvnsGT`~Ldsz^8ziY{9VVek)h&gjfk4Llq3jAZT z&!;4P=i*Et4TOO{xs^Z}YHD+pncozfdGqc)KBtiC3yXB&jdaOr6*N-q2)8xZSUeoD zAS(uE#R~V^MAyswn+~idFZk-?ng%>drBBJavopI$-_2C9aFy+svZBe65ZEWx-y#DxldoTN?@??0=}`ZC&L8l&RVa=fOVs{IF#W*(R39dVVIv@a&HV-^jWoFV-3;(l&0Z8IDUx&1@gr!z4@)kMiz=Ag$q#qDQFA7zmLH=bW&WN@(Y?mV)3 z&$1vof1obc>Z%frlex9^-g@22u@rX_kqq>X%Y4v}jfg13pc~^Twf4>%NG2db7V{)S z8qXm55-VsS^+?s@ug{duKyeo~LV0o|tw1zKdwXGw)4`|^k&}xCl_^wW>z9Nt#7LAA zcgbk+|Ci^(exlj7NPLMVw5mPX*tC}esk?IneI>?f*+xV^#dFPnN9c~_cD9x{HU1hU z5ZBD@l8Zy~%^66N)C@`g&^6_?8{!Bf4*yF{%8NC&s;GU;33aBV2X|)JWk0Dh6qpu< zJZ9w&Mg?0J|H(_c<5-A)uXyPO%Bn8g(hr{$Q$-4H-K{g@h#BWYctm&f=! zs(Qge8h)%n1w>m|s1S7@V^C-@W@(x~HWh5O-FcR_VB<`KD&}cRRy8)YS%7m1+I8S3 zGTbCp=M+zXQ`K&?(&rcpD2%NWgD(xBfsNm%zIJt#RWqKt~ zBm6yMT;qK!%Bq!89c)6i8-?1$z8bEL;Jrh*23@l&;QhdCi0V_wy}f)#L-$d4MYIC& zOM)n*F6sh0!^|76K$>|v12R>%!QohjU_S;ou~PPr5L)W9%&97Z%1+M2u@i<{gIkB= zKf*YJm7A2HIOxPFOoTld4pBYkV1XqE*&t#+2V%%b4LK)A6M6NHfg_Z-hW=Y&>bro%KXpxjJo_D2^Vev4)C=ha`&}}EO0)($75sTFde86oY`#Is5 z2j0Z(MD^4Z9kns6Tgr;((Z16=AxyC1hng>=j>F<;A83vfaBc55P;?610R4GQ(Gyd` zY7lkt7fgqm3 zM@zwpGY)ybSo2Aw-A_7>RW)D>0d%PsJ2F~oj@yj*D1AFJqO*T`FyL9vHk!WtZ{qmi_l+(+^)`q75AelbC;p-@LgUObQEds~AX}YbO~+1{EH2 zjLm^&Ry=>l7AEG5-bVN(z*D|Djl?*T2Kf#UvLZcDW&JJB*{SSi3zUQZX|JH5fcWFb zczGqYC?lcw#jlY zPtS>IRiAoVh|)xfu>V%e9NF?Kv_qw&zL1<){xoQ~Hq#RLc}md9gX!w5zZm@LU5DM1fM6o+%<%Gb>9Vs& zTwLOJU2BZTa*!dQ_PqIy?Nd8Bg-HGsCG-dD`L9+&ZikmjuHip^AIG?jRrf3o)6%j2 z_khBVEu|OUm2u9gdC9`p;FwgM>DVv^9Tc5nZF&qf0VLF!fs3~ct%+LT;qHJ&w<^l- z630=J6c@vrtxsv09$o&$pRCy>Xs9Q~d{Rb3x*h&*!LYMDzq2aqI`HJjIa_NPc$?j0 zxolUMdZ162M6W;7qM^DLgLxnCJnu%9vX}cZv=A54FNHR`G_I_Tfg4K z@H}mJ+g3y1f%v#O=ei^$1t1Ihp=r=G6E<|vI{)KGc3LRR07@e~g)B)~7#t(RL7O&; z9ydCsnXaMX2r4721Xtr=NujZ(v$RF$E^bLkR3>SUEu44iSLd6r_yUQFmh9z>1Xrxx z7XO#VhCQA!JCb4j!Is4y^>?DMo3S*>8J@Tlk^y2n&^3O`S*MM>n|rcS=q$fx3y5wt zmVSlDP>}=HjA%)ltIIaiv}0AbWU=Yg)DCz)I)MJsWu$u^wpGBXyFZUpf4 zvtk5Uo<6pm|BBDKvz^caw0i?0=&-7eJD1$o#!R6cC^M^PJ#qcsUNq{*!v$oipaLUuhZSWYN8d&&%?xkv7s2TAaM=-($3xl1c6Wv z7<45e!$B}r$}?7`O##ir95cw^h2Ca1JH%%rKTeiWtB4kELQ_@KAPDvm@Go-xFKct! z*tktVZQ=(6M#Cu@JT+x+5m3XHVr3vVK2biL4UCR_pYcUqc|fr@Zic|43@GF0CedUE z_agHSL=i3D4G{z}{@IDYz7zdk_Vi!nY&9I4!zv9iuBeI?%>9>sQl~(sN`W6;O#n1` zZ}oEV_2Lj1w7GTsB(Hi}SdV=tgaUYobC?+$VO*yaZL4z6wZ{8~1mJeY=kxO)8A@vL zpqzO4k+Wyma3*mWyc6&a=>d|&KLveC7fkuwIs^hCZ3gimHS}In@Lbk%?U$QBIB;65 zh0rR!SB;o)R`j+J0RYrX=WRR7Jj~}Epo{ISjW0?4#TA!dGxNT<^cjUJMu~^gR(GcF z$xZtOqUybLW{uFs$T;9sh>2pV25%t0Zef=(9FKa;sSRYlMVU|Qz+=(Y;`o1VUwz{j zvy^t*1sbu7aax#ZFDp4S6R}PYtz|X-aItcGL6LIwsG&bGG!!0OSXh`uXc_=Rp2iP6 zp7mqpCUn!8C~7K2K|-uzm7ktn0&%+7W)-sLnz@1U67KOk?Zt0=l1NzP zz9w)>61t8pTwWTjpA+8)rY7jWFolk)xy5FVvW9!6{Be%ZH^UAP;~QsZ!!~BeKnx?= zl1H{pARtM4qZV}iemd~XxV45b)+H+#P1aMpN6>>rA)YLPc(H=&`p(%=G#lnOLhXC< zW*508RoUMY78AXmJ!mm1<(gz=db9cR8R&3(9 z;&spjRqlt&#U7>HJmCpDr!P>Vhy}x7^*@V3T8JsLyNLd zZkXtR30!NT)?vs8^0ndYLZm2_sO)(vt#-ZBrr7QC%HpN!#-QtV;O861=})ah!U&_| zQ)*%I@g^+HoK|-K3Xgy1dcEknyLFpkL{UrH0Mnn}p#IguPjHXVPW6Zm+hJn?!e0G)(%K3wT!)x+!wN@ z_6C=GXrfkP#c3eL$VP?ri{qJCq%By4n$(145th#_NeVfur&bN`Jxa57*E+`*}1eV4!cUf#PAEMLt<` z^~+=)Y|o}riHRhfs)Yzm)3M^B!uD7X%~9u=g2E4AKbhK*FSP9IN_6`DuP22@2Eehr zm^J*RxudLHG$Fko+r=gOM8&hSg)>t8DOqXzEf)IV$%)~B0;oMZj#-@Gb5EC^QnYfllv3g^q%3@HYNp;EGp4|ARjXnf=fG_gXGC9% z4M@<~xXq~Djcn+-K=r%+OK%uHOF*+sAOT3~@zuv*?~}A;Xxcw!A>JyMs!alX(ypC% z@KsYs(n51mnr@@+9p_NMyQZS0$tO#riuOsW=qJ4Z*9J3tBEd z@Ef$b(@T65qU8LL6PmV4ur3LNZIH8Qn&`Qc(MPqfI+gUS$|B3COSal?U_ZVZCHNMy zjW%sFHae^oX7hd)fXrD@34J5^tA{#tYZ@3!ss9@JzW*hjxuGXsmRT6e2ZXsVk~Rfu zLBCCZQR~L@OzscmNJP$o!GChkcb84g?=N-@x~@LuO)vv8-eM^YA2u#v(hn^~bR(>& zSg-ZSBH=Mm4t0C%nXlD93;Eef5^z4fk$i26@N6Tdob`J3HOkhBjoOvMaemS5H};YoyV`FfB~ADZc4QcFV78DyliZu# zKWlPOPPCOZHexfX7sxp}9)DCIOi~KsSrhj3lMlR~42|k1MWy;|xfVGY#aN@KI}gvy zjD^L5)u%H~&bWP5YrfBlt;dqAq0axL#N@gPy&i;onW`w+;I)|y&;`ia@8WwV^q?E4 zLE9tW=L&e;8;Nvwb}A_;Nm*HyX^UmRspF@(6!%Qe4&{mVLNx*ly46Ffb5YM9C>7QT zO2Zn#kl%lgPN+@0a zi)wepdHPcaQI4-dS05P{@V+Rhl%b(JsL*F%yiSHwMxW6cu&zL9E zFzHqbb*~x!c~z0hw+6?%>!|X(hbU9C)|HW-FEDAuP?7XA4*pKm^iSOESFoax>oJ|v zVzFDz4bj5@70`kG?&fmz?tEF&Tn`9Y%`%ahQ3}o;?Fmx>{{2$T+4p569-dm|BlCqp z<+ceBr$6Y40?*v*d}mwM^`OBN3a*rd#8eh4I7V~sj>b|-HX*e24=$XR!Bzm_6JAhP;v}+OlWJ0ONA_(%*rvGDgz)Cqg*uu<%yM*-kUS1|T)oHo5IRf?W`iEbn z#;NcG6OK$5%``*$NNAQVN7j;9g`D*`r=RrTz(>Oh%TYCOG4WPipARB6nOSXoxxAx7 zN8nJx9E{#A8)EaU_g*GQ#zJQ|6`n4NC^4-KxUFSzFzG+K`p}9i2>hb``3VIJ!;FtL zS=SL%LmCI2!og)h_;zm2>0r*->0)a6b{EP2q2~}}bXaZ7( zTU$qGf4ffe8$wNwTjk0^Qs>caW$;Xeg#nu$wZOFqc`ZQ;_SdHwisefM%O($iZ@wP7 z(eT2#nS@PC3`AqWYlc%I?{L4LEfilRqY7zVsE~R+aD{j*gK0l?sJoCmj}fUn4ZsrQ zz-K7rsK*V&I4}^?l!zfrQBN%B&U7bxnkmX3VrGtSL1O`_`VF5t()VV-CZRCl71KXh zO*%dDJ=}E3=biSCN*s-{#&8JOgb1XMmcJj09CXRqtb(mcPl$T>(EQpKi>j4eeWC3l zrE9?LSrWhOS}TKGGNP{Zf?F$`OaW9@R0L1)c&D?3ZhIhdN?xAJFmH{!+?1Alpk1=D zZDp4zGMvRmpMy`Kk%RR3vgoouL-*U;!2GDRPJt9kf{@^X??X~v1N*mkLkes6^eAz( zlyB#qr7=UrGHmh72jeB<eGyS|U*+4p3#T^T#8U>qc2AD1wXIkd;FTMqK&2uSbJfA50--Gw}L z?2C~p?!`1$hP2x3Si}5JC)pl8*a~eIfH3*8RH6E-zjjjEzu$b5(je|xnxouayNkY1 zqTQBsX32=++A}g|LCxETXSKmZ9qY(6DcNO|SdJY#t0?J+#t&sRk`JAtTMngd7^4|e zWHupbTyER)YsGf=s-gvFeQw@%21ImgJKgzra6V1+CHRZtdieW;!H5))(P-(nPr#9Q z@aC@NX4RV=Jkj~BUbWVQMGH4cmv;wMVh0+p*G@xTPpj~!lq!E;J-!#ty&AfTCwaAK zQiMV?YclWL!43*SmCFcVwAzu*CufB`UyjFQ3r(Nvj za*c=IogZDZl3?;l=(fRJ7|j8mMl!ed0$J-0)9Y6)3xjMX!w1#++CXRPeKxF!1Dd~) zq?mHPIwKAvUgYV05*-;o!%PWu=KU4YQFfp8?AUGI53%=~c%HZr{f&CGcUZ78!5nMg zklME=Q8Utx4kXs&UYRb?VO6MS8z5h51s;O}_<(|FxNE)^hvE{@%4V?hzMxE#k&^BC z_vZh*I9(ht%Oh;azV{^W%O|I^8k`-oYnX9|{Cy2%E}M$vEx#!I<+tq8k-oK-7;gv8;!P|qK`WI;O2cvXNR1*(38-DbRvS)S`9 zW}N*R8MaQ>Fo6Arqx%PuDVKn6cQs(pW0P5@nh+*EUNqEujPj^dCc9u({C2SDw_X_- zfW3@j%9#Zys4vaT=sgEDGPPc7YjzpG2r|*A3ltd^A(~U@2tcf397_My<*;5-s64d~ zMlZWUn5ok7Nx)A^Q}mm`4m}E6z_&r%nJWXHaiq10;A=C<)}*Cq=TfU!6*@t5!Cop) z(k{aUz~U(?=*2Cg(Nc%(yCiW`={Bq7>VWH=8yas^Y3_I5RwO^&1yRXMD!8KMYFB?R zbw1>Uu6VyJgD1idZ-1YxCpTPD`o=mzHv5@ZP5eHWv<4rt4<)^Jw#S^z2AAWUryLx9 z+@$Q6&_pRcBCYavkG%hEy6bgUFnoUH1Mo3BY zbEalQVLq05kB8{ivQ<%?rd|twb&6xXo<&^^Q)O}`G$#W z=(n}|p&)yH@=N!(Lu?BTny7Hg6bHn2%g%raj6wDVnep}|)jvaA)xDA*=<&g)@|E+K zrHgmM3*p_l2wU4GAsWpqtc~CW%;M|?ab%Vte%Zt}m@)6(Ol~cZTbP%)X&)87K<$^@ zWEy!pPZx%9VpO}ETu*|F=f>Xp5^>_EsVV^Wuu{_&_}OSp<$xGRW+wrD~sI7Ly_JaT`w^d&f>DGk%&Vx?vK#%W<0f5RzefH3JD581R@*@w;&Fl z3elY?UZQFlY+@O5Ic;b5dMUjYfNuZJzozN#j7{uXR%GLe7b$}lJzJa!)#dwTc~8g1 z*(_xi3g@9vNv+bnUL14;6h@;t7578hnszdx;jIJ+LUt}emUP*u*97^ZoF^-x-ulNY zXf%`Oc9%U*-Y_%I-#wkfr^ljSYhST!p%)8*T{*bg;ZR2C*2Bz}SMs4;s!ix^*T`Z1 zqsMfBV{FTa+gIVUo`mH?4YDYqbWx|Luw~VH%~_wd zLSDSYnx3twZ5(GKMxU>F$xF4#DaC#dpxFSJH&3UAW5X1;^dF~c`@P_>%fJ=UbhrU>x*f%hqpZ?izi7)O?*Ud?^hbp03U}*>gL0*UMrEF z1f&>^Ph9Irg-hbUi2RCvtE+f%ehu43D4;7CYrjzpGvohYlYRd80p02ob1y2A*pJ0&H}1QOHY(RN zzYU+9-@_|vR>ITdO7uCI*CjI3pW=uw=N2FioZ)Er^y4lcKkH@gb^=#|%fX zJy*gt1=ONYfQ)tEi9H>u7r+7-3L|}hd}ku>`H9K4 zDZ9UAmf}%_0MI=-`0blrPfU+;##x=GPp{RZQ-Io4Phoc&N8v^xlw%T)u;$*x88(8S z-z`N+Uf^;TrPK?*28iYIaFB4^nZqBtHw&p=_$Gg3<}SnWP3K~?$x_(1n@YZ{h5C=A zlVzL5PeBTX+|SUDoq|{so}FU9eh_}vdVj}I@B_B7DWVjmXzy>avB$Ra?I$pH(qXke z0NoiJWfnoM{+t>C-}Pen>tYBuA)u(p!E$C0o^8YU4Uma5dsbCCk;gAkc$2Br?_24& z85NwgMOGCIIxS6XdFi=`&cL@^C7mAW3$5^?kPS6JSdGz%S5OP}WZ3DA z5_%~)w~L1F{I%u+EJTZEz+D-Pt-$Y*$n!P%vwTgb-FV-?<&_@j|JXQvZi>Y0OiTkX zrcu#?t&s%GupP2sD+g=7fp>RzFFM7X?WLJgKVa=5{YJEO+r&l>cqQr+Gxz?0J)Fr; z{6~gKj3{J66`)zEjQz08lN{oxh)G=M54d!FZFW5Ntw(*D$Yw7=G8DN7A=;lG{qkJz zqf3bQ!LPWE87LEwSivzx<^=dGg|z~mR+*p9+Qrz2h@;Hu4m#SxNk@Y2cwzCo8P8b_ zwJx?1#kd6Z5gNUtf$yGZzef(-UO|lKt0uG*M`V$=XXqo~G8#N)9b3HOVTh>~bPFq| z%5(;+Td`^3=t~4e{o$3XGApdFoeZDwdBpNF0m{1s62-zA)cz-$t4D6aeo2xTMu~XP zqdzKlt16R37i;#rvK&$c`2vNWV7$$pD(G#Mft(%LN~)twi%LP`EY* z#Z{gkq^qXbrP``1!?|oVL4>)KF+)2^A0TaMbjX#-RCGAP_aL$!zhJ%sm;I=GlnCUT zWw4sNgkexRJgK<&BoCVk+7JaL8R*+~0LDC`AN0l!KicIX%KSJK*C2aG>WP<5ja@Hq z{CkmK84|i@>R&bS*vK~c#xm37Y;DHM0eCbNC|n^uc!DG4KwCcSSw~Mof<*4o9(t7( zlikOSB)%549rZHU_+wb2eI6dk*hjj+e%d4_ys3W&5~m2^D#}E{%;ShGcw$Aeg^meU za@jd?M0$G_P)gil=93f=F7y8E3x3MPE48FJvO0&3T;VF@|4# zl{-acGq`XqG|UF_aPOXqa2V6#=I;`=JGRq!nJQe%^plef<3%OKVmh)Cw<_g$oojaE zR4C{MAY$D0ZCHJe;`r}jnQf9Cf-}yc@L6f-jYiQIih$|I{tD{Qn?O$t;$Aky`#ZXH z7KB5MKc)#Wak%q0ZQIEH*FwJ-vT<;o2d*e!i3wqHepAuNu~yDXC#7yFog2O4>>F!d zQT|?{giVo15tsadgFvR~(k&g78~I#@jtz*pdnzQ9R2nta9s?pS9;imJSHjh7R`onjBYdgo^$ z4=6DajaBgE_&cLaXDJ^c(c~A*BlNRrav~igN4eK!Q_cF;;djIRH$LvIgpC}5{mZzQCc<2waQz4_YxBdM-Pi#W} zUZXh%LleNbt_w@MuM&_>*WxqB7p%AEwlspnTWY|!k7#l}lKf7!#1;D{Ng8Pfc`s6Q zHdmO^LcRw9Z9@n9$Z8)Idiprf4kHAtpzT&Wirvva-cRt;E*%UU-Ga96Dd6URJ1I>y zme@B^6p$&EjvU=8nEAbOy=zRS8`MvbD`@PVV85d$e@tZ`=U!_%m!gQB=r(<8`g>2R z|G{cUJAl%8E-D0K&X&F~uJN`6LnWY{pVV&Dd&O>%F}Jx2n*&P3qGRPRlBFaG57COWv$L)Cea5(F13Cz z%5Z6`P_~VC!S5;ji0~Q%rlwYxVgys|kAF-Skak3=v4$h4 zDz!Upd?*3}T8V-cxfr~lvJ&`FzwZ#C4ZsC8LSLF)dH z(3Bs>l_Hlq<5TYCdgum_d7MuTx zL}7?fTgH4DOAhQX#7{=^ofB z>4U($z>a5UQGxP@fDUqthh}n$8&itQQfl3*Km<{X&O(GO6xm`|gbg{4-L5seWy|GY z^Wf3XeWW!!5DM`uO7bQw34;B)y5tW_*CTzUzH|(ZB^hbpOriXmBY%NlRi($6Q)V)1 za&2{p;`<;knbs=eD)}@e_FurfR(u@GM$fWm2qtWC$WxCmaj zh7_S8E&E_@v@zEeCfe%zBcE>^3$q5etc}d&SJ`*0NAk#{ZY#tnXs!ZRrlP3jaA!f8=N=j%FOJbo%Y%KCO@n-1q zcm1+Qusi&!Z5eCIPu%iwuA0Bu4MHR?P z^uGfi#l1ZG3UQ8bxpbKxbk!4e*CatyLxRkjD7_0sl@d&B8Y$-I`-`ApOgB@hQ zw)O<(!mG5mql~e%&8(Bpo(}y{ z625%Th%^V;qk%koRUe##?bgpV0bLwI5ihFRno^+oj(6`@H$US zV@D2-;zB-!z$bDW!B_hVu!lb`?vubv`fbcH8BIt>f>M1P;XW|y3R-w`l({U$V*PQAXxwyB8{oLVQev+Gql5<}A{PWVa{b~?#a9z&N$&0}Wps;*=i9~Yib5jdK zVOx@c{rAa2EIVvO@3tZM>A^_!s_B8g4Scco(OBF=2JeTf)37(240g(`odjBVqz4{o z=z}}!dO|~W9T!6BAydSVz+~j=$e7+p0x84fz@nisVlvTc{ucMa!Xmk`x9E;-Kb-D=0$v~byC>jIndm_$9jr&{pV^OaV9)weT zGsoS5OEuJx0k6Q86NT8FX~UqlfiUJ4V=<37O6e4>faF+kgr-wn2 z(Tb?E$D=FK#P@c?T96vH)a7M?zh}X?7_wA&|rhIno<{JQatbjd(sl${Od3j%OlK8y8_f#tYT(MpF_& zVv2)*;9yMeTHY8$$DG(T2*)Q?L+2PB-unIwGWa7)S7BD7a~K<>!)LC6NnE>%ymFX9 z&AiaHo;N}~6=D{Vm_RKeJyaM+oPo!`NHwH$dM|BTW^CQvO#1D)6gdmX5 zzeFOrDB}4+HIMy$(qDh4vX=WfhEe2xR}42r`4+W124u;X-r4F&Yz6mgL&G+B|J z=NvsR{R|1sPm*xBNP*wRRK>mtRWQGIsPkb)U zN91|w_8goprgT#}i%d48W}8`~Q4MX!pe3iVH`RjoheYGs2O<&Ru;9nj7JT|gCZ_(8 zg9kc>VCRIY`1=VG%{4v6_mZi3Hq@!)gY{2V!J%j3keyb9$v?XqJ`Fn6k6%Z z-)6_1HNB9XRg5nV6#=}RyV#Nilbv0RCA$j{6{1I^mkP;QW@K>7C7p+Ji^@WIw2*if zleLn|v#T(Fa5R!9S4Wp96{h}_jzbwXg!pRUCViidy48GfU{X~~>)?yuj~kIq!d8q*YE$5)Ug1cZQXONPdg9|>)37el zg4HAl$E?agt!h5lJCV-Ms5p#o=_|6Cnr{_dJNG?P72n(+jsx-ecxgkv5ZEz6S{TR( zJPXQJD-!wqv8(OFiBl+5K)6YZ5h%9iWMs-t!zZx~k*{Fg#oMpio5;>w(iUlEuqlzukPlFmH77wRe zkW^$xW%|v6BApF=3S!Wz5Mxl_7zqra$EYIK>r`kSp%vZolJhJ$oNE*2t*?g)p(Heq zktk$?w{+K0XSy8l@ld02s78!n$=|Z&aTiW*EunY~w0}J^=6&cnpFrt7LGkc0xs|fA zj~W?8Hsp};E?NLNPO60Y8^G!$j!xy9#ULkah;vI?B@$jKEN(?b&1qJmS-2KfG9V6S z^1w9Y&(TnYS1nkFXcC_$dMNDAG&{FZdVT2J)DF_He!|uiGjjPmL=qAOQ>$aw&J46& zlIm>Xqx5lFLP`AjQ2vjSu;RFdKPQy(?WvOw zAkl5L({Dci?9UG~3fC%H2Q{s3Pd34iGFnXMBa6zI6RxMc@wqB8+ORv*D)PwFCWLw@ za3IwTZfFjrGD;u;rKPZube+Ug**1wWz)R)8fiyGH`CB=0e(k6cLgh^77KHpDxI6;JS*D^sOhW1DOxY2EQ@o`=acUjI`@r3weX;{ zq|kkIi1J|PW0P|w;fj#cPF!E)v=e0`ih}O%Yc=hJ^F9J*_K!w0jTys!$bgcbOUYE{ z`*>=gCxI8@uY=uW#paVnRE;uV(_=CCVSO6L{hf!~Dj8tPHA3O%jivpA@Z_>&>^x)P z2fla)aUEWt?oWP+m`Zhw`}uOEi1MI&Q%*ba4_7kn1lMmZs(WMez#zoUK8;nQ;&A9l z4*GqSj+)^*)PcFuxY&wrr?}qqm(Ktef{6Y)Yv_c`G0ydVE&f01Sqm%e)BJYN|j7sX@VUT*Cw-O0C99Hrso zQYd$_L=re03mx8@%Y;{5X+NvXcqiV@aJh1tO6A)bDfzk1(OGfQHr`y4Cf=9x<=(yY zzV2k}NBG-`u<k%I90A_ZaDh@!X-g7D3IqS4XUiYHfR zQQhglU&oA?a&I&yxAVfjBr|G<>hRq1Q_zI^;FXU4NJ%folvSx{*EkS4Cvy>6Edcja z*JJ<5LhL33zh@l-;$ArhpD-`%7!iTTKRt!7PMD!Mx7WpGBFaMw!n3$iLD;OSH`etJ z#))Dj(o*tp$Kq6|eKh!s1b;mr72-)0^2^`e^XPf3(P8zF2;}FP5gw?++MU^0o9e)8 ztqiC%BLS1{i@~@!B_iHRz$0D5(9hS7qk0bnX650opYyTq(HI0NY*3R3+rboIinO#e)U8|hn$@Rnv+kCF3yF;rg_gtu zKa|-pKBwSH!j#%Bev5Ti7+x>+5EEh8k6Xsrxwi48!YUTS+`FS1LUsFl}G6FBgs9Nkk&IBC;STg$Q3pI>m8H zZ4>L{%AzDa9EN}MlGEi%9|eHfY(k$*{^h&xWb9; zyp8?b`Axa zjc-#hZ)*WGR9|_hfybAhK|)3`27H->hknk2S*t!nNoer-yfd4(+1!xTA&_mhR0HQy1}k#ocQAqLIH6udYkSjK6ZxqplAg zYwUwMz6x}mcM`SVJq;_RMXgbxTMbW)|LQdQeQ}2Nw&UXy#dy4xFV^mIe&Ud+Rbxye z9^mX|yvxqU{XF9q89o=%N%vnn_qBMgtySLKQi4!YzKDyD{>NvSv6H(rvPS$5ta^SB z8jEfCC9zn{srX-{;YP%HejM1IT#OUkXOjh+Ynw!Jvp~dW179CCiV?rLb4aka8XkHjCUp$LlMOtvExrg|1~rOuEr`#z!kAEmYztjoWQ-=! z@5w#G@!8#>IGI=glS2uA4<(vc^}^y_!6?o$;Yg;_Mv(l!z4HK!s>uKNcl&OV&8BD5 z8=xRPC@2V06cj5eHU!094$pe_a#rl($=Mb7p9rWF6%;{w7o`XY={?!>x-Gl;|9-Q( zVd22jm;fR3fyur%GjHDX`TgFTHzSd>Fa9!bN6Npl`AU8lu6+E4=Xg*1VWm(gJjXE( zCn6$0eC6Y{lSn!XLb&JecX{>!e_QSFJPB<#C@dC$V}jMta$9N7u#W-Uw8Mkc2nkdn zj0AY!LmwSGP`Y^KX0fL(+i8`E=Z#VgDlxG0cjY{3z@SqhHB?I;fVY3Fz=**SxI9UX z|Lo*_V3pK|h|5n{gc8V#E>FwnH&9ep#B}%efWi)<{7T|Ij9HQPS|3k%s0*gDzWS4V z6eu* z8|yLQj4+f}*-%>P604`{>H6#{2O1ijm@=mj1K!NVH4DqItk8}qvmOJ?I-Gr0C?cua zgaoS5H$sDaQu_91)Z(%^CA>}Fg;gTvtvi^}k49K65`|FT`EM7gi1Hi4 zV@pSVLny){;r^llCft@DEVfmcx1j(%48 z1reb-te6^)=f5bxf-Hxaa?auTU;bwiS<%to5R!s&`^acKH7poajV=Tmlz3u60dz5; z7#-ro#5v`dG&Tx1C8{yw&ubT8#Ou6n0WB+T$Uc$d3;AhEU`Km1AZV8FK=*Ks*r#^tqB2zYKJVgt z`#;*|b2fZSw)dU;`}3Vs8D2F!8gC8K!TekIyx;6&g0u*pknEzgF!OQDw z(JL(&OD37|$+BX6knhI&o8#!(3f%u~6<)qE2@zEdn7X_cA6*xRaYy>)y#kqbU&YUK9aGV>6VLZFhW8 zj34rxp3fPMLTC7fu(q}qd3kw=i;ENWub`kn{3`kBibQe(Ao@ju6~zAi`$MaxhT0CC zGzy_egf}Ti>&y=(l?N#A;h#WU&q44LZLxR<9aBL5LAClJ%k~&l{Q0Z#Z!bDBjYr~O zx{1T``*_lt56m&j3m@X%ED-U^sbUM1<69?=6;dHQwsfdMDBkj@;GW(Cb5~igr4i_F zXu%ynREl=a0%NFNDYj}6uz$K&1dSh+P0iw4Iu9)fCxxNj?hb2?SC8TN>SB zN@=K8fzk#S)O6oop=zOc@QS{?XJHK~AG{KFs!@ggRSwt{D&#)a4R3svkJoqb8=dxf zLnz|gQ3@gTbN7xl$HJh;3X7xoLro8J^;wS^ogA0h%&sG|BnYL=)YDzXcqdQ(G6 zw2SkBvXASUL90Tu+YMe>TdPwdko;9wkzzg4j5pG?hN zUtZO^xXFzcib`0JQv9+$Vx@4JC~w0IbV7E#8nl7hLvK=)Ep$J=2gjW&cnn=vN$w7+ zFj3^ck;q0H1T}2soT7(AW^-fTb=n-we##`fY z;|IC;rBQ*^52WJbMfrGST|J`0wPJyrB2o%@`z|hm-^;hosu0G;#zL)Dd+MV^a)MA? zT#Oz)dO)w&du{$2EJ=2HL>px#jSUG8Ej+d4FhoPXY6yoh^H@ZmIoYpD}%w`nbe2)mgE5}rCk$8Cy#J0Rjg|HDZLQHrQ^Y(#u{DpsuI zvZFlh*gO7jI8OXM2T0(dS^)8EhK|KvOnltYc=M!RQUE@^I1+IjiFPgrkw#8WJBKTg z90DsO^z%!86J{(b#neR=2nsb|^KFS(dsi~D9_@xz*PEfH_LdSCfc+1q;pcl(ux@G` zQUd5ojS35IO2TjVrC`tB5-~23MO_8Hzbyq>52hmL{$vaeQ^7{E>T6fUqJYBNFeL_Y z+-Tdee{OF)g0ez<6KEm9-9o~4DM{zN?`M;P>_}N)MchtfS!oj`j+C2b^2=LF)RH@I z4NyZ0052HBLjErnhfpLEB6V;#*f9H7QU>XojuOb*6^f(Ts6ibmeT#P1;L^8qNEvja zUrHdh-;;`8sqDAhnt&uK`q8~ZaNzz_?7A-%8B`yVbqZYAHxyZ<#1uS|hJW@Bz@Sr2 z$oxkdavx5^+vkS~dB7N9!2YMYBm2P=Jj@^4@g9BN+aF6IgPZb@Dt&*q=t@OLC*p58j580>4(%@V|^@e9+Q*Tc7#f3(yA6#c@Ne-%D zS%}&m-(_#P!&7fKJW>XEL#()Y(wstlP<=@mlC7tx`gmKcDY_om{fI}>n5xatt4kTZ{; zn7w#q9C98^#UuTLgmT5!9&<30{W;W)EJ%Qe4ASDhkr5bfCW(SNSn_f3ItsH-au6~e z>yEkOLr4LoPLlA1;}@2&J;=d(u<=^@!S=j4;lv@4*mHLR(u`DWhY2%$c1SoLYr*fj z`P&P_cK+s|y?}&_t4@zV(PP~(txo_cY5vz8=GP0K9(42h^|<{{s2m3*1z`^j^50!! zhK?J7_hL`DhnZIUL*wCbC%Z$A*^k$Fyd69j#%t*Z+w+szFq017+?j-uC%dEa=^j{g zof$n%GQ{a5p2;YAU z_WNJqdhPfjbFxv@s`UH29W-7`iKH#C?ATz(Gpnod%9JGReLNMtqg6D}0sWH$vH9mp zjQywxr}s2rV$VR#7$1$@d#Z89YdLTS>hQooBknyX5doT*_s7x7UEkCpYt?N?;EDpb3W!@ zR`R^=!uZ_Do|T6@OB4FNosSBB&FBf@#D|J$=2{E%5rHUq_7uE!afDEm9vc&hg8Vx4 zemNVB1|42JHx#?`t+@H~V)P?BQ+yC6BUj2 ze66qEaM(UOI%f&7ndGadmRWH6yZJbi%%xwHp~<(RHQ%%KT2J^VGd%7wB9Ekz<~SPP z6lOkdFFL(KD4;eP7p6tucmvYA2jlfoq>MFrHjWq2z~3{`fQN^MAlYod=`lK1;eW?VSbLo5K-5K_o46O- z|31RY;pM{VWqZ3Dy%UYdc{~jRj8wq|F;I9JsmGup&$&e|riN z9R|mdiYl+jiqsb~@$jk|+&4ZJ(}x8kEnI^^J&agnuuMR7i+W5vzAK;D^jc5*K*U74QAK zIHHblHKF`ypf)7x4k>{`8T5GRQgD9on;EOsDvdP9-S9Vryugfaks1E|D|GsA2u18k zA|BN@1S==UqNvu1I1(H}?%j)8E!~HEEvp;v-IRh`duUKcBjNH5<+$>jN-nn-ArnfH8g`~a3qFm5Upy#&N?^FPctBdj=w%&MHPP+!~Us*jSkgfXq*-% zwE|lTZP;5$Dknd6^A6aeh)5`(c76=r8XbiGujF87xl4S!Eeel>d0v66KPkGLmhyTB zmS@{oCl=`sBq6C6+4rRZw5qWgYw~S$f4Y(e%vh5aF*InFQ*_QDp=*1d6}*O~8O2vY%EaDsO6TtgCj6E+#HhihyOYsW(S(6Lf-!4(32ykd z3NY|KTS^iNcwWD!9&j+l)lO7XUY!xEM|q6{IZYI2atjM0;W0#mttB=w&8$zT3VSM= zF^0;d+18Aud3LzyeuA-JgvAd{>0Y3n?zV zuwRH4MyhTWo{B@^1P7^ccA_3;y%IZ%ZTQJoKZs~b)0r_^@$FAG)vGmm6es>Bm2NVe z>cKuzwo@Z@=uUB7Oae-M6V;J0t;j=O^WaA+qkM|{4d=z;lM784`dkJjP)~xi5^JfP zvc=S)-~Tp<^Om3PrgNQ%)}V;;Z573%$>~N|uoma{55@mo5`kMj$;CPgf1Bcj&Dw$p z%EO^?Is{U5w@|vWYWQA9FT?gmenWU{>F7me0(ELoGr>OULn9M{V7WgYm(D81f}7$n z^q;?Bhh2r8_ov{4)fP+|8i`dKEqH~5`LF;rGOL`Jc~dG*wKw95`4*T0RH&i${?H|{ z7#-Y<0Uwqi%%njW^;KWpkbvjr=VN}36YCyG!`6+ZxPN^EjYU+E+knUlYAbF1hEU`s zwL$TK#Rp%CaWF!*{G_fK?8eR6v?S#gUF`i79NJ%z@V3>u$ZjV^rq zNDnj@)gwB|h+lVB;E}a;Lc!0kX~w6wry#k>hN~A?uQa)X2M_wEXu^=T=k zTZ5JtyJO;vOf1NEB07l1_&yPMbBGS+C$lkncogQGAA;2B*|3RzzwMdSmTw5#lh&14 zTkBN|^`CU^tqx*5Vo{9c1h%8G<*%gokI-4aAvBxK;u}JhN`;b=5;Qb4K&R7ruaHQN z58Ph4uKSK_YHE<4o{nJZ^V-5mqY#cJg>c2>7(Bnig2%>0Bspj#zG8u07A z$yl_e3_}J-V$Ze;O!%@4cbp%K8RNoGPfC)4l!OoxOkP=9iieihASy^rD%2Xm{JIE_(jdpn zhl_5U6L|!MkUM(umdCBYoa^Ipb)FNy0s+=}}BdM;s}yOV*d-jf@tox-kY@NxA4{)*?tpndo-on)mbY z`93SgoF0ZBt}|oGyIJ_UMuozMl8{BpK@us3dQxn(G>|?vHxDmtqx>hOe96=VoS&>m zE)Ce-$PbS`nYs1o|85}~DTPNHgj#GaTsAlyUtST3@o#3~(J5)@*<#0l8M!EE0!}9d zaqZL?Q3rV3Gg7^JYjFV{*lb1dKYL)q#xh*CgcN@gf+mfM!JLajF>ZPW%1lAnIMs|o z5-7Wy^=PQEVc=^yNbehle~phIL67p5%GIcFW7_;;ytj+eK}xkK3koZal&|ggra)C| z#YLYMVsib02Q!1TxcYPdEC3Xuo?sK{iGPg>J+FaVS^Rm>nI+dW!a%%<^LEU6~bdn zhbn~BZ+D~q^5Kiv(H;F9 z{0==Q^p}1P4(T2byhsVB(m`$Kw8TJM)=i7&R#l6Kj{b0*4;2RvhWWn`mvxXr$Zw-Q zxhf7rBUC7EbYpO$9+$k6gZS>Dcy(kje)zQk(NuS4|4<5ZZz_*d^w^MZ$HYM)cw}}C z@{C5@(_M+-b4uZ&IL*B)4dYmQ{yM}xn}acDM`G64P;`4W8&=An zw#J(|o(ds9@Qb3le|_&je7dy;l`Jz-pFOsx5#2~`;mWiwyAiv}o#>w&fK&7>n0LVT zyANpFgOfoaME=_lfpUWwQPJ-V8k@D{jIC9~NNrzwU^7+{m5k?_UH2Ep=Xfu zOqt@bkyo7SPRj1pALZim&q`s_YVhhsQOL@vLpsH?|694(O;LUM!U%-$Iu$~RA}HXZ z8arNJSA__Q%a3=aVCML6L{r%dt(3}i=9Q#0Y1|m~Rt~y}G&kbLF=liRRf>(E|Gl;x zgWk+V?>BR>B-c*)8i53@lCqY{gW{P=O6u$1m0~0*?R!bc3*|=(62~JB3 z!m}en@a7K|-2Qci@c;L@1(e4{*hXz**41%{RJtk8@~BPZVK3*Idj!U`);IdPK5W{^4jaQ;EQ9mLUL9^4ibx90{MNxaV zk%NQMz_UBVOa$`7Z+v(b1b?qiZCkW&QpQ9$R}D%bh;5Bg@oUvUBy$|xj*Cn zio@aS>6+GSec}7UsK0t*sRggn`2F3!dNEEdFLdJ5&6T)zfdzZ39GJbO8q3O@ zczs1Vo?cyrt@(|(nEK1bg$``UsKrxjs>O<<2vFh91%rRO5$zwYYsL z^>;=!b`~_^jzy(#wDBRwiGV0aQqCux5sp_cjUib>=P6gJKhItqgU3lO;Bj^1T}iki zO^@?PNqlZZsK{r@AA|Fll}_JcGtx+Nybae!(f0oWCz(PBG&H&J%7$trCIw>*KYi7Z z;&yf@cK=p`ZADIg=_XLmy%J&31~FY~TXqvn5nA+*)?#zEm99}^SU5$92mIed+(Ln?2b$2*=)i$G zH_quE0K3hJ+&ULVl8|AySaJW{Vtk)tM>k$dniN0Y-5E0~2z?bZ7fVa{HvY>?&%SfsNJMyjKEd@^(<}{OD zij^BI_{TRDSbd-Y${?M1yZSFu5Eo`zky+uu4OE_={Av|SEl1{tPm8c7+lDVlNZwp* zN1BQ14uy48R4QD0S`eCRZP-uB`{+{x(9qb71C+-XkZ{mgVaFB=e<=%Wr#icZ@+2@+ zkAajYv)7bk-kv7>R&K-1pO@f`y*%BOQzw!9IYe7hwBSc-!!NI{#`|0AP(?y3cTGxa zAB(9Cy}GUj8>vs?Cvm)r=8|9Q@zjr%nEgvVO03insoiYOX~J`>s_@R1TI?;SPE)54 zZRZ724Cd`?Ktr=zD2K(YBvDgdL4DM2s`qVuv)Nwo!^znjD{$_cc^LLa4$75U%)Zu) z6ax?vtih*O#bD+|5tuyKgpaO@#qIqJc>IEBymW2^R^6F^zL6R{dR`PZJ&=kot}!Fo zKip|a;PuWo3@tSuDQx@u%lJt(Yn z;Oi{v$EY9XtrnJKHj(n^y?1AXic3jj_4R!Nu(QyH+lGdV2ia^NWKd$>`f`kTJ`)?N zS}?g^5R{Zo8dfC|Nf+m&QwRez3e4SDC5|Wc55};b!5}>gZ;)aKt-3YAEi`zf_cbBw znI72pKpK9$EkR7L;DJ(2y(j4+D6JtugvSK#O?e>~9#H6%!)Rt@jh`@+Q0^dsgZ>{; z1{ZG%X+Tx+D=Sh$d0`-asOuyRg{NpTmkbWWl1Xt`G&v6U4K~7IcZu&0#m)W~uL{*G z000@LNkl117$&S*`%bUCTn#1Dc9k1cwC)Ye}kc@9HFwzx;63jk6 zpPGONFNnmfE6gY^wIjchJh4tn`5=tv5)Z7sks01SSZwJqw08*R-bHbr7)AELuwKE4 z45GkA8kAUYLjozpc090x%3V!)>>bGcpO*N)FB8t>R$NEr%jqK%CzM^_wvkaJNX25& z_3^lAKmckgZLN9L3SUp@T)w9k{a?+(1^-t9RiFmXjg3IxFi$vV^$iu}@FkV&^P@uH zqzY2f=tg{MAhti=9S0st#hN>kaQ~2Ck^jD1u!>h&%kiQkidQ%Zja=VyswjV1X=KCs zOqOiVG_&?gb&D-rblfVxalZPD*NP8(`*f*Idc1vQ48Ei~GxutgChJl+o8=MZzKy+m?2-2W)3p7R4bHeC10s+!P$dJY-_xDAjy z*@=FJ`p{Ot{LANjJ@rq%>&1$P{$cw3u^)f`+#erjY|a*3(BFiAO^(Hk%Oj9eVMkE z5{)WUG`Rlj;i*I->6*|8d~(5ytDwq;>gp75S<_CBXh5f zLyVHA_GKaFxeUCqwNChPHn*Uj+#`cM!o8SU!&5FuYWH=V{_=!pDES@8+2_OeOG6II z_;nZY>cS#~Kb?W(7qXD@VkW|$Jb-m|bSH0oefaL}V2Pg%$BPu(DpDY8Eln8uY9?Zz z&!jN25dUljM$9h7`Gdl6?_d)?TWdkWbD0=6vjFup0`O+Nt5ID9HQ8siC4O!&u(%A?~5 zj~0G=K-~Um9-{x5iDb&dl;<-L`*JpR){_TsxI(evKsa26ESIgRz!p+)S;4pS_lLxS zm8FPy@?e>UKE59d3YszFl2{CkR%84-If$b&dWe)*B?&J6h_c|D5j9k{e5VBd?t}A* zgBNx2U|L%1qPWbjHnIo4hLgrh8QYPgiv)%S3ZuHT2}7o5A&%+{r#Jqe85lXgTs#2q zg&`IupnH|^in0W+^eDpQCm9!a2o8=J5{abCa`JTb-X2W| z$22fbUs(Ztpauiub@*^yCCd4`2o~ynogaCO4pgG3!j6rkEG9+=V00=Cek_nUnz13@ z26KE6ZXIA0Z%3{;BNQK95DKl!BN+R4pd4naP)_h+>(fZji8%Jbel?`HTFR_Ke!l+f z2=oe7iA`XmLbaIE&nSfO!x6zKl^77GNACnZ(i08n9;Ly`-|AtC3c#fPbR7-&PDS1K0E8%~*I@Bx||8%SGCg!gQF>LytMDEI8wpY^0N7 zb~>4}-Yozxc6!4=BhnHA@CjYJKf4|)i=F6|5(Dh~miU z^~Hs6)mmLBWAjM?=4qX`jgG_!s>{5z`i#rWxUQE$ytO?XzE)gMVa9}L@#W+M+}yey zhQ|-Pt;=pgsF~`_z+mxKk-z6UGc5>bMp7gxSfBDXBH4f>QpS1V9o|Xhrcsd?nXE-T zh5hn{QMjeMPAm+v?8Z2ZkJC_`Re1B#7-}nFVhY+MYBy`iErq|2@{58*ax}Ov@qeRp zvZB){uxwWi?wVbQoA^NtwLzU$A*RA}+YjV7@YMP6K6#?IqRxS5^H(25PktQK%u_kIuVc?DCG^xRJrDQD&f5UlpNst2c)mY{uTZp*a>}=8frI)i zF`I?D0x|nQ(s`hw(~=A^E=3PhgaONMPZWzcolE0Bzin5ml~}s95@X)Y!znZJkx8Ap zjvt~+B$6(Vzo^@(@pWlJj-^3#V`c+>=I@|fE_}S3gnjlvhF@$5@tX$1e{HBkT8a?| z9!|v%B;Y6V2ZnaH=rpIVD94_1C*Hd;5xb|QV8K-}Xt27(XMJqMIO)wjv8N#!ey_p< ztBZtho|@r_6#dcw>)k_AC{OGzrYt#|arKuLL`EC%(>NI>0 zALQN612t(;n9m?u=npN*m+kn7E^jINiksMzB(MJ za$?4$1ZhHhk#$@%ZC>6KV21!Dq#Y1vVZ_SZH8( zaV^8{947tl<7EO#8ktB+?ltKq+&?A~EAC9j`>dcj+_+>R=wiMJl|=<9UUx66K(Fq>*nUqEex|V2-;#)boJnyb zzy7eY*)J^^Ywu3NuD>N?)y?t9D`~>hE2~6(oAP-H>Qrj1rh2pU?qqDEus*mb62X+Q zA8e|D%BaVVhtja&mU#3skqGACyrh6BI(#cPKeA=$ysqAfN0ydDOV_NRaCZGI3Ad3l z%+t+0@BBRV&g)&QD(~VCV5m$?foe=3MfB_#ULKqMc+)|d@ZRQXEX}myvCHD9-X&wp z9m!ZYDGsC9FL~o{c7CgA#xqwWU<1|3-XRKHzrX^UUXSmn{I*X^#a)AfQQpvueu+UC z*V}~Qu{!8?3TGu7 zFnw$k*8Wt9ukxDl(dB09Ut7e!x042iV92Qk(RK~o=~7=D9-tDdN$xGNBQ8Ws-J}9P z7g(teGvS4?5%`e${Oi)Rq%5|eZ)y;(9%e!?DWQRSC3Y2Bp$IYHt?^N~_rfTQPf+9Y zT@45cR6*yRnWClp@YX((&Vd-8sgJ$5ACc6Qg8qb(@Oy5&4y*F5DCg;aWZ#>F+c=VMzvqwW zg|rNe>k){GYCC@7#Rtf4Oj;lY$7r#y#EM;&F7ymhVQH>Syfq0oYH)G)0EB53IAC$$ z>+B|Is1rReIRFPrY{+QzC~dste_xXdEAp(clkzh@)qp&U4LhrNn*}}klhD5~+bTX( zXa8gAp5--m{9HkCAjPStS&w0fIE>;g3@dSuXxV!GX3n>Wveda1se*35#==)I__WmOc8#h~U z-sj~22_quCbiW>yZ~a1*SaP6=^3;=7-+-48rV(%CeyQL+C$&N;<^169G%M;I_=TsA zlE0BX0+1f5L4KtZEAs6ao}k0}+$J(xzEelT^;!PSVGd+@nRj|}K@h#;mPwf(sA!RK&Qj6h92E+%cP)y3z&m^?% z;_oJ@&J9U0U?g43O4mw?*U(rkmSs`AND&<0&46O61HbU=I1Y>NN5P(E)`{}vtteJg z8g^KkVWvoqG;8rqRwH-@F2{dJya9ov6|SWEHubz{d~{(1E_@>s-}1s7B0QiQDR+tH zAgnHG!PYAc*ljhSgvxJRyc^?}G$B4jiR-&5@$3!-=3fv97ge}1|5t^l&N0BM(V@6r zfw#|6VcHrS<{z-*=2KcwQm4RA)Q0lw+!#&uU*mM)yBr%*s2qox)mW0zgjyZ ze=M`$te&Bm)+Ye}*;)m2axk{-uB86j2@~}}PmT=7pfEM{2M&~0+3@1VI^1G-6hU1NZdSuo1IW0L|`Qn~ilxbW1LI`6=! zz%x`&c4pP#wC5>bJ!wDKu7$?-zm*!N5c7sGK{vtWK6~CjtWlVKf>&c`lKh z04SA86c!dDIXSt_+fH`;6MC&g*rJQ;EQvQyKlo)j~yBAg}5{1a~ilr zkPojvadZ&>a5!9_GvC`6AOCcSfryUzmy6-B_%T8`V*hOC9k^P=8$3GZJYaW@2gj40 zz3FZB>x-vKiMdmgaCHw2?pshQR<0bJ8ieP^MPkL4a@2(c;u13{kT2w*o;OnWUeEB8 zZ*iZ)ge#GMzWnC<^Dq5x#g{I|SGIgP`{g)s{MpVM|3mKS%R^2o!%m!sCm4sRJB#a-aL2M5ac_oh?eSGT-2-tgFXErdJ$av0t;9a6W5fItm$AL)k8+beMK zd<$=8=JCB?+<1^o&d$>CimbIE zDHyeBf;;G6TK(ph+dA32G|)BSj|tI*hnCuei2d_rvKy&M=cJ-^#@jb8{R5z45y08XJ;tM%cD!ku{uU#*5^)qP5f%y`w*4ATTH*L*=C;Xmf*G7HI;Qv z)mD7-35%pIGVASiFIk;Gs-@psV??CBc=-k1O=}%B`zV;5-2_p zZ;L!GhT zrHWCjs?9Y$ImXAS^d~3f?T5L|fh!&DuI@8LWR7j8ZTiD1^-$m^y`j*r*n-il z6YRp&CREras}SdD?`L|$`+!i{hyDpa+Y%{gE2Oa%y_$sSt;uthUt}-WY<=7uL=tZn zt&H$!#wH5btvd$V#$mEoJ6Q7<_BKshmvBEqc}F*Xl_|&Aw3wdA0R&*HyX<+X&f!<4 zw(19hmg9;B{G%$0$rl$V-#F>>?Swrt{?h!p1p5+| zC^)MG1(rtb`m5~@;;l4|SU((O=;r1|s;xaBoo0n)FS;YNyvv+#E3**|Rx{&}uxj>& zkQQz^D#$+s4i{tUhfxh@kqapYgTWvWNS6MD)cL3NnDo~Y%SWjVtq|zP>h_5z>NCq; zlz#`W2QqKZ4Hrq^T)?Q2`Qb+J7-VSaV%Wy|WLy)Z^C4)?zGKS(e zExN)3rg%R1t~Jp^?_)2$^y(XskRlhYooDHC5z;uQ>L!04a@v275S9;*)17;T#?~+* z;HB^$^ri-X0g^g6F_QOg(uX|4vaC0jh~HRk&s(0rM4piIhuBiF@%;<+gT8%_CtU&w zCy%KLH&(4YLe<7a*|y=1dC);%<9052{Jv;7ygr-LaLu>#m6G5hC#5kn#Bo7==h_Mn zK`};w3N}PcgVF%2iGbklpY`XR(&5~9c*>n0G4GHSCjEg5-8o?!UTxky}M}kt|=r?JK77}AiyHl!197nBV zgB+1S1=o5Y5TM!TEISB-#*A<#)pqy(95&|FH^~L*A2vuORhJ-I1-=$UqWqo+j=;Gs zII5!kdzTE_lqP#BVTLkWe&=bwL|SH!$uan-H&EBV8m;pW$p=skQ0RmY5aa`Jc&a^|6goaqTxmJ!lKa&PiZ0)c?X<4<8Qf4J?@I-*CnK}^5Z%>HG- zZYdzU_{_GpG!u6)O*6kB&(GxQay9PJ{ibG>zAohg06tPDypQRl4tD?k(``LXRj|{c zp6+fd2M5SFa8wn5SaBP7=mVSmtG~a$8VdFK`WTk0a9gP;#;ep#TI&eJ>}oxUv?Dz| z9RvpRMn%Rsm6jBpDsWZZ;WF2#M*H1>mXdWF=bFVcZPn^I<+%nzgr-3FeszAVLnqc{ z9SE>wCpNm{i?Aey{QzCGnheh?ynEMDd~HE9pDAMws;sQE*b)ilRK^YYGxv2;#Gv4q zak + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/15.handling-attachments/src/main/webapp/index.html b/samples/15.handling-attachments/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/15.handling-attachments/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +

+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+
+
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java b/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java new file mode 100644 index 000000000..da78b30bc --- /dev/null +++ b/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.attachments; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 13b647ad8d27dc900e1d2c50acc5c37dd2fec6a0 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Thu, 11 Mar 2021 11:18:42 -0600 Subject: [PATCH 096/221] Fixed some 15.handling-attachments leaks (#1049) --- .../sample/attachments/AttachmentsBot.java | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java index ec02e0aee..0a00c4bda 100644 --- a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java +++ b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java @@ -153,6 +153,9 @@ private CompletableFuture handleOutgoingAttachment(TurnContext turnCon private Activity handleIncomingAttachment(Activity activity) { String replyText = ""; for (Attachment file : activity.getAttachments()) { + ReadableByteChannel remoteChannel = null; + FileOutputStream fos = null; + try { // Determine where the file is hosted. URL remoteFileUrl = new URL(file.getContentUrl()); @@ -161,12 +164,18 @@ private Activity handleIncomingAttachment(Activity activity) { String localFileName = file.getName(); // Download the actual attachment - ReadableByteChannel remoteChannel = Channels.newChannel(remoteFileUrl.openStream()); - FileOutputStream fos = new FileOutputStream(localFileName); - + remoteChannel = Channels.newChannel(remoteFileUrl.openStream()); + fos = new FileOutputStream(localFileName); fos.getChannel().transferFrom(remoteChannel, 0, Long.MAX_VALUE); } catch (Throwable t) { replyText += "Attachment \"" + file.getName() + "\" failed to download.\r\n"; + } finally { + if (remoteChannel != null) { + try {remoteChannel.close(); } catch (Throwable ignored) {}; + } + if (fos != null) { + try {fos.close(); } catch (Throwable ignored) {}; + } } } @@ -237,10 +246,10 @@ private CompletableFuture getEncodedFileData(String filename) { } private CompletableFuture getFileData(String filename) { - return Async.wrapBlock(() -> { - InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream(filename); - return IOUtils.toByteArray(inputStream); - }); + try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)) { + return CompletableFuture.completedFuture(IOUtils.toByteArray(inputStream)); + } catch (Throwable t) { + return Async.completeExceptionally(t); + } } } From 4dd06e088f45616ef5bd1aae140e6726bd682332 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 11 Mar 2021 13:42:24 -0600 Subject: [PATCH 097/221] Skills implementation and Samples 80. Bot to Bot and 81. Dialog to Dialog (#1050) * Push for backup * One more file. * SkillDialog complete * Skills updates to other areas. * Http Clients * Unit tests complete * Root bot and bug fixes * Corrected token issues for skill to skill * Skills fixes * Remove proxy setup. * Refactor SimpleRootBot * Refactor EchoSkillBot * Update readme.md files * Commit before merge with main. * Working DialogRootBot * Fixes for unit tests. * Initial DialogSkillBot * Sample 81 and skills fixes. --- etc/bot-checkstyle.xml | 3 + .../bot/builder/ActivityHandler.java | 14 + .../bot/builder/BotFrameworkAdapter.java | 30 +- .../bot/builder/ChannelServiceHandler.java | 632 +++++++++++++++ .../microsoft/bot/builder/InvokeResponse.java | 21 +- .../bot/builder/ShowTypingMiddleware.java | 14 +- .../bot/builder/TypedInvokeResponse.java | 41 + .../builder/skills/BotFrameworkClient.java | 73 ++ .../bot/builder/skills/BotFrameworkSkill.java | 74 ++ .../SkillConversationIdFactoryBase.java | 93 +++ .../SkillConversationIdFactoryOptions.java | 88 +++ .../skills/SkillConversationReference.java | 49 ++ .../bot/builder/skills/SkillHandler.java | 340 +++++++++ .../bot/builder/skills/package-info.java | 8 + .../bot/builder/adapters/TestAdapter.java | 25 +- .../AuthenticationConfiguration.java | 21 +- .../authentication/ClaimsValidator.java | 20 + .../authentication/JwtTokenValidation.java | 35 + .../MicrosoftGovernmentAppCredentials.java | 19 +- .../authentication/SkillValidation.java | 156 ++-- .../bot/dialogs/BeginSkillDialogOptions.java | 31 + .../bot/dialogs/ComponentDialog.java | 721 +++++++++--------- .../com/microsoft/bot/dialogs/Dialog.java | 103 ++- .../microsoft/bot/dialogs/DialogCommon.java | 37 + .../microsoft/bot/dialogs/DialogManager.java | 98 ++- .../microsoft/bot/dialogs/SkillDialog.java | 513 +++++++++++++ .../bot/dialogs/SkillDialogOptions.java | 153 ++++ .../bot/dialogs/SkillInvokeException.java | 41 + .../dialogs/memory/DialogStateManager.java | 9 +- .../bot/dialogs/prompts/ActivityPrompt.java | 138 ++-- .../bot/dialogs/prompts/AttachmentPrompt.java | 4 +- .../bot/dialogs/prompts/ChoicePrompt.java | 4 +- .../bot/dialogs/prompts/ConfirmPrompt.java | 3 +- .../bot/dialogs/prompts/DateTimePrompt.java | 4 +- .../bot/dialogs/prompts/NumberPrompt.java | 4 +- .../microsoft/bot/dialogs/prompts/Prompt.java | 169 ++-- .../bot/dialogs/prompts/TextPrompt.java | 4 +- .../bot/dialogs/DialogManagerTests.java | 2 +- .../bot/dialogs/DialogTestClient.java | 217 ++++++ .../microsoft/bot/dialogs/LamdbaDialog.java | 4 +- .../bot/dialogs/ReplaceDialogTests.java | 2 +- .../bot/dialogs/SkillDialogTests.java | 705 +++++++++++++++++ .../dialogs/prompts/ActivityPromptTests.java | 2 +- .../integration/BotFrameworkHttpAdapter.java | 30 + .../integration/BotFrameworkHttpClient.java | 303 ++++++++ .../ClasspathPropertiesConfiguration.java | 16 + .../bot/integration/Configuration.java | 7 + .../bot/integration/SkillHttpClient.java | 103 +++ .../spring/BotDependencyConfiguration.java | 15 + .../spring/ChannelServiceController.java | 546 +++++++++++++ .../bot/schema/EndOfConversationCodes.java | 14 +- .../com/microsoft/bot/schema/RoleTypes.java | 9 +- .../translation/TranslationMiddleware.java | 6 +- .../DialogRootBot/LICENSE | 21 + .../DialogRootBot/README.md | 3 + .../template-with-new-rg.json | 291 +++++++ .../template-with-preexisting-rg.json | 259 +++++++ .../DialogRootBot/pom.xml | 238 ++++++ .../bot/sample/simplerootbot/Application.java | 132 ++++ .../bot/sample/simplerootbot/RootBot.java | 191 +++++ .../SkillAdapterWithErrorHandler.java | 129 ++++ .../SkillConversationIdFactory.java | 51 ++ .../simplerootbot/SkillsConfiguration.java | 77 ++ .../AllowedSkillsClaimsValidator.java | 58 ++ .../controller/SkillController.java | 16 + .../src/main/resources/application.properties | 8 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../DialogRootBot/src/main/webapp/index.html | 418 ++++++++++ .../sample/simplerootbot/ApplicationTest.java | 19 + .../DialogSkillBot/LICENSE | 21 + .../DialogSkillBot/README.md | 3 + .../template-with-new-rg.json | 291 +++++++ .../template-with-preexisting-rg.json | 259 +++++++ .../DialogSkillBot/pom.xml | 238 ++++++ .../bot/sample/echoskillbot/Application.java | 74 ++ .../bot/sample/echoskillbot/EchoBot.java | 53 ++ .../SkillAdapterWithErrorHandler.java | 82 ++ .../AllowedCallersClaimsValidator.java | 67 ++ .../src/main/resources/application.properties | 9 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../DialogSkillBot/src/main/webapp/index.html | 418 ++++++++++ .../manifest/echoskillbot-manifest-1.0.json | 25 + .../sample/echoskillbot/ApplicationTest.java | 19 + samples/80.skills-simple-bot-to-bot/README.md | 59 ++ samples/81.skills-skilldialog/README.md | 97 +++ .../dialog-root-bot/.vscode/settings.json | 3 + .../dialog-root-bot/LICENSE | 21 + .../dialog-root-bot/README.md | 3 + .../template-with-new-rg.json | 291 +++++++ .../template-with-preexisting-rg.json | 259 +++++++ .../dialog-root-bot/pom.xml | 249 ++++++ .../AdapterWithErrorHandler.java | 137 ++++ .../sample/dialogrootbot/AdaptiveCard.java | 83 ++ .../bot/sample/dialogrootbot/Application.java | 158 ++++ .../bot/sample/dialogrootbot/Body.java | 135 ++++ .../sample/dialogrootbot/Bots/RootBot.java | 97 +++ .../SkillConversationIdFactory.java | 77 ++ .../dialogrootbot/SkillsConfiguration.java | 77 ++ .../AllowedSkillsClaimsValidator.java | 58 ++ .../controller/SkillController.java | 16 + .../dialogrootbot/dialogs/MainDialog.java | 365 +++++++++ .../middleware/ConsoleLogger.java | 11 + .../dialogrootbot/middleware/Logger.java | 11 + .../middleware/LoggerMiddleware.java | 70 ++ .../src/main/resources/application.properties | 8 + .../src/main/resources/log4j2.json | 18 + .../src/main/resources/welcomeCard.json | 29 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++ .../sample/dialogrootbot/ApplicationTest.java | 19 + .../dialog-skill-bot/.vscode/settings.json | 3 + .../dialog-skill-bot/LICENSE | 21 + .../dialog-skill-bot/README.md | 3 + .../template-with-new-rg.json | 291 +++++++ .../template-with-preexisting-rg.json | 259 +++++++ .../dialog-skill-bot/pom.xml | 248 ++++++ .../sample/dialogskillbot/Application.java | 82 ++ .../SkillAdapterWithErrorHandler.java | 82 ++ .../AllowedCallersClaimsValidator.java | 67 ++ .../sample/dialogskillbot/bots/SkillBot.java | 31 + .../cognitivemodels/flightbooking.json | 339 ++++++++ .../dialogs/ActivityRouterDialog.java | 232 ++++++ .../dialogs/BookingDetails.java | 66 ++ .../dialogskillbot/dialogs/BookingDialog.java | 112 +++ .../dialogs/CancelAndHelpDialog.java | 62 ++ .../dialogs/DateResolverDialog.java | 100 +++ .../dialogs/DialogSkillBotRecognizer.java | 54 ++ .../dialogskillbot/dialogs/Location.java | 64 ++ .../src/main/resources/application.properties | 13 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++ .../manifest/echoskillbot-manifest-1.0.json | 25 + .../dialogskillbot/ApplicationTest.java | 19 + 140 files changed, 14073 insertions(+), 644 deletions(-) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TypedInvokeResponse.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkClient.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkSkill.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryBase.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryOptions.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationReference.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillHandler.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/package-info.java create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsValidator.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/BeginSkillDialogOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCommon.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialogOptions.java create mode 100644 libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillInvokeException.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogTestClient.java create mode 100644 libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java create mode 100644 libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpClient.java create mode 100644 libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/SkillHttpClient.java create mode 100644 libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/ChannelServiceController.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/LICENSE create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/README.md create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillAdapterWithErrorHandler.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/authentication/AllowedSkillsClaimsValidator.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/application.properties create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/log4j2.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/index.html create mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/test/java/com/microsoft/bot/sample/simplerootbot/ApplicationTest.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/LICENSE create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/README.md create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/EchoBot.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/SkillAdapterWithErrorHandler.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/application.properties create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/log4j2.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/index.html create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json create mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/test/java/com/microsoft/bot/sample/echoskillbot/ApplicationTest.java create mode 100644 samples/80.skills-simple-bot-to-bot/README.md create mode 100644 samples/81.skills-skilldialog/README.md create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/.vscode/settings.json create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/LICENSE create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/README.md create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/pom.xml create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdapterWithErrorHandler.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdaptiveCard.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Body.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillsConfiguration.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/authentication/AllowedSkillsClaimsValidator.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/controller/SkillController.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/dialogs/MainDialog.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/ConsoleLogger.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/Logger.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/LoggerMiddleware.java create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/application.properties create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/log4j2.json create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/welcomeCard.json create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/index.html create mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/test/java/com/microsoft/bot/sample/dialogrootbot/ApplicationTest.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/.vscode/settings.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/LICENSE create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/README.md create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/pom.xml create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/SkillAdapterWithErrorHandler.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/bots/SkillBot.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/cognitivemodels/flightbooking.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/ActivityRouterDialog.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDetails.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDialog.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/CancelAndHelpDialog.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DateResolverDialog.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DialogSkillBotRecognizer.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/Location.java create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/application.properties create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/log4j2.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/index.html create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json create mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/test/java/com/microsoft/bot/sample/dialogskillbot/ApplicationTest.java diff --git a/etc/bot-checkstyle.xml b/etc/bot-checkstyle.xml index da9b3f275..963f7ccf5 100644 --- a/etc/bot-checkstyle.xml +++ b/etc/bot-checkstyle.xml @@ -86,6 +86,9 @@ + + + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index 60987f678..58b32a75f 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -87,6 +87,9 @@ public CompletableFuture onTurn(TurnContext turnContext) { case ActivityTypes.INSTALLATION_UPDATE: return onInstallationUpdate(turnContext); + case ActivityTypes.END_OF_CONVERSATION: + return onEndOfConversationActivity(turnContext); + case ActivityTypes.TYPING: return onTypingActivity(turnContext); @@ -565,6 +568,17 @@ protected CompletableFuture onInstallationUpdateRemove(TurnContext turnCon return CompletableFuture.completedFuture(null); } + /** + * Override this in a derived class to provide logic specific to + * ActivityTypes.END_OF_CONVERSATION activities. + * + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + protected CompletableFuture onEndOfConversationActivity(TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + /** * Override this in a derived class to provide logic specific to * ActivityTypes.Typing activities. diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 60e0e8f44..eb8ed6491 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -26,6 +26,7 @@ import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; import com.microsoft.bot.connector.authentication.MicrosoftGovernmentAppCredentials; import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; +import com.microsoft.bot.connector.authentication.SkillValidation; import com.microsoft.bot.connector.rest.RestConnectorClient; import com.microsoft.bot.connector.rest.RestOAuthClient; import com.microsoft.bot.schema.AadResourceUrls; @@ -438,7 +439,10 @@ public CompletableFuture processActivity(ClaimsIdentity identity // The OAuthScope is also stored on the TurnState to get the correct // AppCredentials if fetching a token is required. - String scope = getBotFrameworkOAuthScope(); + String scope = SkillValidation.isSkillClaim(identity.claims()) + ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(identity.claims())) + : getBotFrameworkOAuthScope(); + context.getTurnState().add(OAUTH_SCOPE_KEY, scope); pipelineResult = createConnectorClient(activity.getServiceUrl(), identity, scope) @@ -493,6 +497,13 @@ private CompletableFuture generateCallerId(ClaimsIdentity claimsIdentity return null; } + // Is the activity from another bot? + if (SkillValidation.isSkillClaim(claimsIdentity.claims())) { + return String.format("%s%s", + CallerIdConstants.BOT_TO_BOT_PREFIX, + JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())); + } + // Is the activity from Public Azure? if (channelProvider == null || channelProvider.isPublicAzure()) { return CallerIdConstants.PUBLIC_AZURE_CHANNEL; @@ -1133,7 +1144,13 @@ public CompletableFuture createConnectorClient(String serviceUr } if (botAppIdClaim != null) { - String scope = getBotFrameworkOAuthScope(); + String scope = audience; + + if (StringUtils.isBlank(audience)) { + scope = SkillValidation.isSkillClaim(claimsIdentity.claims()) + ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())) + : getBotFrameworkOAuthScope(); + } return getAppCredentials(botAppIdClaim, scope) .thenCompose(credentials -> getOrCreateConnectorClient(serviceUrl, credentials)); @@ -1236,8 +1253,8 @@ private CompletableFuture getAppCredentials(String appId, String protected CompletableFuture buildAppCredentials(String appId, String scope) { return credentialProvider.getAppPassword(appId).thenApply(appPassword -> { AppCredentials credentials = channelProvider != null && channelProvider.isGovernment() - ? new MicrosoftGovernmentAppCredentials(appId, appPassword, scope) - : new MicrosoftAppCredentials(appId, appPassword); + ? new MicrosoftGovernmentAppCredentials(appId, appPassword, null, scope) + : new MicrosoftAppCredentials(appId, appPassword, null, scope); return credentials; }); } @@ -1368,12 +1385,13 @@ public CompletableFuture getUserToken(TurnContext context, AppCre )); } - OAuthClient client = createOAuthAPIClient(context, oAuthAppCredentials).join(); - return client.getUserToken().getToken( + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(client -> { + return client.getUserToken().getToken( context.getActivity().getFrom().getId(), connectionName, context.getActivity().getChannelId(), magicCode); + }); } /** diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java new file mode 100644 index 000000000..cb3e74ddd --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java @@ -0,0 +1,632 @@ +package com.microsoft.bot.builder; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.connector.authentication.AuthenticationException; +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.AttachmentData; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationParameters; +import com.microsoft.bot.schema.ConversationResourceResponse; +import com.microsoft.bot.schema.ConversationsResult; +import com.microsoft.bot.schema.PagedMembersResult; +import com.microsoft.bot.schema.ResourceResponse; +import com.microsoft.bot.schema.Transcript; + +import org.apache.commons.lang3.NotImplementedException; +import org.apache.commons.lang3.StringUtils; + +/** + * A class to help with the implementation of the Bot Framework protocol. + */ +public class ChannelServiceHandler { + + private ChannelProvider channelProvider; + + private final AuthenticationConfiguration authConfiguration; + private final CredentialProvider credentialProvider; + + /** + * Initializes a new instance of the {@link ChannelServiceHandler} class, + * using a credential provider. + * + * @param credentialProvider The credential provider. + * @param authConfiguration The authentication configuration. + * @param channelProvider The channel provider. + */ + public ChannelServiceHandler( + CredentialProvider credentialProvider, + AuthenticationConfiguration authConfiguration, + ChannelProvider channelProvider) { + + if (credentialProvider == null) { + throw new IllegalArgumentException("credentialprovider cannot be nul"); + } + + if (authConfiguration == null) { + throw new IllegalArgumentException("authConfiguration cannot be nul"); + } + + this.credentialProvider = credentialProvider; + this.authConfiguration = authConfiguration; + this.channelProvider = channelProvider; + } + + /** + * Sends an activity to the end of a conversation. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param activity The activity to send. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleSendToConversation( + String authHeader, + String conversationId, + Activity activity) { + + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onSendToConversation(claimsIdentity, conversationId, activity); + }); + } + + /** + * Sends a reply to an activity. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param activityId The activity Id the reply is to. + * @param activity The activity to send. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleReplyToActivity( + String authHeader, + String conversationId, + String activityId, + Activity activity) { + + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onReplyToActivity(claimsIdentity, conversationId, activityId, activity); + }); + } + + /** + * Edits a previously sent existing activity. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param activityId The activity Id to update. + * @param activity The replacement activity. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleUpdateActivity( + String authHeader, + String conversationId, + String activityId, + Activity activity) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onUpdateActivity(claimsIdentity, conversationId, activityId, activity); + }); + } + + /** + * Deletes an existing activity. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param activityId The activity Id. + * + * @return A {@link CompletableFuture} representing the result of + * the asynchronous operation. + */ + public CompletableFuture handleDeleteActivity(String authHeader, String conversationId, String activityId) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onDeleteActivity(claimsIdentity, conversationId, activityId); + }); + } + + /** + * Enumerates the members of an activity. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param activityId The activity Id. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture> handleGetActivityMembers( + String authHeader, + String conversationId, + String activityId) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onGetActivityMembers(claimsIdentity, conversationId, activityId); + }); + } + + /** + * Create a new Conversation. + * + * @param authHeader The authentication header. + * @param parameters Parameters to create the conversation from. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleCreateConversation( + String authHeader, + ConversationParameters parameters) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onCreateConversation(claimsIdentity, parameters); + }); + } + + /** + * Lists the Conversations in which the bot has participated. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param continuationToken A skip or continuation token. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleGetConversations( + String authHeader, + String conversationId, + String continuationToken) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onGetConversations(claimsIdentity, conversationId, continuationToken); + }); + } + + /** + * Enumerates the members of a conversation. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture> handleGetConversationMembers( + String authHeader, + String conversationId) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onGetConversationMembers(claimsIdentity, conversationId); + }); + } + + /** + * Enumerates the members of a conversation one page at a time. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param pageSize Suggested page size. + * @param continuationToken A continuation token. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleGetConversationPagedMembers( + String authHeader, + String conversationId, + Integer pageSize, + String continuationToken) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onGetConversationPagedMembers(claimsIdentity, conversationId, pageSize, continuationToken); + }); + } + + /** + * Deletes a member from a conversation. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param memberId Id of the member to delete from this + * conversation. + * + * @return A {@link CompletableFuture} representing the + * asynchronous operation. + */ + public CompletableFuture handleDeleteConversationMember( + String authHeader, + String conversationId, + String memberId) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onDeleteConversationMember(claimsIdentity, conversationId, memberId); + }); + } + + /** + * Uploads the historic activities of the conversation. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param transcript Transcript of activities. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleSendConversationHistory( + String authHeader, + String conversationId, + Transcript transcript) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onSendConversationHistory(claimsIdentity, conversationId, transcript); + }); + } + + /** + * Stores data in a compliant store when dealing with enterprises. + * + * @param authHeader The authentication header. + * @param conversationId The conversation Id. + * @param attachmentUpload Attachment data. + * + * @return A {@link CompletableFuture{TResult}} representing the + * result of the asynchronous operation. + */ + public CompletableFuture handleUploadAttachment( + String authHeader, + String conversationId, + AttachmentData attachmentUpload) { + return authenticate(authHeader).thenCompose(claimsIdentity -> { + return onUploadAttachment(claimsIdentity, conversationId, attachmentUpload); + }); + } + + /** + * SendToConversation() API for Skill. + * + * This method allows you to send an activity to the end of a conversation. + * This is slightly different from ReplyToActivity(). * + * SendToConversation(conversationId) - will append the activity to the end + * of the conversation according to the timestamp or semantics of the + * channel. * ReplyToActivity(conversationId,ActivityId) - adds the + * activity as a reply to another activity, if the channel supports it. If + * the channel does not support nested replies, ReplyToActivity falls back + * to SendToConversation. Use ReplyToActivity when replying to a specific + * activity in the conversation. Use SendToConversation in all other cases. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId conversationId. + * @param activity Activity to send. + * + * @return task for a resource response. + */ + protected CompletableFuture onSendToConversation( + ClaimsIdentity claimsIdentity, + String conversationId, + Activity activity) { + throw new NotImplementedException("onSendToConversation is not implemented"); + } + + /** + * OnReplyToActivity() API. + * + * Override this method allows to reply to an Activity. This is slightly + * different from SendToConversation(). * + * SendToConversation(conversationId) - will append the activity to the end + * of the conversation according to the timestamp or semantics of the + * channel. * ReplyToActivity(conversationId,ActivityId) - adds the + * activity as a reply to another activity, if the channel supports it. If + * the channel does not support nested replies, ReplyToActivity falls back + * to SendToConversation. Use ReplyToActivity when replying to a specific + * activity in the conversation. Use SendToConversation in all other cases. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param activityId activityId the reply is to (OPTONAL). + * @param activity Activity to send. + * + * @return task for a resource response. + */ + protected CompletableFuture onReplyToActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId, + Activity activity) { + throw new NotImplementedException("onReplyToActivity is not implemented"); + } + + /** + * OnUpdateActivity() API. + * + * Override this method to edit a previously sent existing activity. Some + * channels allow you to edit an existing activity to reflect the new state + * of a bot conversation. For example, you can remove buttons after someone + * has clicked "Approve" button. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param activityId activityId to update. + * @param activity replacement Activity. + * + * @return task for a resource response. + */ + protected CompletableFuture onUpdateActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId, + Activity activity) { + throw new NotImplementedException("onUpdateActivity is not implemented"); + } + + /** + * OnDeleteActivity() API. + * + * Override this method to Delete an existing activity. Some channels allow + * you to delete an existing activity, and if successful this method will + * remove the specified activity. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param activityId activityId to delete. + * + * @return task for a resource response. + */ + protected CompletableFuture onDeleteActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId) { + throw new NotImplementedException("onDeleteActivity is not implemented"); + } + + /** + * OnGetActivityMembers() API. + * + * Override this method to enumerate the members of an activity. This REST + * API takes a ConversationId and a ActivityId, returning an array of + * ChannelAccount Objects representing the members of the particular + * activity in the conversation. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param activityId Activity D. + * + * @return task with result. + */ + protected CompletableFuture> onGetActivityMembers( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId) { + throw new NotImplementedException("onGetActivityMembers is not implemented"); + } + + /** + * CreateConversation() API. + * + * Override this method to create a new Conversation. POST to this method + * with a * Bot being the bot creating the conversation * IsGroup set to + * true if this is not a direct message (default instanceof false) * Array + * containing the members to include in the conversation The return value + * is a ResourceResponse which contains a conversation D which is suitable + * for use in the message payload and REST API URIs. Most channels only + * support the semantics of bots initiating a direct message conversation. + * An example of how to do that would be: var resource = + * connector.getconversations().CreateConversation(new + * ConversationParameters(){ Bot = bot, members = new ChannelAccount[] { + * new ChannelAccount("user1") } ); + * connect.getConversations().OnSendToConversation(resource.getId(), new + * Activity() ... ) ; end. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param parameters Parameters to create the conversation + * from. + * + * @return task for a conversation resource response. + */ + protected CompletableFuture onCreateConversation( + ClaimsIdentity claimsIdentity, + ConversationParameters parameters) { + throw new NotImplementedException("onCreateConversation is not implemented"); + } + + /** + * OnGetConversations() API for Skill. + * + * Override this method to list the Conversations in which this bot has + * participated. GET from this method with a skip token The return value is + * a ConversationsResult, which contains an array of ConversationMembers + * and a skip token. If the skip token is not empty, then there are further + * values to be returned. Call this method again with the returned token to + * get more values. Each ConversationMembers Object contains the D of the + * conversation and an array of ChannelAccounts that describe the members + * of the conversation. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId conversationId. + * @param continuationToken skip or continuation token. + * + * @return task for ConversationsResult. + */ + protected CompletableFuture onGetConversations( + ClaimsIdentity claimsIdentity, + String conversationId, + String continuationToken) { + throw new NotImplementedException("onGetConversationMembers is not implemented"); + } + + /** + * GetConversationMembers() API for Skill. + * + * Override this method to enumerate the members of a conversation. This + * REST API takes a ConversationId and returns an array of ChannelAccount + * Objects representing the members of the conversation. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * + * @return task for a response. + */ + protected CompletableFuture> onGetConversationMembers( + ClaimsIdentity claimsIdentity, + String conversationId) { + throw new NotImplementedException("onGetConversationMembers is not implemented"); + } + + /** + * GetConversationPagedMembers() API for Skill. + * + * Override this method to enumerate the members of a conversation one page + * at a time. This REST API takes a ConversationId. Optionally a pageSize + * and/or continuationToken can be provided. It returns a + * PagedMembersResult, which contains an array of ChannelAccounts + * representing the members of the conversation and a continuation token + * that can be used to get more values. One page of ChannelAccounts records + * are returned with each call. The number of records in a page may vary + * between channels and calls. The pageSize parameter can be used as a + * suggestion. If there are no additional results the response will not + * contain a continuation token. If there are no members in the + * conversation the Members will be empty or not present in the response. A + * response to a request that has a continuation token from a prior request + * may rarely return members from a previous request. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param pageSize Suggested page size. + * @param continuationToken Continuation Token. + * + * @return task for a response. + */ + protected CompletableFuture onGetConversationPagedMembers( + ClaimsIdentity claimsIdentity, + String conversationId, + Integer pageSize, + String continuationToken) { + throw new NotImplementedException("onGetConversationPagedMembers is not implemented"); + } + + /** + * DeleteConversationMember() API for Skill. + * + * Override this method to deletes a member from a conversation. This REST + * API takes a ConversationId and a memberId (of type String) and removes + * that member from the conversation. If that member was the last member of + * the conversation, the conversation will also be deleted. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param memberId D of the member to delete from this + * conversation. + * + * @return task. + */ + protected CompletableFuture onDeleteConversationMember( + ClaimsIdentity claimsIdentity, + String conversationId, + String memberId) { + throw new NotImplementedException("onDeleteConversationMember is not implemented"); + } + + /** + * SendConversationHistory() API for Skill. + * + * Override this method to this method allows you to upload the historic + * activities to the conversation. Sender must ensure that the historic + * activities have unique ids and appropriate timestamps. The ids are used + * by the client to deal with duplicate activities and the timestamps are + * used by the client to render the activities in the right order. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param transcript Transcript of activities. + * + * @return task for a resource response. + */ + protected CompletableFuture onSendConversationHistory( + ClaimsIdentity claimsIdentity, + String conversationId, + Transcript transcript) { + throw new NotImplementedException("onSendConversationHistory is not implemented"); + } + + /** + * UploadAttachment() API. + * + * Override this method to store data in a compliant store when dealing + * with enterprises. The response is a ResourceResponse which contains an + * AttachmentId which is suitable for using with the attachments API. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation D. + * @param attachmentUpload Attachment data. + * + * @return task with result. + */ + protected CompletableFuture onUploadAttachment( + ClaimsIdentity claimsIdentity, + String conversationId, + AttachmentData attachmentUpload) { + throw new NotImplementedException("onUploadAttachment is not implemented"); + } + + /** + * Helper to authenticate the header. + * + * This code is very similar to the code in + * {@link JwtTokenValidation#authenticateRequest(Activity, String, + * CredentialProvider, ChannelProvider, AuthenticationConfiguration, + * HttpClient)} , we should move this code somewhere in that library when + * we refactor auth, for now we keep it private to avoid adding more public + * static functions that we will need to deprecate later. + */ + private CompletableFuture authenticate(String authHeader) { + if (StringUtils.isEmpty(authHeader)) { + return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> { + if (!isAuthDisabled) { + return Async.completeExceptionally( + // No auth header. Auth is required. Request is not authorized. + new AuthenticationException("No auth header, Auth is required. Request is not authorized") + ); + } + + // In the scenario where auth is disabled, we still want to have the + // IsAuthenticated flag set in the ClaimsIdentity. + // To do this requires adding in an empty claim. + // Since ChannelServiceHandler calls are always a skill callback call, we set the skill claim too. + return CompletableFuture.completedFuture(SkillValidation.createAnonymousSkillClaim()); + }); + } + + // Validate the header and extract claims. + return JwtTokenValidation.validateAuthHeader( + authHeader, credentialProvider, getChannelProvider(), "unknown", null, authConfiguration); + } + /** + * Gets the channel provider that implements {@link ChannelProvider} . + * @return the ChannelProvider value as a getChannelProvider(). + */ + protected ChannelProvider getChannelProvider() { + return this.channelProvider; + } + +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/InvokeResponse.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/InvokeResponse.java index 7a4af8d6b..483c23de4 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/InvokeResponse.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/InvokeResponse.java @@ -10,6 +10,7 @@ * Serialized content from the Body property. */ public class InvokeResponse { + /** * The POST that is generated in response to the incoming Invoke Activity will * have the HTTP Status code specified by this field. @@ -23,7 +24,7 @@ public class InvokeResponse { /** * Initializes new instance of InvokeResponse. - * + * * @param withStatus The invoke response status. * @param withBody The invoke response body. */ @@ -34,7 +35,7 @@ public InvokeResponse(int withStatus, Object withBody) { /** * Gets the HTTP status code for the response. - * + * * @return The HTTP status code. */ public int getStatus() { @@ -43,7 +44,7 @@ public int getStatus() { /** * Sets the HTTP status code for the response. - * + * * @param withStatus The HTTP status code. */ public void setStatus(int withStatus) { @@ -52,7 +53,7 @@ public void setStatus(int withStatus) { /** * Gets the body content for the response. - * + * * @return The body content. */ public Object getBody() { @@ -61,10 +62,20 @@ public Object getBody() { /** * Sets the body content for the response. - * + * * @param withBody The body content. */ public void setBody(Object withBody) { body = withBody; } + + /** + * Returns if the status of the request was successful. + * @return True if the status code is successful, false if not. + */ + @SuppressWarnings("MagicNumber") + public boolean getIsSuccessStatusCode() { + return status >= 200 && status <= 299; + } + } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java index bf490d079..500bc12a2 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java @@ -4,6 +4,8 @@ package com.microsoft.bot.builder; import com.microsoft.bot.connector.ExecutorFactory; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.SkillValidation; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ConversationReference; @@ -73,7 +75,7 @@ public ShowTypingMiddleware(long withDelay, long withPeriod) throws IllegalArgum */ @Override public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { - if (!turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { + if (!turnContext.getActivity().isType(ActivityTypes.MESSAGE) || isSkillBot(turnContext)) { return next.next(); } @@ -83,6 +85,16 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next return next.next().thenAccept(result -> sendFuture.cancel(true)); } + private static Boolean isSkillBot(TurnContext turnContext) { + Object identity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (identity instanceof ClaimsIdentity) { + ClaimsIdentity claimsIdentity = (ClaimsIdentity) identity; + return SkillValidation.isSkillClaim(claimsIdentity.claims()); + } else { + return false; + } + } + private static CompletableFuture sendTyping( TurnContext turnContext, long delay, diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TypedInvokeResponse.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TypedInvokeResponse.java new file mode 100644 index 000000000..7ac36c695 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TypedInvokeResponse.java @@ -0,0 +1,41 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +/** + * Tuple class containing an HTTP Status Code and a JSON Serializable object. + * The HTTP Status code is, in the invoke activity scenario, what will be set in + * the resulting POST. The Body of the resulting POST will be the JSON + * Serialized content from the Body property. + * @param The type for the body of the TypedInvokeResponse. + */ +public class TypedInvokeResponse extends InvokeResponse { + + /** + * Initializes new instance of InvokeResponse. + * + * @param withStatus The invoke response status. + * @param withBody The invoke response body. + */ + public TypedInvokeResponse(int withStatus, T withBody) { + super(withStatus, withBody); + } + + /** + * Sets the body with a typed value. + * @param withBody the typed value to set the body to. + */ + public void setTypedBody(T withBody) { + super.setBody(withBody); + } + + /** + * Gets the body content for the response. + * + * @return The body content. + */ + public T getTypedBody() { + return (T) super.getBody(); + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkClient.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkClient.java new file mode 100644 index 000000000..0db9994bd --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkClient.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import java.net.URI; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.TypedInvokeResponse; +import com.microsoft.bot.schema.Activity; + +/** + * A Bot Framework client. + */ +public abstract class BotFrameworkClient { + + // /** + // * Forwards an activity to a skill (bot). + // * + // * NOTE: Forwarding an activity to a skill will flush UserState and + // * ConversationState changes so that skill has accurate state. + // * + // * @param fromBotId The MicrosoftAppId of the bot sending the + // * activity. + // * @param toBotId The MicrosoftAppId of the bot receiving + // * the activity. + // * @param toUrl The URL of the bot receiving the activity. + // * @param serviceUrl The callback Url for the skill host. + // * @param conversationId A conversation ID to use for the + // * conversation with the skill. + // * @param activity The {@link Activity} to send to forward. + // * + // * @return task with optional invokeResponse. + // */ + // public abstract CompletableFuture postActivity( + // String fromBotId, + // String toBotId, + // URI toUrl, + // URI serviceUrl, + // String conversationId, + // Activity activity); + + /** + * Forwards an activity to a skill (bot). + * + * NOTE: Forwarding an activity to a skill will flush UserState and + * ConversationState changes so that skill has accurate state. + * + * @param fromBotId The MicrosoftAppId of the bot sending the + * activity. + * @param toBotId The MicrosoftAppId of the bot receiving + * the activity. + * @param toUri The URL of the bot receiving the activity. + * @param serviceUri The callback Url for the skill host. + * @param conversationId A conversation ID to use for the + * conversation with the skill. + * @param activity The {@link Activity} to send to forward. + * @param type The type for the response body to contain, can't really use due to type erasure + * in Java. + * @param The type for the TypedInvokeResponse body to contain. + * + * @return task with optional invokeResponse. + */ + public abstract CompletableFuture> postActivity( + String fromBotId, + String toBotId, + URI toUri, + URI serviceUri, + String conversationId, + Activity activity, + Class type); +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkSkill.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkSkill.java new file mode 100644 index 000000000..99f4f97f1 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/BotFrameworkSkill.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import java.net.URI; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Registration for a BotFrameworkHttpProtocol super. Skill endpoint. + */ +public class BotFrameworkSkill { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "appId") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String appId; + + @JsonProperty(value = "skillEndpoint") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private URI skillEndpoint; + + /** + * Gets Id of the skill. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * Sets Id of the skill. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + /** + * Gets appId of the skill. + * @return the AppId value as a String. + */ + public String getAppId() { + return this.appId; + } + + /** + * Sets appId of the skill. + * @param withAppId The AppId value. + */ + public void setAppId(String withAppId) { + this.appId = withAppId; + } + /** + * Gets /api/messages endpoint for the skill. + * @return the SkillEndpoint value as a Uri. + */ + public URI getSkillEndpoint() { + return this.skillEndpoint; + } + + /** + * Sets /api/messages endpoint for the skill. + * @param withSkillEndpoint The SkillEndpoint value. + */ + public void setSkillEndpoint(URI withSkillEndpoint) { + this.skillEndpoint = withSkillEndpoint; + } +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryBase.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryBase.java new file mode 100644 index 000000000..8e912ba73 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryBase.java @@ -0,0 +1,93 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.schema.ConversationReference; + +import org.apache.commons.lang3.NotImplementedException; + +/** + * Defines the interface of a factory that is used to create unique conversation + * IDs for skill conversations. + */ +public abstract class SkillConversationIdFactoryBase { + + /** + * Creates a conversation ID for a skill conversation super. on the + * caller's {@link ConversationReference} . + * + * @param conversationReference The skill's caller {@link ConversationReference} . + * + * @return A unique conversation ID used to communicate with the + * skill. + * + * It should be possible to use the returned String on a request URL and it + * should not contain special characters. + */ + public CompletableFuture createSkillConversationId(ConversationReference conversationReference) { + throw new NotImplementedException("createSkillConversationId"); + } + + /** + * Creates a conversation id for a skill conversation. + * + * @param options A {@link SkillConversationIdFactoryOptions} + * instance containing parameters for creating the conversation ID. + * + * @return A unique conversation ID used to communicate with the skill. + * + * It should be possible to use the returned String on a request URL and it + * should not contain special characters. + */ + public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { + throw new NotImplementedException("createSkillConversationId"); + } + + /** + * Gets the {@link ConversationReference} created using + * {@link + * CreateSkillConversationId(Microsoft#getBot()#getSchema()#getConversatio + * Reference(),System#getThreading()#getCancellationToken())} for a + * skillConversationId. + * + * @param skillConversationId A skill conversationId created using {@link + * CreateSkillConversationId(Microsoft#getBot()#getSchema()#getConversatio + * Reference(),System#getThreading()#getCancellationToken())} . + * + * @return The caller's {@link ConversationReference} for a skillConversationId. null if not found. + */ + public CompletableFuture getConversationReference(String skillConversationId) { + throw new NotImplementedException("getConversationReference"); + } + + /** + * Gets the {@link SkillConversationReference} used during {@link + * CreateSkillConversationId(SkillConversationIdFactoryOptions,System#getT + * reading()#getCancellationToken())} for a skillConversationId. + * + * @param skillConversationId A skill conversationId created using {@link + * CreateSkillConversationId(SkillConversationIdFactoryOptions,System#getT + * reading()#getCancellationToken())} . + * + * @return The caller's {@link ConversationReference} for a skillConversationId, with originatingAudience. + * Null if not found. + */ + public CompletableFuture getSkillConversationReference(String skillConversationId) { + throw new NotImplementedException("getSkillConversationReference"); + } + + /** + * Deletes a {@link ConversationReference} . + * + * @param skillConversationId A skill conversationId created using {@link + * CreateSkillConversationId(SkillConversationIdFactoryOptions,System#getT + * reading()#getCancellationToken())} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + public abstract CompletableFuture deleteConversationReference(String skillConversationId); +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryOptions.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryOptions.java new file mode 100644 index 000000000..c0ffde7ef --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactoryOptions.java @@ -0,0 +1,88 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import com.microsoft.bot.schema.Activity; + +/** + * A class defining the parameters used in + * {@link SkillConversationIdFactoryBase#createSkillConversationId(SkillConversationI + * FactoryOptions,System#getThreading()#getCancellationToken())} . + */ +public class SkillConversationIdFactoryOptions { + + private String fromBotOAuthScope; + + private String fromBotId; + + private Activity activity; + + private BotFrameworkSkill botFrameworkSkill; + + /** + * Gets the oauth audience scope, used during token retrieval + * (either https://api.getbotframework().com or bot app id). + * @return the FromBotOAuthScope value as a String. + */ + public String getFromBotOAuthScope() { + return this.fromBotOAuthScope; + } + + /** + * Sets the oauth audience scope, used during token retrieval + * (either https://api.getbotframework().com or bot app id). + * @param withFromBotOAuthScope The FromBotOAuthScope value. + */ + public void setFromBotOAuthScope(String withFromBotOAuthScope) { + this.fromBotOAuthScope = withFromBotOAuthScope; + } + + /** + * Gets the id of the parent bot that is messaging the skill. + * @return the FromBotId value as a String. + */ + public String getFromBotId() { + return this.fromBotId; + } + + /** + * Sets the id of the parent bot that is messaging the skill. + * @param withFromBotId The FromBotId value. + */ + public void setFromBotId(String withFromBotId) { + this.fromBotId = withFromBotId; + } + + /** + * Gets the activity which will be sent to the skill. + * @return the Activity value as a getActivity(). + */ + public Activity getActivity() { + return this.activity; + } + + /** + * Sets the activity which will be sent to the skill. + * @param withActivity The Activity value. + */ + public void setActivity(Activity withActivity) { + this.activity = withActivity; + } + /** + * Gets the skill to create the conversation Id for. + * @return the BotFrameworkSkill value as a getBotFrameworkSkill(). + */ + public BotFrameworkSkill getBotFrameworkSkill() { + return this.botFrameworkSkill; + } + + /** + * Sets the skill to create the conversation Id for. + * @param withBotFrameworkSkill The BotFrameworkSkill value. + */ + public void setBotFrameworkSkill(BotFrameworkSkill withBotFrameworkSkill) { + this.botFrameworkSkill = withBotFrameworkSkill; + } +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationReference.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationReference.java new file mode 100644 index 000000000..5e538fbe7 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationReference.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import com.microsoft.bot.schema.ConversationReference; + +/** + * A conversation reference type for skills. + */ +public class SkillConversationReference { + + private ConversationReference conversationReference; + + private String oAuthScope; + + /** + * Gets the conversation reference. + * @return the ConversationReference value as a getConversationReference(). + */ + public ConversationReference getConversationReference() { + return this.conversationReference; + } + + /** + * Sets the conversation reference. + * @param withConversationReference The ConversationReference value. + */ + public void setConversationReference(ConversationReference withConversationReference) { + this.conversationReference = withConversationReference; + } + + /** + * Gets the OAuth scope. + * @return the OAuthScope value as a String. + */ + public String getOAuthScope() { + return this.oAuthScope; + } + + /** + * Sets the OAuth scope. + * @param withOAuthScope The OAuthScope value. + */ + public void setOAuthScope(String withOAuthScope) { + this.oAuthScope = withOAuthScope; + } + +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillHandler.java new file mode 100644 index 000000000..e2107182f --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillHandler.java @@ -0,0 +1,340 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder.skills; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.atomic.AtomicReference; + +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.CallerIdConstants; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.ResourceResponse; + +import org.apache.commons.lang3.NotImplementedException; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * A Bot Framework Handler for skills. + */ +public class SkillHandler extends ChannelServiceHandler { + + /** + * The skill conversation reference. + */ + public static final String SKILL_CONVERSATION_REFERENCE_KEY = + "com.microsoft.bot.builder.skills.SkillConversationReference"; + + private final BotAdapter adapter; + private final Bot bot; + private final SkillConversationIdFactoryBase conversationIdFactory; + + /** + * The slf4j Logger to use. Note that slf4j is configured by providing Log4j + * dependencies in the POM, and corresponding Log4j configuration in the + * 'resources' folder. + */ + private Logger logger = LoggerFactory.getLogger(SkillHandler.class); + + /** + * Initializes a new instance of the {@link SkillHandler} class, using a + * credential provider. + * + * @param adapter An instance of the {@link BotAdapter} that will handle the request. + * @param bot The {@link IBot} instance. + * @param conversationIdFactory A {@link SkillConversationIdFactoryBase} to unpack the conversation ID and + * map it to the calling bot. + * @param credentialProvider The credential provider. + * @param authConfig The authentication configuration. + * @param channelProvider The channel provider. + * + * Use a {@link MiddlewareSet} Object to add multiple middleware components + * in the constructor. Use the Use({@link Middleware} ) method to add + * additional middleware to the adapter after construction. + */ + public SkillHandler( + BotAdapter adapter, + Bot bot, + SkillConversationIdFactoryBase conversationIdFactory, + CredentialProvider credentialProvider, + AuthenticationConfiguration authConfig, + ChannelProvider channelProvider + ) { + + super(credentialProvider, authConfig, channelProvider); + + if (adapter == null) { + throw new IllegalArgumentException("adapter cannot be null"); + } + + if (bot == null) { + throw new IllegalArgumentException("bot cannot be null"); + } + + if (conversationIdFactory == null) { + throw new IllegalArgumentException("conversationIdFactory cannot be null"); + } + + this.adapter = adapter; + this.bot = bot; + this.conversationIdFactory = conversationIdFactory; + } + + /** + * SendToConversation() API for Skill. + * + * This method allows you to send an activity to the end of a conversation. + * This is slightly different from ReplyToActivity(). * + * SendToConversation(conversationId) - will append the activity to the end + * of the conversation according to the timestamp or semantics of the + * channel. * ReplyToActivity(conversationId,ActivityId) - adds the + * activity as a reply to another activity, if the channel supports it. If + * the channel does not support nested replies, ReplyToActivity falls back + * to SendToConversation. Use ReplyToActivity when replying to a specific + * activity in the conversation. Use SendToConversation in all other cases. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId conversationId. + * @param activity Activity to send. + * + * @return task for a resource response. + */ + @Override + protected CompletableFuture onSendToConversation( + ClaimsIdentity claimsIdentity, + String conversationId, + Activity activity) { + return processActivity(claimsIdentity, conversationId, null, activity); + } + + /** + * ReplyToActivity() API for Skill. + * + * This method allows you to reply to an activity. This is slightly + * different from SendToConversation(). * + * SendToConversation(conversationId) - will append the activity to the end + * of the conversation according to the timestamp or semantics of the + * channel. * ReplyToActivity(conversationId,ActivityId) - adds the + * activity as a reply to another activity, if the channel supports it. If + * the channel does not support nested replies, ReplyToActivity falls back + * to SendToConversation. Use ReplyToActivity when replying to a specific + * activity in the conversation. Use SendToConversation in all other cases. + * + * @param claimsIdentity claimsIdentity for the bot, should have + * AudienceClaim, AppIdClaim and ServiceUrlClaim. + * @param conversationId Conversation ID. + * @param activityId activityId the reply is to (OPTIONAL). + * @param activity Activity to send. + * + * @return task for a resource response. + */ + @Override + protected CompletableFuture onReplyToActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId, + Activity activity) { + return processActivity(claimsIdentity, conversationId, activityId, activity); + } + + /** + */ + @Override + protected CompletableFuture onDeleteActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId) { + + SkillConversationReference skillConversationReference = getSkillConversationReference(conversationId).join(); + + BotCallbackHandler callback = turnContext -> { + turnContext.getTurnState().add(SKILL_CONVERSATION_REFERENCE_KEY, skillConversationReference); + return turnContext.deleteActivity(activityId); + }; + + return adapter.continueConversation(claimsIdentity, + skillConversationReference.getConversationReference(), + skillConversationReference.getOAuthScope(), + callback); + } + + /** + */ + @Override + protected CompletableFuture onUpdateActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId, + Activity activity) { + SkillConversationReference skillConversationReference = getSkillConversationReference(conversationId).join(); + + AtomicReference resourceResponse = new AtomicReference(); + + BotCallbackHandler callback = turnContext -> { + turnContext.getTurnState().add(SKILL_CONVERSATION_REFERENCE_KEY, skillConversationReference); + activity.applyConversationReference(skillConversationReference.getConversationReference()); + turnContext.getActivity().setId(activityId); + String callerId = String.format("%s%s", + CallerIdConstants.BOT_TO_BOT_PREFIX, + JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())); + turnContext.getActivity().setCallerId(callerId); + resourceResponse.set(turnContext.updateActivity(activity).join()); + return CompletableFuture.completedFuture(null); + }; + + adapter.continueConversation(claimsIdentity, + skillConversationReference.getConversationReference(), + skillConversationReference.getOAuthScope(), + callback); + + if (resourceResponse.get() != null) { + return CompletableFuture.completedFuture(resourceResponse.get()); + } else { + return CompletableFuture.completedFuture(new ResourceResponse(UUID.randomUUID().toString())); + } + } + + private static void applyEoCToTurnContextActivity(TurnContext turnContext, Activity endOfConversationActivity) { + // transform the turnContext.Activity to be the EndOfConversation. + turnContext.getActivity().setType(endOfConversationActivity.getType()); + turnContext.getActivity().setText(endOfConversationActivity.getText()); + turnContext.getActivity().setCode(endOfConversationActivity.getCode()); + + turnContext.getActivity().setReplyToId(endOfConversationActivity.getReplyToId()); + turnContext.getActivity().setValue(endOfConversationActivity.getValue()); + turnContext.getActivity().setEntities(endOfConversationActivity.getEntities()); + turnContext.getActivity().setLocale(endOfConversationActivity.getLocale()); + turnContext.getActivity().setLocalTimestamp(endOfConversationActivity.getLocalTimestamp()); + turnContext.getActivity().setTimestamp(endOfConversationActivity.getTimestamp()); + turnContext.getActivity().setChannelData(endOfConversationActivity.getChannelData()); + for (Map.Entry entry : endOfConversationActivity.getProperties().entrySet()) { + turnContext.getActivity().setProperties(entry.getKey(), entry.getValue()); + } + } + + private static void applyEventToTurnContextActivity(TurnContext turnContext, Activity eventActivity) { + // transform the turnContext.Activity to be the EventActivity. + turnContext.getActivity().setType(eventActivity.getType()); + turnContext.getActivity().setName(eventActivity.getName()); + turnContext.getActivity().setValue(eventActivity.getValue()); + turnContext.getActivity().setRelatesTo(eventActivity.getRelatesTo()); + + turnContext.getActivity().setReplyToId(eventActivity.getReplyToId()); + turnContext.getActivity().setValue(eventActivity.getValue()); + turnContext.getActivity().setEntities(eventActivity.getEntities()); + turnContext.getActivity().setLocale(eventActivity.getLocale()); + turnContext.getActivity().setLocalTimestamp(eventActivity.getLocalTimestamp()); + turnContext.getActivity().setTimestamp(eventActivity.getTimestamp()); + turnContext.getActivity().setChannelData(eventActivity.getChannelData()); + for (Map.Entry entry : eventActivity.getProperties().entrySet()) { + turnContext.getActivity().setProperties(entry.getKey(), entry.getValue()); + } + } + + private CompletableFuture getSkillConversationReference(String conversationId) { + + SkillConversationReference skillConversationReference; + try { + skillConversationReference = conversationIdFactory.getSkillConversationReference(conversationId).join(); + } catch (NotImplementedException ex) { + if (logger != null) { + logger.warn("Got NotImplementedException when trying to call " + + "GetSkillConversationReference() on the ConversationIdFactory," + + " attempting to use deprecated GetConversationReference() method instead."); + } + + // Attempt to get SkillConversationReference using deprecated method. + // this catch should be removed once we remove the deprecated method. + // We need to use the deprecated method for backward compatibility. + ConversationReference conversationReference = + conversationIdFactory.getConversationReference(conversationId).join(); + skillConversationReference = new SkillConversationReference(); + skillConversationReference.setConversationReference(conversationReference); + if (getChannelProvider() != null && getChannelProvider().isGovernment()) { + skillConversationReference.setOAuthScope( + GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE); + } else { + skillConversationReference.setOAuthScope( + AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE); + } + } + + if (skillConversationReference == null) { + if (logger != null) { + logger.warn( + String.format("Unable to get skill conversation reference for conversationId %s.", conversationId) + ); + } + throw new RuntimeException("Key not found"); + } + + return CompletableFuture.completedFuture(skillConversationReference); + } + + private CompletableFuture processActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String replyToActivityId, + Activity activity) { + + SkillConversationReference skillConversationReference = getSkillConversationReference(conversationId).join(); + + AtomicReference resourceResponse = new AtomicReference(); + + BotCallbackHandler callback = turnContext -> { + turnContext.getTurnState().add(SKILL_CONVERSATION_REFERENCE_KEY, skillConversationReference); + activity.applyConversationReference(skillConversationReference.getConversationReference()); + turnContext.getActivity().setId(replyToActivityId); + String callerId = String.format("%s%s", + CallerIdConstants.BOT_TO_BOT_PREFIX, + JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())); + turnContext.getActivity().setCallerId(callerId); + + switch (activity.getType()) { + case ActivityTypes.END_OF_CONVERSATION: + conversationIdFactory.deleteConversationReference(conversationId).join(); + applyEoCToTurnContextActivity(turnContext, activity); + bot.onTurn(turnContext).join(); + break; + case ActivityTypes.EVENT: + applyEventToTurnContextActivity(turnContext, activity); + bot.onTurn(turnContext).join(); + break; + default: + resourceResponse.set(turnContext.sendActivity(activity).join()); + break; + } + return CompletableFuture.completedFuture(null); + }; + + adapter.continueConversation(claimsIdentity, + skillConversationReference.getConversationReference(), + skillConversationReference.getOAuthScope(), + callback).join(); + + if (resourceResponse.get() != null) { + return CompletableFuture.completedFuture(resourceResponse.get()); + } else { + return CompletableFuture.completedFuture(new ResourceResponse(UUID.randomUUID().toString())); + } + } +} + diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/package-info.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/package-info.java new file mode 100644 index 000000000..4bf1c6ad6 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for Bot-Builder. + */ +package com.microsoft.bot.builder.skills; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 2226517c6..6137eb730 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -9,7 +9,7 @@ import com.microsoft.bot.connector.authentication.AppCredentials; import com.microsoft.bot.schema.*; import org.apache.commons.lang3.StringUtils; - +import org.junit.rules.ExpectedException; import java.time.OffsetDateTime; import java.time.ZoneId; @@ -271,7 +271,7 @@ public CompletableFuture sendActivities(TurnContext context, Thread.sleep(delayMs); } catch (InterruptedException e) { } - } else if (activity.getType() == ActivityTypes.TRACE) { + } else if (activity.getType().equals(ActivityTypes.TRACE)) { if (sendTraceActivity) { synchronized (botReplies) { botReplies.add(activity); @@ -506,8 +506,8 @@ public void addExchangeableToken(String connectionName, String channelId, String userId, String exchangableItem, - String token) - { + String token + ) { ExchangableTokenKey key = new ExchangableTokenKey(); key.setConnectionName(connectionName); key.setChannelId(channelId); @@ -521,6 +521,23 @@ public void addExchangeableToken(String connectionName, } } + public void throwOnExchangeRequest(String connectionName, + String channelId, + String userId, + String exchangableItem) { + ExchangableTokenKey key = new ExchangableTokenKey(); + key.setConnectionName(connectionName); + key.setChannelId(channelId); + key.setUserId(userId); + key.setExchangableItem(exchangableItem); + + if (exchangableToken.containsKey(key)) { + exchangableToken.replace(key, exceptionExpected); + } else { + exchangableToken.put(key, exceptionExpected); + } + } + @Override public CompletableFuture signOutUser(TurnContext turnContext, AppCredentials oAuthAppCredentials, String connectionName, String userId) { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConfiguration.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConfiguration.java index 327cedc11..2754253ea 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConfiguration.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AuthenticationConfiguration.java @@ -10,12 +10,31 @@ * General configuration settings for authentication. */ public class AuthenticationConfiguration { + + private ClaimsValidator claimsValidator = null; + /** * Required endorsements for auth. - * + * * @return A List of endorsements. */ public List requiredEndorsements() { return new ArrayList(); } + + /** + * Access to the ClaimsValidator used to validate the identity claims. + * @return the ClaimsValidator value if set. + */ + public ClaimsValidator getClaimsValidator() { + return claimsValidator; + } + + /** + * Access to the ClaimsValidator used to validate the identity claims. + * @param withClaimsValidator the value to set the ClaimsValidator to. + */ + public void setClaimsValidator(ClaimsValidator withClaimsValidator) { + claimsValidator = withClaimsValidator; + } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsValidator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsValidator.java new file mode 100644 index 000000000..42692da1a --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ClaimsValidator.java @@ -0,0 +1,20 @@ +package com.microsoft.bot.connector.authentication; + +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * An abstract class used to validate identity. + */ +public abstract class ClaimsValidator { + + /** + * Validates a Map of claims and should throw an exception if the + * validation fails. + * + * @param claims The Map of claims to validate. + * + * @return true if the validation is successful, false if not. + */ + public abstract CompletableFuture validateClaims(Map claims); +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java index 4d6398b78..f81a70ca6 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java @@ -147,6 +147,40 @@ public static CompletableFuture validateAuthHeader( new IllegalArgumentException("No authHeader present. Auth is required.")); } + return authenticateToken(authHeader, credentials, channelProvider, channelId, serviceUrl, authConfig) + .thenApply(identity -> { + validateClaims(authConfig, identity.claims()); + return identity; + } + ); + } + + private static CompletableFuture validateClaims( + AuthenticationConfiguration authConfig, + Map claims + ) { + if (authConfig.getClaimsValidator() != null) { + return authConfig.getClaimsValidator().validateClaims(claims); + } else if (SkillValidation.isSkillClaim(claims)) { + return Async.completeExceptionally( + new RuntimeException("ClaimValidator is required for validation of Skill Host calls") + ); + } + return CompletableFuture.completedFuture(null); + } + + private static CompletableFuture authenticateToken( + String authHeader, + CredentialProvider credentials, + ChannelProvider channelProvider, + String channelId, + String serviceUrl, + AuthenticationConfiguration authConfig + ) { + if (SkillValidation.isSkillToken(authHeader)) { + return SkillValidation.authenticateChannelToken( + authHeader, credentials, channelProvider, channelId, authConfig); + } boolean usingEmulator = EmulatorValidation.isTokenFromEmulator(authHeader); if (usingEmulator) { return EmulatorValidation @@ -168,6 +202,7 @@ public static CompletableFuture validateAuthHeader( authHeader, credentials, channelProvider, serviceUrl, channelId, authConfig ); } + } /** diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java index 619689313..88190f19b 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/MicrosoftGovernmentAppCredentials.java @@ -42,9 +42,26 @@ public MicrosoftGovernmentAppCredentials(String appId, String password, String o ); } + /** + * Initializes a new instance of the MicrosoftGovernmentAppCredentials class. + * + * @param withAppId The Microsoft app ID. + * @param withAppPassword The Microsoft app password. + * @param withChannelAuthTenant Optional. The oauth token tenant. + * @param withOAuthScope The scope for the token. + */ + public MicrosoftGovernmentAppCredentials( + String withAppId, + String withAppPassword, + String withChannelAuthTenant, + String withOAuthScope + ) { + super(withAppId, withAppPassword, withChannelAuthTenant, withOAuthScope); + } + /** * An empty set of credentials. - * + * * @return An empty Gov credentials. */ public static MicrosoftGovernmentAppCredentials empty() { diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java index 33891fe5a..cbf1885be 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/SkillValidation.java @@ -52,44 +52,71 @@ private SkillValidation() { true, Duration.ofMinutes(5), true); /** - * Checks if the given list of claims represents a skill. A skill claim should - * contain: An AuthenticationConstants.VersionClaim" claim. An - * AuthenticationConstants.AudienceClaim claim. An - * AuthenticationConstants.AppIdClaim claim (v1) or an a - * AuthenticationConstants.AuthorizedParty claim (v2). And the appId claim - * should be different than the audience claim. When a channel (webchat, teams, - * etc.) invokes a bot, the - * is set to - * but when a bot calls another bot, the audience claim is set to the appId of - * the bot being invoked. The protocol supports v1 and v2 tokens: For v1 tokens, - * the AuthenticationConstants.AppIdClaim is present and set to the app Id of - * the calling bot. For v2 tokens, the AuthenticationConstants.AuthorizedParty - * is present and set to the app Id of the calling bot. + * Determines if a given Auth header is from from a skill to bot or bot to skill + * request. + * + * @param authHeader Bearer Token, in the "Bearer [Long String]" Format. + * @return True, if the token was issued for a skill to bot communication. + * Otherwise, false. + */ + public static boolean isSkillToken(String authHeader) { + if (!JwtTokenValidation.isValidTokenFormat(authHeader)) { + return false; + } + + // We know is a valid token, split it and work with it: + // [0] = "Bearer" + // [1] = "[Big Long String]" + String bearerToken = authHeader.split(" ")[1]; + + // Parse token + ClaimsIdentity identity = new ClaimsIdentity(JWT.decode(bearerToken)); + + return isSkillClaim(identity.claims()); + } + + /** + * Checks if the given list of claims represents a skill. + * + * A skill claim should contain: An {@link AuthenticationConstants#versionClaim} + * claim. An {@link AuthenticationConstants#audienceClaim} claim. An + * {@link AuthenticationConstants#appIdClaim} claim (v1) or an a + * {@link AuthenticationConstants#authorizedParty} claim (v2). And the appId + * claim should be different than the audience claim. When a channel (webchat, + * teams, etc.) invokes a bot, the {@link AuthenticationConstants#audienceClaim} + * is set to {@link AuthenticationConstants#toBotFromChannelTokenIssuer} but + * when a bot calls another bot, the audience claim is set to the appId of the + * bot being invoked. The protocol supports v1 and v2 tokens: For v1 tokens, the + * {@link AuthenticationConstants#appIdClaim} is present and set to the app Id + * of the calling bot. For v2 tokens, the + * {@link AuthenticationConstants#authorizedParty} is present and set to the app + * Id of the calling bot. + * + * @param claims A list of claims. * - * @param claims A map of claims * @return True if the list of claims is a skill claim, false if is not. */ public static Boolean isSkillClaim(Map claims) { for (Map.Entry entry : claims.entrySet()) { - if (entry.getValue() == AuthenticationConstants.ANONYMOUS_SKILL_APPID - && entry.getKey() == AuthenticationConstants.APPID_CLAIM) { + if (entry.getValue() != null && entry.getValue().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID) + && entry.getKey().equals(AuthenticationConstants.APPID_CLAIM)) { return true; } } Optional> version = claims.entrySet().stream() - .filter((x) -> x.getKey() == AuthenticationConstants.VERSION_CLAIM).findFirst(); + .filter((x) -> x.getKey().equals(AuthenticationConstants.VERSION_CLAIM)).findFirst(); if (!version.isPresent()) { // Must have a version claim. return false; } Optional> audience = claims.entrySet().stream() - .filter((x) -> x.getKey() == AuthenticationConstants.AUDIENCE_CLAIM).findFirst(); + .filter((x) -> x.getKey().equals(AuthenticationConstants.AUDIENCE_CLAIM)).findFirst(); if (!audience.isPresent() - || AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER == audience.get().getValue()) { + || AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER.equals(audience.get().getValue())) { // The audience is https://api.botframework.com and not an appId. return false; } @@ -105,38 +132,50 @@ public static Boolean isSkillClaim(Map claims) { } /** - * Determines if a given Auth header is from from a skill to bot or bot to skill request. + * Validates that the incoming Auth Header is a token sent from a bot to a skill + * or from a skill to a bot. * - * @param authHeader Bearer Token, in the "Bearer [Long String]" Format. - * @return True, if the token was issued for a skill to bot communication. Otherwise, false. + * @param authHeader The raw HTTP header in the format: "Bearer + * [longString]". + * @param credentials The user defined set of valid credentials, such as the + * AppId. + * @param channelProvider The channelService value that distinguishes public + * Azure from US Government Azure. + * @param channelId The ID of the channel to validate. + * @param authConfig The authentication configuration. + * + * @return A {@link ClaimsIdentity} instance if the validation is successful. */ - public static boolean isSkillToken(String authHeader) { - if (!JwtTokenValidation.isValidTokenFormat(authHeader)) { - return false; + public static CompletableFuture authenticateChannelToken(String authHeader, + CredentialProvider credentials, ChannelProvider channelProvider, String channelId, + AuthenticationConfiguration authConfig) { + if (authConfig == null) { + return Async.completeExceptionally(new IllegalArgumentException("authConfig cannot be null.")); } - // We know is a valid token, split it and work with it: - // [0] = "Bearer" - // [1] = "[Big Long String]" - String bearerToken = authHeader.split(" ")[1]; + String openIdMetadataUrl = channelProvider != null && channelProvider.isGovernment() + ? GovernmentAuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL + : AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL; - // Parse token - ClaimsIdentity identity = new ClaimsIdentity(JWT.decode(bearerToken)); + JwtTokenExtractor tokenExtractor = new JwtTokenExtractor(TOKENVALIDATIONPARAMETERS, openIdMetadataUrl, + AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS); - return isSkillClaim(identity.claims()); + return tokenExtractor.getIdentity(authHeader, channelId, authConfig.requiredEndorsements()) + .thenCompose(identity -> { + return validateIdentity(identity, credentials).thenCompose(result -> { + return CompletableFuture.completedFuture(identity); + }); + }); } /** * Helper to validate a skills ClaimsIdentity. * - * @param identity The ClaimsIdentity to validate. + * @param identity The ClaimsIdentity to validate. * @param credentials The CredentialProvider. * @return Nothing if success, otherwise a CompletionException */ - public static CompletableFuture validateIdentity( - ClaimsIdentity identity, - CredentialProvider credentials - ) { + public static CompletableFuture validateIdentity(ClaimsIdentity identity, CredentialProvider credentials) { if (identity == null) { // No valid identity. Not Authorized. return Async.completeExceptionally(new AuthenticationException("Invalid Identity")); @@ -148,28 +187,20 @@ public static CompletableFuture validateIdentity( } Optional> versionClaim = identity.claims().entrySet().stream() - .filter(item -> StringUtils.equals(AuthenticationConstants.VERSION_CLAIM, item.getKey())) - .findFirst(); + .filter(item -> StringUtils.equals(AuthenticationConstants.VERSION_CLAIM, item.getKey())).findFirst(); if (!versionClaim.isPresent()) { // No version claim - return Async.completeExceptionally( - new AuthenticationException( - AuthenticationConstants.VERSION_CLAIM + " claim is required on skill Tokens." - ) - ); + return Async.completeExceptionally(new AuthenticationException( + AuthenticationConstants.VERSION_CLAIM + " claim is required on skill Tokens.")); } // Look for the "aud" claim, but only if issued from the Bot Framework Optional> audienceClaim = identity.claims().entrySet().stream() - .filter(item -> StringUtils.equals(AuthenticationConstants.AUDIENCE_CLAIM, item.getKey())) - .findFirst(); + .filter(item -> StringUtils.equals(AuthenticationConstants.AUDIENCE_CLAIM, item.getKey())).findFirst(); if (!audienceClaim.isPresent() || StringUtils.isEmpty(audienceClaim.get().getValue())) { // Claim is not present or doesn't have a value. Not Authorized. - return Async.completeExceptionally( - new AuthenticationException( - AuthenticationConstants.AUDIENCE_CLAIM + " claim is required on skill Tokens." - ) - ); + return Async.completeExceptionally(new AuthenticationException( + AuthenticationConstants.AUDIENCE_CLAIM + " claim is required on skill Tokens.")); } String appId = JwtTokenValidation.getAppIdFromClaims(identity.claims()); @@ -177,28 +208,25 @@ public static CompletableFuture validateIdentity( return Async.completeExceptionally(new AuthenticationException("Invalid appId.")); } - return credentials.isValidAppId(audienceClaim.get().getValue()) - .thenApply(isValid -> { - if (!isValid) { - throw new AuthenticationException("Invalid audience."); - } - return null; - }); + return credentials.isValidAppId(audienceClaim.get().getValue()).thenApply(isValid -> { + if (!isValid) { + throw new AuthenticationException("Invalid audience."); + } + return null; + }); } /** * Creates a ClaimsIdentity for an anonymous (unauthenticated) skill. * * @return A ClaimsIdentity instance with authentication type set to - * AuthenticationConstants.AnonymousAuthType and a reserved - * AuthenticationConstants.AnonymousSkillAppId claim. + * AuthenticationConstants.AnonymousAuthType and a reserved + * AuthenticationConstants.AnonymousSkillAppId claim. */ public static ClaimsIdentity createAnonymousSkillClaim() { Map claims = new HashMap<>(); claims.put(AuthenticationConstants.APPID_CLAIM, AuthenticationConstants.ANONYMOUS_SKILL_APPID); - return new ClaimsIdentity( - AuthenticationConstants.ANONYMOUS_AUTH_TYPE, - AuthenticationConstants.ANONYMOUS_AUTH_TYPE, - claims); + return new ClaimsIdentity(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, + AuthenticationConstants.ANONYMOUS_AUTH_TYPE, claims); } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/BeginSkillDialogOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/BeginSkillDialogOptions.java new file mode 100644 index 000000000..1f472a3ba --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/BeginSkillDialogOptions.java @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.schema.Activity; + +/** + * A class with dialog arguments for a {@link SkillDialog} . + */ +public class BeginSkillDialogOptions { + + private Activity activity; + + /** + * Gets the {@link Activity} to send to the skill. + * @return the Activity value as a getActivity(). + */ + public Activity getActivity() { + return this.activity; + } + + /** + * Sets the {@link Activity} to send to the skill. + * @param withActivity The Activity value. + */ + public void setActivity(Activity withActivity) { + this.activity = withActivity; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java index 92022a2ae..a66b080fb 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/ComponentDialog.java @@ -10,409 +10,406 @@ import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.connector.Async; +/** + * A {@link Dialog} that is composed of other dialogs. + * + * A component dialog has an inner {@link DialogSet} and {@link DialogContext} + * ,which provides an inner dialog stack that is hidden from the parent dialog. + */ +public class ComponentDialog extends DialogContainer { + + private String initialDialogId; + /** - * A {@link Dialog} that is composed of other dialogs. - * - * A component dialog has an inner {@link DialogSet} and {@link DialogContext} ,which provides - * an inner dialog stack that is hidden from the parent dialog. + * The id for the persisted dialog state. */ - public class ComponentDialog extends DialogContainer { + public static final String PERSISTEDDIALOGSTATE = "dialogs"; - private String initialDialogId; + private boolean initialized; - /** - * The id for the persisted dialog state. - */ - public static final String PERSISTEDDIALOGSTATE = "dialogs"; + /** + * Initializes a new instance of the {@link ComponentDialog} class. + * + * @param dialogId The D to assign to the new dialog within the parent dialog + * set. + */ + public ComponentDialog(String dialogId) { + super(dialogId); + } - private boolean initialized; + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext outerDc, Object options) { - /** - * Initializes a new instance of the {@link ComponentDialog} class. - * - * @param dialogId The D to assign to the new dialog within the parent dialog - * set. - */ - public ComponentDialog(String dialogId) { - super(dialogId); + if (outerDc == null) { + return Async.completeExceptionally(new IllegalArgumentException("outerDc cannot be null.")); } - /** - * Called when the dialog is started and pushed onto the parent's dialog stack. - * - * @param outerDc The parent {@link DialogContext} for the current turn of - * conversation. - * @param options Optional, initial information to pass to the dialog. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * If the task is successful, the result indicates whether the dialog is - * still active after the turn has been processed by the dialog. - */ - @Override - public CompletableFuture beginDialog(DialogContext outerDc, Object options) { - - if (outerDc == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "outerDc cannot be null." - )); - } - - ensureInitialized(outerDc).join(); - - this.checkForVersionChange(outerDc).join(); - - DialogContext innerDc = this.createChildContext(outerDc); - DialogTurnResult turnResult = onBeginDialog(innerDc, options).join(); + return ensureInitialized(outerDc).thenCompose(ensureResult -> { + return this.checkForVersionChange(outerDc).thenCompose(checkResult -> { + DialogContext innerDc = this.createChildContext(outerDc); + return onBeginDialog(innerDc, options).thenCompose(turnResult -> { + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return result to calling dialog + return endComponent(outerDc, turnResult.getResult()) + .thenCompose(result -> CompletableFuture.completedFuture(result)); + } + getTelemetryClient().trackDialogView(getId(), null, null); + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); + }); + }); + }); + } - // Check for end of inner dialog - if (turnResult.getStatus() != DialogTurnStatus.WAITING) { - // Return result to calling dialog - DialogTurnResult result = endComponent(outerDc, turnResult.getResult()).join(); - return CompletableFuture.completedFuture(result); - } + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. If this method is *not* + * overridden, the component dialog calls the + * {@link DialogContext#continueDialog(CancellationToken)} method on its + * inner dialog context. If the inner dialog stack is empty, the + * component dialog ends, and if a {@link DialogTurnResult#result} is + * available, the component dialog uses that as its return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext outerDc) { + return ensureInitialized(outerDc).thenCompose(ensureResult -> { + return this.checkForVersionChange(outerDc).thenCompose(checkResult -> { + // Continue execution of inner dialog + DialogContext innerDc = this.createChildContext(outerDc); + return this.onContinueDialog(innerDc).thenCompose(turnResult -> { + // Check for end of inner dialog + if (turnResult.getStatus() != DialogTurnStatus.WAITING) { + // Return to calling dialog + return this.endComponent(outerDc, turnResult.getResult()) + .thenCompose(result -> CompletableFuture.completedFuture(result)); + } - getTelemetryClient().trackDialogView(getId(), null, null); + // Just signal waiting + return CompletableFuture.completedFuture(END_OF_TURN); - // Just signal waiting - return CompletableFuture.completedFuture(END_OF_TURN); - } + }); + }); - /** - * Called when the dialog is _continued_, where it is the active dialog and the - * user replies with a new activity. - * - * @param outerDc The parent {@link DialogContext} for the current turn of - * conversation. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * If the task is successful, the result indicates whether the dialog is - * still active after the turn has been processed by the dialog. The - * result may also contain a return value. If this method is *not* - * overridden, the component dialog calls the - * {@link DialogContext#continueDialog(CancellationToken)} method on its - * inner dialog context. If the inner dialog stack is empty, the - * component dialog ends, and if a {@link DialogTurnResult#result} is - * available, the component dialog uses that as its return value. - */ - @Override - public CompletableFuture continueDialog(DialogContext outerDc) { - ensureInitialized(outerDc).join(); - - this.checkForVersionChange(outerDc).join(); - - // Continue execution of inner dialog - DialogContext innerDc = this.createChildContext(outerDc); - DialogTurnResult turnResult = this.onContinueDialog(innerDc).join(); - - // Check for end of inner dialog - if (turnResult.getStatus() != DialogTurnStatus.WAITING) { - // Return to calling dialog - DialogTurnResult result = this.endComponent(outerDc, turnResult.getResult()).join(); - return CompletableFuture.completedFuture(result); - } + }); + } - // Just signal waiting - return CompletableFuture.completedFuture(END_OF_TURN); - } + /** + * Called when a child dialog on the parent's dialog stack completed this turn, + * returning control to this dialog component. + * + * @param outerDc The {@link DialogContext} for the current turn of + * conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether this dialog + * is still active after this dialog turn has been processed. Generally, + * the child dialog was started with a call to + * {@link BeginDialog(DialogContext, Object)} in the parent's context. + * However, if the {@link DialogContext#replaceDialog(String, Object)} + * method is called, the logical child dialog may be different than the + * original. If this method is *not* overridden, the dialog + * automatically calls its {@link RepromptDialog(TurnContext, + * DialogInstance)} when the user replies. + */ + @Override + public CompletableFuture resumeDialog(DialogContext outerDc, DialogReason reason, Object result) { + return ensureInitialized(outerDc).thenCompose(ensureResult -> { + return this.checkForVersionChange(outerDc).thenCompose(versionCheckResult -> { + // Containers are typically leaf nodes on the stack but the dev is free to push + // other dialogs + // on top of the stack which will result in the container receiving an + // unexpected call to + // dialogResume() when the pushed on dialog ends. + // To avoid the container prematurely ending we need to implement this method + // and simply + // ask our inner dialog stack to re-prompt. + return repromptDialog(outerDc.getContext(), outerDc.getActiveDialog()).thenCompose(repromptResult -> { + return CompletableFuture.completedFuture(END_OF_TURN); + }); + }); + }); + } - /** - * Called when a child dialog on the parent's dialog stack completed this turn, - * returning control to this dialog component. - * - * @param outerDc The {@link DialogContext} for the current turn of - * conversation. - * @param reason Reason why the dialog resumed. - * @param result Optional, value returned from the dialog that was called. The - * type of the value returned is dependent on the child dialog. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * If the task is successful, the result indicates whether this dialog - * is still active after this dialog turn has been processed. Generally, - * the child dialog was started with a call to - * {@link BeginDialog(DialogContext, Object)} in the parent's context. - * However, if the {@link DialogContext#replaceDialog(String, Object)} - * method is called, the logical child dialog may be different than the - * original. If this method is *not* overridden, the dialog - * automatically calls its {@link RepromptDialog(TurnContext, - * DialogInstance)} when the user replies. - */ - @Override - public CompletableFuture resumeDialog(DialogContext outerDc, DialogReason reason, - Object result) { - - ensureInitialized(outerDc).join(); - - this.checkForVersionChange(outerDc).join(); - - // Containers are typically leaf nodes on the stack but the dev is free to push - // other dialogs - // on top of the stack which will result in the container receiving an - // unexpected call to - // dialogResume() when the pushed on dialog ends. - // To avoid the container prematurely ending we need to implement this method - // and simply - // ask our inner dialog stack to re-prompt. - repromptDialog(outerDc.getContext(), outerDc.getActiveDialog()).join(); - return CompletableFuture.completedFuture(END_OF_TURN); - } + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information for this dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // Delegate to inner dialog. + DialogContext innerDc = this.createInnerDc(turnContext, instance); + return innerDc.repromptDialog().thenCompose(result -> onRepromptDialog(turnContext, instance)); + } - /** - * Called when the dialog should re-prompt the user for input. - * - * @param turnContext The context Object for this turn. - * @param instance State information for this dialog. - * - * @return A {@link CompletableFuture} representing the hronous operation. - */ - @Override - public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { - // Delegate to inner dialog. + /** + * Called when the dialog is ending. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the instance of this + * component dialog on its parent's dialog stack. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * When this method is called from the parent dialog's context, the + * component dialog cancels all of the dialogs on its inner dialog stack + * before ending. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + // Forward cancel to inner dialogs + if (reason == DialogReason.CANCEL_CALLED) { DialogContext innerDc = this.createInnerDc(turnContext, instance); - innerDc.repromptDialog().join(); - - // Notify component - return onRepromptDialog(turnContext, instance); - } - - /** - * Called when the dialog is ending. - * - * @param turnContext The context Object for this turn. - * @param instance State information associated with the instance of this - * component dialog on its parent's dialog stack. - * @param reason Reason why the dialog ended. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * When this method is called from the parent dialog's context, the - * component dialog cancels all of the dialogs on its inner dialog stack - * before ending. - */ - @Override - public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, - DialogReason reason) { - // Forward cancel to inner dialogs - if (reason == DialogReason.CANCEL_CALLED) { - DialogContext innerDc = this.createInnerDc(turnContext, instance); - innerDc.cancelAllDialogs().join(); - } - + return innerDc.cancelAllDialogs().thenCompose(result -> onEndDialog(turnContext, instance, reason)); + } else { return onEndDialog(turnContext, instance, reason); } + } - /** - * Adds a new {@link Dialog} to the component dialog and returns the updated - * component. - * - * @param dialog The dialog to add. - * - * @return The {@link ComponentDialog} after the operation is complete. - * - * The added dialog's {@link Dialog#telemetryClient} is set to the - * {@link DialogContainer#telemetryClient} of the component dialog. - */ - public ComponentDialog addDialog(Dialog dialog) { - this.getDialogs().add(dialog); - - if (this.getInitialDialogId() == null) { - this.setInitialDialogId(dialog.getId()); - } + /** + * Adds a new {@link Dialog} to the component dialog and returns the updated + * component. + * + * @param dialog The dialog to add. + * + * @return The {@link ComponentDialog} after the operation is complete. + * + * The added dialog's {@link Dialog#telemetryClient} is set to the + * {@link DialogContainer#telemetryClient} of the component dialog. + */ + public ComponentDialog addDialog(Dialog dialog) { + this.getDialogs().add(dialog); - return this; + if (this.getInitialDialogId() == null) { + this.setInitialDialogId(dialog.getId()); } - /** - * Creates an inner {@link DialogContext} . - * - * @param dc The parent {@link DialogContext} . - * - * @return The created Dialog Context. - */ - @Override - public DialogContext createChildContext(DialogContext dc) { - return this.createInnerDc(dc, dc.getActiveDialog()); - } + return this; + } - /** - * Ensures the dialog is initialized. - * - * @param outerDc The outer {@link DialogContext} . - * - * @return A {@link CompletableFuture} representing the hronous operation. - */ - protected CompletableFuture ensureInitialized(DialogContext outerDc) { - if (!this.initialized) { - this.initialized = true; - onInitialize(outerDc).join(); - } + /** + * Creates an inner {@link DialogContext} . + * + * @param dc The parent {@link DialogContext} . + * + * @return The created Dialog Context. + */ + @Override + public DialogContext createChildContext(DialogContext dc) { + return this.createInnerDc(dc, dc.getActiveDialog()); + } + + /** + * Ensures the dialog is initialized. + * + * @param outerDc The outer {@link DialogContext} . + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture ensureInitialized(DialogContext outerDc) { + if (!this.initialized) { + this.initialized = true; + return onInitialize(outerDc).thenApply(result -> null); + } else { return CompletableFuture.completedFuture(null); } + } - /** - * Initilizes the dialog. - * - * @param dc The {@link DialogContext} to initialize. - * - * @return A {@link CompletableFuture} representing the hronous operation. - */ - protected CompletableFuture onInitialize(DialogContext dc) { - if (this.getInitialDialogId() == null) { - Collection dialogs = getDialogs().getDialogs(); - if (dialogs.size() > 0) { - this.setInitialDialogId(dialogs.stream().findFirst().get().getId()); - } + /** + * Initilizes the dialog. + * + * @param dc The {@link DialogContext} to initialize. + * + * @return A {@link CompletableFuture} representing the hronous operation. + */ + protected CompletableFuture onInitialize(DialogContext dc) { + if (this.getInitialDialogId() == null) { + Collection dialogs = getDialogs().getDialogs(); + if (dialogs.size() > 0) { + this.setInitialDialogId(dialogs.stream().findFirst().get().getId()); } - - return CompletableFuture.completedFuture(null); } - /** - * Called when the dialog is started and pushed onto the parent's dialog stack. - * - * @param innerDc The inner {@link DialogContext} for the current turn of - * conversation. - * @param options Optional, initial information to pass to the dialog. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * If the task is successful, the result indicates whether the dialog is - * still active after the turn has been processed by the dialog. By - * default, this calls the - * {@link Dialog#beginDialog(DialogContext, Object)} method of the - * component dialog's initial dialog, as defined by - * {@link InitialDialogId} . Override this method in a derived class to - * implement interrupt logic. - */ - protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { - return innerDc.beginDialog(getInitialDialogId(), options); - } + return CompletableFuture.completedFuture(null); + } - /** - * Called when the dialog is _continued_, where it is the active dialog and the - * user replies with a new activity. - * - * @param innerDc The inner {@link DialogContext} for the current turn of - * conversation. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * If the task is successful, the result indicates whether the dialog is - * still active after the turn has been processed by the dialog. The - * result may also contain a return value. By default, this calls the - * currently active inner dialog's - * {@link Dialog#continueDialog(DialogContext)} method. Override this - * method in a derived class to implement interrupt logic. - */ - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - return innerDc.continueDialog(); - } + /** + * Called when the dialog is started and pushed onto the parent's dialog stack. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. By + * default, this calls the + * {@link Dialog#beginDialog(DialogContext, Object)} method of the + * component dialog's initial dialog, as defined by + * {@link InitialDialogId} . Override this method in a derived class to + * implement interrupt logic. + */ + protected CompletableFuture onBeginDialog(DialogContext innerDc, Object options) { + return innerDc.beginDialog(getInitialDialogId(), options); + } - /** - * Called when the dialog is ending. - * - * @param context The context Object for this turn. - * @param instance State information associated with the inner dialog stack of - * this component dialog. - * @param reason Reason why the dialog ended. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * Override this method in a derived class to implement any additional - * logic that should happen at the component level, after all inner - * dialogs have been canceled. - */ - protected CompletableFuture onEndDialog(TurnContext context, DialogInstance instance, - DialogReason reason) { - return CompletableFuture.completedFuture(null); - } + /** + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. + * + * @param innerDc The inner {@link DialogContext} for the current turn of + * conversation. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. By default, this calls the + * currently active inner dialog's + * {@link Dialog#continueDialog(DialogContext)} method. Override this + * method in a derived class to implement interrupt logic. + */ + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return innerDc.continueDialog(); + } - /** - * Called when the dialog should re-prompt the user for input. - * - * @param turnContext The context Object for this turn. - * @param instance State information associated with the inner dialog stack - * of this component dialog. - * - * @return A {@link CompletableFuture} representing the hronous operation. - * - * Override this method in a derived class to implement any additional - * logic that should happen at the component level, after the re-prompt - * operation completes for the inner dialog. - */ - protected CompletableFuture onRepromptDialog(TurnContext turnContext, DialogInstance instance) { - return CompletableFuture.completedFuture(null); - } + /** + * Called when the dialog is ending. + * + * @param context The context Object for this turn. + * @param instance State information associated with the inner dialog stack of + * this component dialog. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after all inner + * dialogs have been canceled. + */ + protected CompletableFuture onEndDialog(TurnContext context, DialogInstance instance, DialogReason reason) { + return CompletableFuture.completedFuture(null); + } - /** - * Ends the component dialog in its parent's context. - * - * @param outerDc The parent {@link DialogContext} for the current turn of - * conversation. - * @param result Optional, value to return from the dialog component to the - * parent context. - * - * @return A task that represents the work queued to execute. - * - * If the task is successful, the result indicates that the dialog ended - * after the turn was processed by the dialog. In general, the parent - * context is the dialog or bot turn handler that started the dialog. If - * the parent is a dialog, the stack calls the parent's - * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} - * method to return a result to the parent dialog. If the parent dialog - * does not implement `ResumeDialog`, then the parent will end, too, and - * the result is passed to the next parent context, if one exists. The - * returned {@link DialogTurnResult} contains the return value in its - * {@link DialogTurnResult#result} property. - */ - protected CompletableFuture endComponent(DialogContext outerDc, Object result) { - return outerDc.endDialog(result); - } + /** + * Called when the dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the inner dialog stack + * of this component dialog. + * + * @return A {@link CompletableFuture} representing the hronous operation. + * + * Override this method in a derived class to implement any additional + * logic that should happen at the component level, after the re-prompt + * operation completes for the inner dialog. + */ + protected CompletableFuture onRepromptDialog(TurnContext turnContext, DialogInstance instance) { + return CompletableFuture.completedFuture(null); + } - private static DialogState buildDialogState(DialogInstance instance) { - DialogState state; + /** + * Ends the component dialog in its parent's context. + * + * @param outerDc The parent {@link DialogContext} for the current turn of + * conversation. + * @param result Optional, value to return from the dialog component to the + * parent context. + * + * @return A task that represents the work queued to execute. + * + * If the task is successful, the result indicates that the dialog ended + * after the turn was processed by the dialog. In general, the parent + * context is the dialog or bot turn handler that started the dialog. If + * the parent is a dialog, the stack calls the parent's + * {@link Dialog#resumeDialog(DialogContext, DialogReason, Object)} + * method to return a result to the parent dialog. If the parent dialog + * does not implement `ResumeDialog`, then the parent will end, too, and + * the result is passed to the next parent context, if one exists. The + * returned {@link DialogTurnResult} contains the return value in its + * {@link DialogTurnResult#result} property. + */ + protected CompletableFuture endComponent(DialogContext outerDc, Object result) { + return outerDc.endDialog(result); + } - if (instance.getState().containsKey(PERSISTEDDIALOGSTATE)) { - state = (DialogState) instance.getState().get(PERSISTEDDIALOGSTATE); - } else { - state = new DialogState(); - instance.getState().put(PERSISTEDDIALOGSTATE, state); - } + private static DialogState buildDialogState(DialogInstance instance) { + DialogState state; - if (state.getDialogStack() == null) { - state.setDialogStack(new ArrayList()); - } + if (instance.getState().containsKey(PERSISTEDDIALOGSTATE)) { + state = (DialogState) instance.getState().get(PERSISTEDDIALOGSTATE); + } else { + state = new DialogState(); + instance.getState().put(PERSISTEDDIALOGSTATE, state); + } - return state; + if (state.getDialogStack() == null) { + state.setDialogStack(new ArrayList()); } - private DialogContext createInnerDc(DialogContext outerDc, DialogInstance instance) { - DialogState state = buildDialogState(instance); + return state; + } - return new DialogContext(this.getDialogs(), outerDc, state); - } + private DialogContext createInnerDc(DialogContext outerDc, DialogInstance instance) { + DialogState state = buildDialogState(instance); - // NOTE: You should only call this if you don't have a dc to work with (such as OnResume()) - private DialogContext createInnerDc(TurnContext turnContext, DialogInstance instance) { - DialogState state = buildDialogState(instance); + return new DialogContext(this.getDialogs(), outerDc, state); + } - return new DialogContext(this.getDialogs(), turnContext, state); - } - /** - * Gets the id assigned to the initial dialog. - * @return the InitialDialogId value as a String. - */ - public String getInitialDialogId() { - return this.initialDialogId; - } + // NOTE: You should only call this if you don't have a dc to work with (such as + // OnResume()) + private DialogContext createInnerDc(TurnContext turnContext, DialogInstance instance) { + DialogState state = buildDialogState(instance); - /** - * Sets the id assigned to the initial dialog. - * @param withInitialDialogId The InitialDialogId value. - */ - public void setInitialDialogId(String withInitialDialogId) { - this.initialDialogId = withInitialDialogId; - } + return new DialogContext(this.getDialogs(), turnContext, state); + } + + /** + * Gets the id assigned to the initial dialog. + * + * @return the InitialDialogId value as a String. + */ + public String getInitialDialogId() { + return this.initialDialogId; + } + + /** + * Sets the id assigned to the initial dialog. + * + * @param withInitialDialogId The InitialDialogId value. + */ + public void setInitialDialogId(String withInitialDialogId) { + this.initialDialogId = withInitialDialogId; } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 59974a94a..f20a3ae2e 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -5,10 +5,21 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.builder.BotAdapter; import com.microsoft.bot.builder.BotTelemetryClient; import com.microsoft.bot.builder.NullBotTelemetryClient; import com.microsoft.bot.builder.StatePropertyAccessor; import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.EndOfConversationCodes; + import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -295,20 +306,100 @@ public static CompletableFuture run( dialogSet.setTelemetryClient(dialog.getTelemetryClient()); return dialogSet.createContext(turnContext) - .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog)) + .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog, turnContext)) .thenApply(result -> null); } - private static CompletableFuture continueOrStart( - DialogContext dialogContext, Dialog dialog + private static CompletableFuture continueOrStart( + DialogContext dialogContext, Dialog dialog, TurnContext turnContext ) { + if (DialogCommon.isFromParentToSkill(turnContext)) { + // Handle remote cancellation request from parent. + if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)) { + if (dialogContext.getStack().size() == 0) { + // No dialogs to cancel, just return. + return CompletableFuture.completedFuture(null); + } + + DialogContext activeDialogContext = getActiveDialogContext(dialogContext); + + // Send cancellation message to the top dialog in the stack to ensure all the parents + // are canceled in the right order. + return activeDialogContext.cancelAllDialogs(true, null, null).thenApply(result -> null); + } + + // Handle a reprompt event sent from the parent. + if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT) + && turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) { + if (dialogContext.getStack().size() == 0) { + // No dialogs to reprompt, just return. + return CompletableFuture.completedFuture(null); + } + + return dialogContext.repromptDialog(); + } + } return dialogContext.continueDialog() .thenCompose(result -> { if (result.getStatus() == DialogTurnStatus.EMPTY) { - return dialogContext.beginDialog(dialog.getId(), null); + return dialogContext.beginDialog(dialog.getId(), null).thenCompose(finalResult -> { + return processEOC(finalResult, turnContext); + }); } - - return CompletableFuture.completedFuture(result); + return processEOC(result, turnContext); }); } + + private static CompletableFuture processEOC(DialogTurnResult result, TurnContext turnContext) { + if (result.getStatus() == DialogTurnStatus.COMPLETE + || result.getStatus() == DialogTurnStatus.CANCELLED + && sendEoCToParent(turnContext)) { + EndOfConversationCodes code = result.getStatus() == DialogTurnStatus.COMPLETE + ? EndOfConversationCodes.COMPLETED_SUCCESSFULLY + : EndOfConversationCodes.USER_CANCELLED; + Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION); + activity.setValue(result.getResult()); + activity.setLocalTimeZone(turnContext.getActivity().getLocale()); + activity.setCode(code); + return turnContext.sendActivity(activity).thenApply(finalResult -> null); + } + return CompletableFuture.completedFuture(null); + } + + /** + * Helper to determine if we should send an EoC to the parent or not. + * @param turnContext + * @return + */ + private static boolean sendEoCToParent(TurnContext turnContext) { + + ClaimsIdentity claimsIdentity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + + if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) { + // EoC Activities returned by skills are bounced back to the bot by SkillHandler. + // In those cases we will have a SkillConversationReference instance in state. + SkillConversationReference skillConversationReference = + turnContext.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY); + if (skillConversationReference != null) { + // If the skillConversationReference.OAuthScope is for one of the supported channels, + // we are at the root and we should not send an EoC. + return skillConversationReference.getOAuthScope() + != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + && skillConversationReference.getOAuthScope() + != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + } + return true; + } + return false; + } + + // Recursively walk up the DC stack to find the active DC. + private static DialogContext getActiveDialogContext(DialogContext dialogContext) { + DialogContext child = dialogContext.getChild(); + if (child == null) { + return dialogContext; + } + + return getActiveDialogContext(child); + } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCommon.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCommon.java new file mode 100644 index 000000000..0ddcc8382 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogCommon.java @@ -0,0 +1,37 @@ +package com.microsoft.bot.dialogs; + +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.SkillValidation; + +/** + * A class to contain code that is duplicated across multiple Dialog related + * classes and can be shared through this common class. + */ +final class DialogCommon { + + private DialogCommon() { + + } + + /** + * Determine if a turnContext is from a Parent to a Skill. + * @param turnContext the turnContext. + * @return true if the turnContext is from a Parent to a Skill, false otherwise. + */ + static boolean isFromParentToSkill(TurnContext turnContext) { + if (turnContext.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY) != null) { + return false; + } + + Object identity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (identity instanceof ClaimsIdentity) { + ClaimsIdentity claimsIdentity = (ClaimsIdentity) identity; + return SkillValidation.isSkillClaim(claimsIdentity.claims()); + } else { + return false; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java index 38965e924..519465aeb 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java @@ -17,14 +17,18 @@ import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.TurnContextStateCollection; import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.builder.skills.SkillHandler; import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; import com.microsoft.bot.connector.authentication.SkillValidation; import com.microsoft.bot.dialogs.memory.DialogStateManager; import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; import com.microsoft.bot.schema.Activity; - -import org.apache.commons.lang3.NotImplementedException; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.EndOfConversationCodes; /** * Class which runs the dialog system. @@ -294,7 +298,7 @@ public CompletableFuture onTurn(TurnContext context) { ClaimsIdentity claimIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); if (claimIdentity != null && SkillValidation.isSkillClaim(claimIdentity.claims())) { // The bot is running as a skill. - turnResult = handleSkillOnTurn().join(); + turnResult = handleSkillOnTurn(dc).join(); } else { // The bot is running as root bot. turnResult = handleBotOnTurn(dc).join(); @@ -351,12 +355,86 @@ private static DialogContext getActiveDialogContext(DialogContext dialogContext) } } - @SuppressWarnings({"TodoComment"}) - //TODO: Add Skills support here - private CompletableFuture handleSkillOnTurn() { - return Async.completeExceptionally(new NotImplementedException( - "Skills are not implemented in this release" - )); + private CompletableFuture handleSkillOnTurn(DialogContext dc) { + // the bot instanceof running as a skill. + TurnContext turnContext = dc.getContext(); + + // Process remote cancellation + if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION) + && dc.getActiveDialog() != null + && DialogCommon.isFromParentToSkill(turnContext)) { + // Handle remote cancellation request from parent. + DialogContext activeDialogContext = getActiveDialogContext(dc); + + // Send cancellation message to the top dialog in the stack to ensure all the + // parents are canceled in the right order. + return activeDialogContext.cancelAllDialogs(); + } + + // Handle reprompt + // Process a reprompt event sent from the parent. + if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT) + && turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) { + if (dc.getActiveDialog() == null) { + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); + } + + dc.repromptDialog(); + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING)); + } + + // Continue execution + // - This will apply any queued up interruptions and execute the current/next step(s). + DialogTurnResult turnResult = dc.continueDialog().join(); + if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) { + // restart root dialog + turnResult = dc.beginDialog(rootDialogId).join(); + } + + sendStateSnapshotTrace(dc, "Skill State"); + + if (shouldSendEndOfConversationToParent(turnContext, turnResult)) { + // Send End of conversation at the end. + EndOfConversationCodes code = turnResult.getStatus().equals(DialogTurnStatus.COMPLETE) + ? EndOfConversationCodes.COMPLETED_SUCCESSFULLY + : EndOfConversationCodes.USER_CANCELLED; + Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION); + activity.setValue(turnResult.getResult()); + activity.setLocale(turnContext.getActivity().getLocale()); + activity.setCode(code); + turnContext.sendActivity(activity).join(); + } + + return CompletableFuture.completedFuture(turnResult); + } + + /** + * Helper to determine if we should send an EndOfConversation to the parent + * or not. + */ + private static boolean shouldSendEndOfConversationToParent(TurnContext context, DialogTurnResult turnResult) { + if (!(turnResult.getStatus().equals(DialogTurnStatus.COMPLETE) + || turnResult.getStatus().equals(DialogTurnStatus.CANCELLED))) { + // The dialog instanceof still going, don't return EoC. + return false; + } + ClaimsIdentity claimsIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) { + // EoC Activities returned by skills are bounced back to the bot by SkillHandler. + // In those cases we will have a SkillConversationReference instance in state. + SkillConversationReference skillConversationReference = + context.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY); + if (skillConversationReference != null) { + // If the skillConversationReference.OAuthScope instanceof for one of the supported channels, + // we are at the root and we should not send an EoC. + return skillConversationReference.getOAuthScope() + != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + && skillConversationReference.getOAuthScope() + != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + } + return true; + } + return false; } /** @@ -395,7 +473,7 @@ private CompletableFuture handleBotOnTurn(DialogContext dc) { // step(s). turnResult = dc.continueDialog().join(); - if (turnResult.getStatus() == DialogTurnStatus.EMPTY) { + if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) { // restart root dialog turnResult = dc.beginDialog(rootDialogId).join(); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java new file mode 100644 index 000000000..3295c03f8 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java @@ -0,0 +1,513 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.dialogs; + +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserTokenProvider; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.DeliveryModes; +import com.microsoft.bot.schema.ExpectedReplies; +import com.microsoft.bot.schema.OAuthCard; +import com.microsoft.bot.schema.SignInConstants; +import com.microsoft.bot.schema.TokenExchangeInvokeRequest; +import com.microsoft.bot.schema.TokenExchangeRequest; + +import org.apache.commons.lang3.StringUtils; + +import com.fasterxml.jackson.databind.JsonNode; + +/** + * A specialized {@link Dialog} that can wrap remote calls to a skill. + * + * The options parameter in {@link BeginDialog} must be a + * {@link BeginSkillDialogOptions} instancewith the initial parameters for the + * dialog. + */ +public class SkillDialog extends Dialog { + + private SkillDialogOptions dialogOptions; + + private final String deliverModeStateKey = "deliverymode"; + private final String skillConversationIdStateKey = "Microsoft.Bot.Builder.Dialogs.SkillDialog.SkillConversationId"; + + /** + * Initializes a new instance of the {@link SkillDialog} class to wrap remote + * calls to a skill. + * + * @param dialogOptions The options to execute the skill dialog. + * @param dialogId The id of the dialog. + */ + public SkillDialog(SkillDialogOptions dialogOptions, String dialogId) { + super(dialogId); + if (dialogOptions == null) { + throw new IllegalArgumentException("dialogOptions cannot be null."); + } + + this.dialogOptions = dialogOptions; + } + + /** + * Called when the skill dialog is started and pushed onto the dialog stack. + * + * @param dc The {@link DialogContext} for the current turn of + * conversation. + * @param options Optional, initial information to pass to the dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. + */ + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + BeginSkillDialogOptions dialogArgs = validateBeginDialogArgs(options); + + // Create deep clone of the original activity to avoid altering it before + // forwarding it. + Activity skillActivity = Activity.clone(dialogArgs.getActivity()); + + // Apply conversation reference and common properties from incoming activity + // before sending. + ConversationReference conversationReference = dc.getContext().getActivity().getConversationReference(); + skillActivity.applyConversationReference(conversationReference, true); + + // Store delivery mode and connection name in dialog state for later use. + dc.getActiveDialog().getState().put(deliverModeStateKey, dialogArgs.getActivity().getDeliveryMode()); + + // Create the conversationId and store it in the dialog context state so we can + // use it later + return createSkillConversationId(dc.getContext(), dc.getContext().getActivity()) + .thenCompose(skillConversationId -> { + dc.getActiveDialog().getState().put(skillConversationIdStateKey, skillConversationId); + + // Send the activity to the skill. + return sendToSkill(dc.getContext(), skillActivity, skillConversationId).thenCompose(eocActivity -> { + if (eocActivity != null) { + return dc.endDialog(eocActivity.getValue()); + } + return CompletableFuture.completedFuture(END_OF_TURN); + }); + }); + } + + /** + * Called when the skill dialog is _continued_, where it is the active dialog + * and the user replies with a new activity. + * + * @param dc The {@link DialogContext} for the current turn of conversation. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + * + * If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. + */ + @Override + public CompletableFuture continueDialog(DialogContext dc) { + if (!onValidateActivity(dc.getContext().getActivity())) { + return CompletableFuture.completedFuture(END_OF_TURN); + } + + // Handle EndOfConversation from the skill (this will be sent to the this dialog + // by the SkillHandler + // if received from the Skill) + if (dc.getContext().getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)) { + return dc.endDialog(dc.getContext().getActivity().getValue()); + } + + // Create deep clone of the original activity to avoid altering it before + // forwarding it. + Activity skillActivity = Activity.clone(dc.getContext().getActivity()); + if (dc.getActiveDialog().getState().get(deliverModeStateKey) != null) { + skillActivity.setDeliveryMode((String) dc.getActiveDialog().getState().get(deliverModeStateKey)); + } + + String skillConversationId = (String) dc.getActiveDialog().getState().get(skillConversationIdStateKey); + + // Just forward to the remote skill + return sendToSkill(dc.getContext(), skillActivity, skillConversationId).thenCompose(eocActivity -> { + if (eocActivity != null) { + return dc.endDialog(eocActivity.getValue()); + } + + return CompletableFuture.completedFuture(END_OF_TURN); + }); + } + + /** + * Called when the skill dialog should re-prompt the user for input. + * + * @param turnContext The context Object for this turn. + * @param instance State information for this dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { + // Create and send an envent to the skill so it can resume the dialog. + Activity repromptEvent = Activity.createEventActivity(); + repromptEvent.setName(DialogEvents.REPROMPT_DIALOG); + + // Apply conversation reference and common properties from incoming activity + // before sending. + repromptEvent.applyConversationReference(turnContext.getActivity().getConversationReference(), true); + + String skillConversationId = (String) instance.getState().get(skillConversationIdStateKey); + + // connection Name instanceof not applicable for a RePrompt, as we don't expect + // as OAuthCard in response. + return sendToSkill(turnContext, (Activity) repromptEvent, skillConversationId).thenApply(result -> null); + } + + /** + * Called when a child skill dialog completed its turn, returning control to + * this dialog. + * + * @param dc The dialog context for the current turn of the conversation. + * @param reason Reason why the dialog resumed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { + return repromptDialog(dc.getContext(), dc.getActiveDialog()).thenCompose(x -> { + return CompletableFuture.completedFuture(END_OF_TURN); + }); + } + + /** + * Called when the skill dialog is ending. + * + * @param turnContext The context Object for this turn. + * @param instance State information associated with the instance of this + * dialog on the dialog stack. + * @param reason Reason why the dialog ended. + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + // Send of of conversation to the skill if the dialog has been cancelled. + return onEndDialog(turnContext, instance, reason) + .thenCompose(result -> super.endDialog(turnContext, instance, reason)); + } + + private CompletableFuture onEndDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + if (reason == DialogReason.CANCEL_CALLED || reason == DialogReason.REPLACE_CALLED) { + Activity activity = Activity.createEndOfConversationActivity(); + + // Apply conversation reference and common properties from incoming activity + // before sending. + activity.applyConversationReference(turnContext.getActivity().getConversationReference(), true); + activity.setChannelData(turnContext.getActivity().getChannelData()); + for (Map.Entry entry : turnContext.getActivity().getProperties().entrySet()) { + activity.setProperties(entry.getKey(), entry.getValue()); + } + + String skillConversationId = (String) instance.getState().get(skillConversationIdStateKey); + + // connection Name instanceof not applicable for an EndDialog, as we don't + // expect as OAuthCard in response. + return sendToSkill(turnContext, activity, skillConversationId).thenApply(result -> null); + } else { + return CompletableFuture.completedFuture(null); + } + + } + + /** + * Validates the activity sent during {@link ContinueDialog} . + * + * @param activity The {@link Activity} for the current turn of conversation. + * + * Override this method to implement a custom validator for the + * activity being sent during the {@link ContinueDialog} . This + * method can be used to ignore activities of a certain type if + * needed. If this method returns false, the dialog will end the + * turn without processing the activity. + * + * @return true if the activity is valid, false if not. + */ + protected boolean onValidateActivity(Activity activity) { + return true; + } + + /** + * Validates the required properties are set in the options argument passed to + * the BeginDialog call. + */ + private static BeginSkillDialogOptions validateBeginDialogArgs(Object options) { + if (options == null) { + throw new IllegalArgumentException("options cannot be null."); + } + + if (!(options instanceof BeginSkillDialogOptions)) { + throw new IllegalArgumentException("Unable to cast options to beginSkillDialogOptions}"); + } + + BeginSkillDialogOptions dialogArgs = (BeginSkillDialogOptions) options; + + if (dialogArgs.getActivity() == null) { + throw new IllegalArgumentException("dialogArgs.getActivity is null in options"); + } + + return dialogArgs; + } + + private CompletableFuture sendToSkill(TurnContext context, Activity activity, + String skillConversationId) { + if (activity.getType().equals(ActivityTypes.INVOKE)) { + // Force ExpectReplies for invoke activities so we can get the replies right + // away and send them + // back to the channel if needed. This makes sure that the dialog will receive + // the Invoke response + // from the skill and any other activities sent, including EoC. + activity.setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); + } + + // Always save state before forwarding + // (the dialog stack won't get updated with the skillDialog and things won't + // work if you don't) + getDialogOptions().getConversationState().saveChanges(context, true); + + BotFrameworkSkill skillInfo = getDialogOptions().getSkill(); + return getDialogOptions().getSkillClient() + .postActivity(getDialogOptions().getBotId(), skillInfo.getAppId(), skillInfo.getSkillEndpoint(), + getDialogOptions().getSkillHostEndpoint(), skillConversationId, activity, Object.class) + .thenCompose(response -> { + // Inspect the skill response status + if (!response.getIsSuccessStatusCode()) { + return Async.completeExceptionally(new SkillInvokeException(String.format( + "Error invoking the skill id: %s at %s (status is %s). %s", skillInfo.getId(), + skillInfo.getSkillEndpoint(), response.getStatus(), response.getBody()))); + } + + ExpectedReplies replies = null; + if (response.getBody() instanceof ExpectedReplies) { + replies = (ExpectedReplies) response.getBody(); + } + + Activity eocActivity = null; + if (activity.getDeliveryMode() != null + && activity.getDeliveryMode().equals(DeliveryModes.EXPECT_REPLIES.toString()) + && replies.getActivities() != null && replies.getActivities().size() > 0) { + // Track sent invoke responses, so more than one instanceof not sent. + boolean sentInvokeResponse = false; + + // Process replies in the response.getBody(). + for (Activity activityFromSkill : replies.getActivities()) { + if (activityFromSkill.getType().equals(ActivityTypes.END_OF_CONVERSATION)) { + // Capture the EndOfConversation activity if it was sent from skill + eocActivity = activityFromSkill; + + // The conversation has ended, so cleanup the conversation id. + getDialogOptions().getConversationIdFactory() + .deleteConversationReference(skillConversationId).join(); + } else if (!sentInvokeResponse && interceptOAuthCards(context, activityFromSkill, + getDialogOptions().getConnectionName()).join()) { + // do nothing. Token exchange succeeded, so no OAuthCard needs to be shown to + // the user + sentInvokeResponse = true; + } else { + if (activityFromSkill.getType().equals(ActivityTypes.INVOKE_RESPONSE)) { + // An invoke respones has already been sent. This instanceof a bug in the skill. + // Multiple invoke responses are not possible. + if (sentInvokeResponse) { + continue; + } + + sentInvokeResponse = true; + + // Not sure this is needed in Java, looks like a workaround for some .NET issues + // Ensure the value in the invoke response instanceof of type InvokeResponse + // (it gets deserialized as JObject by default). + + // if (activityFromSkill.getValue() instanceof JObject jObject) { + // activityFromSkill.setValue(jObject.ToObject()); + // } + } + + // Send the response back to the channel. + context.sendActivity(activityFromSkill); + } + } + } + + return CompletableFuture.completedFuture(eocActivity); + + }); + } + + /** + * Tells is if we should intercept the OAuthCard message. + * + * The SkillDialog only attempts to intercept OAuthCards when the following + * criteria are met: 1. An OAuthCard was sent from the skill 2. The SkillDialog + * was called with a connectionName 3. The current adapter supports token + * exchange If any of these criteria are false, return false. + */ + private CompletableFuture interceptOAuthCards(TurnContext turnContext, Activity activity, + String connectionName) { + + UserTokenProvider tokenExchangeProvider; + + if (StringUtils.isEmpty(connectionName) || !(turnContext.getAdapter() instanceof UserTokenProvider)) { + // The adapter may choose not to support token exchange, + // in which case we fallback to showing an oauth card to the user. + return CompletableFuture.completedFuture(false); + } else { + tokenExchangeProvider = (UserTokenProvider) turnContext.getAdapter(); + } + + Attachment oauthCardAttachment = null; + + if (activity.getAttachments() != null) { + Optional optionalAttachment = activity.getAttachments().stream() + .filter(a -> a.getContentType() != null && a.getContentType().equals(OAuthCard.CONTENTTYPE)) + .findFirst(); + if (optionalAttachment.isPresent()) { + oauthCardAttachment = optionalAttachment.get(); + } + } + + if (oauthCardAttachment != null) { + OAuthCard oauthCard = (OAuthCard) oauthCardAttachment.getContent(); + if (oauthCard != null && oauthCard.getTokenExchangeResource() != null + && !StringUtils.isEmpty(oauthCard.getTokenExchangeResource().getUri())) { + try { + return tokenExchangeProvider + .exchangeToken(turnContext, connectionName, turnContext.getActivity().getFrom().getId(), + new TokenExchangeRequest(oauthCard.getTokenExchangeResource().getUri(), null)) + .thenCompose(result -> { + if (result != null && !StringUtils.isEmpty(result.getToken())) { + // If token above instanceof null, then SSO has failed and hence we return + // false. + // If not, send an invoke to the skill with the token. + return sendTokenExchangeInvokeToSkill(activity, + oauthCard.getTokenExchangeResource().getId(), oauthCard.getConnectionName(), + result.getToken()); + } else { + return CompletableFuture.completedFuture(false); + } + + }); + } catch (Exception ex) { + // Failures in token exchange are not fatal. They simply mean that the user + // needs + // to be shown the OAuth card. + return CompletableFuture.completedFuture(false); + } + } + } + return CompletableFuture.completedFuture(false); + } + + // private CompletableFuture interceptOAuthCards(TurnContext turnContext, Activity activity, + // String connectionName) { + + // UserTokenProvider tokenExchangeProvider; + + // if (StringUtils.isEmpty(connectionName) || !(turnContext.getAdapter() instanceof UserTokenProvider)) { + // // The adapter may choose not to support token exchange, + // // in which case we fallback to showing an oauth card to the user. + // return CompletableFuture.completedFuture(false); + // } else { + // tokenExchangeProvider = (UserTokenProvider) turnContext.getAdapter(); + // } + + // Attachment oauthCardAttachment = null; + + // if (activity.getAttachments() != null) { + // Optional optionalAttachment = activity.getAttachments().stream() + // .filter(a -> a.getContentType() != null && a.getContentType().equals(OAuthCard.CONTENTTYPE)) + // .findFirst(); + // if (optionalAttachment.isPresent()) { + // oauthCardAttachment = optionalAttachment.get(); + // } + // } + + // if (oauthCardAttachment != null) { + // OAuthCard oauthCard = (OAuthCard) oauthCardAttachment.getContent(); + // if (oauthCard != null && oauthCard.getTokenExchangeResource() != null + // && !StringUtils.isEmpty(oauthCard.getTokenExchangeResource().getUri())) { + // try { + // TokenResponse result = tokenExchangeProvider + // .exchangeToken(turnContext, connectionName, turnContext.getActivity().getFrom().getId(), + // new TokenExchangeRequest(oauthCard.getTokenExchangeResource().getUri(), null)) + // .join(); + + // if (result != null && !StringUtils.isEmpty(result.getToken())) { + // // If token above instanceof null, then SSO has failed and hence we return + // // false. + // // If not, send an invoke to the skill with the token. + // return sendTokenExchangeInvokeToSkill(activity, oauthCard.getTokenExchangeResource().getId(), + // oauthCard.getConnectionName(), result.getToken()); + // } + // } catch (Exception ex) { + // // Failures in token exchange are not fatal. They simply mean that the user + // // needs + // // to be shown the OAuth card. + // return CompletableFuture.completedFuture(false); + // } + // } + // } + + // return CompletableFuture.completedFuture(false); + // } + + + private CompletableFuture sendTokenExchangeInvokeToSkill(Activity incomingActivity, String id, + String connectionName, String token) { + Activity activity = incomingActivity.createReply(); + activity.setType(ActivityTypes.INVOKE); + activity.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + TokenExchangeInvokeRequest tokenRequest = new TokenExchangeInvokeRequest(); + tokenRequest.setId(id); + tokenRequest.setToken(token); + tokenRequest.setConnectionName(connectionName); + activity.setValue(tokenRequest); + + // route the activity to the skill + BotFrameworkSkill skillInfo = getDialogOptions().getSkill(); + return getDialogOptions().getSkillClient() + .postActivity(getDialogOptions().getBotId(), skillInfo.getAppId(), skillInfo.getSkillEndpoint(), + getDialogOptions().getSkillHostEndpoint(), incomingActivity.getConversation().getId(), activity, + Object.class) + .thenApply(response -> response.getIsSuccessStatusCode()); + } + + private CompletableFuture createSkillConversationId(TurnContext context, Activity activity) { + // Create a conversationId to interact with the skill and send the activity + SkillConversationIdFactoryOptions conversationIdFactoryOptions = new SkillConversationIdFactoryOptions(); + conversationIdFactoryOptions.setFromBotOAuthScope(context.getTurnState().get(BotAdapter.OAUTH_SCOPE_KEY)); + conversationIdFactoryOptions.setFromBotId(getDialogOptions().getBotId()); + conversationIdFactoryOptions.setActivity(activity); + conversationIdFactoryOptions.setBotFrameworkSkill(getDialogOptions().getSkill()); + + return getDialogOptions().getConversationIdFactory().createSkillConversationId(conversationIdFactoryOptions); + } + + /** + * Gets the options used to execute the skill dialog. + * + * @return the DialogOptions value as a SkillDialogOptions. + */ + protected SkillDialogOptions getDialogOptions() { + return this.dialogOptions; + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialogOptions.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialogOptions.java new file mode 100644 index 000000000..53eb9dac1 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialogOptions.java @@ -0,0 +1,153 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.dialogs; + +import java.net.URI; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.skills.BotFrameworkClient; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; + +/** + * Defines the options that will be used to execute a {@link SkillDialog} . + */ +public class SkillDialogOptions { + + @JsonProperty(value = "botId") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String botId; + + private BotFrameworkClient skillClient; + + @JsonProperty(value = "skillHostEndpoint") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private URI skillHostEndpoint; + + @JsonProperty(value = "skill") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private BotFrameworkSkill skill; + + private SkillConversationIdFactoryBase conversationIdFactory; + + private ConversationState conversationState; + + @JsonProperty(value = "connectionName") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String connectionName; + + /** + * Gets the Microsoft app ID of the bot calling the skill. + * @return the BotId value as a String. + */ + public String getBotId() { + return this.botId; + } + + /** + * Sets the Microsoft app ID of the bot calling the skill. + * @param withBotId The BotId value. + */ + public void setBotId(String withBotId) { + this.botId = withBotId; + } + /** + * Gets the {@link BotFrameworkClient} used to call the remote + * skill. + * @return the SkillClient value as a BotFrameworkClient. + */ + public BotFrameworkClient getSkillClient() { + return this.skillClient; + } + + /** + * Sets the {@link BotFrameworkClient} used to call the remote + * skill. + * @param withSkillClient The SkillClient value. + */ + public void setSkillClient(BotFrameworkClient withSkillClient) { + this.skillClient = withSkillClient; + } + /** + * Gets the callback Url for the skill host. + * @return the SkillHostEndpoint value as a Uri. + */ + public URI getSkillHostEndpoint() { + return this.skillHostEndpoint; + } + + /** + * Sets the callback Url for the skill host. + * @param withSkillHostEndpoint The SkillHostEndpoint value. + */ + public void setSkillHostEndpoint(URI withSkillHostEndpoint) { + this.skillHostEndpoint = withSkillHostEndpoint; + } + /** + * Gets the {@link BotFrameworkSkill} that the dialog will call. + * @return the Skill value as a BotFrameworkSkill. + */ + public BotFrameworkSkill getSkill() { + return this.skill; + } + + /** + * Sets the {@link BotFrameworkSkill} that the dialog will call. + * @param withSkill The Skill value. + */ + public void setSkill(BotFrameworkSkill withSkill) { + this.skill = withSkill; + } + /** + * Gets an instance of a {@link SkillConversationIdFactoryBase} + * used to generate conversation IDs for interacting with the skill. + * @return the ConversationIdFactory value as a SkillConversationIdFactoryBase. + */ + public SkillConversationIdFactoryBase getConversationIdFactory() { + return this.conversationIdFactory; + } + + /** + * Sets an instance of a {@link SkillConversationIdFactoryBase} + * used to generate conversation IDs for interacting with the skill. + * @param withConversationIdFactory The ConversationIdFactory value. + */ + public void setConversationIdFactory(SkillConversationIdFactoryBase withConversationIdFactory) { + this.conversationIdFactory = withConversationIdFactory; + } + /** + * Gets the {@link ConversationState} to be used by the dialog. + * @return the ConversationState value as a getConversationState(). + */ + public ConversationState getConversationState() { + return this.conversationState; + } + + /** + * Sets the {@link ConversationState} to be used by the dialog. + * @param withConversationState The ConversationState value. + */ + public void setConversationState(ConversationState withConversationState) { + this.conversationState = withConversationState; + } + /** + * Gets the OAuth Connection Name, that would be used to perform + * Single SignOn with a skill. + * @return the ConnectionName value as a String. + */ + public String getConnectionName() { + return this.connectionName; + } + + /** + * Sets the OAuth Connection Name, that would be used to perform + * Single SignOn with a skill. + * @param withConnectionName The ConnectionName value. + */ + public void setConnectionName(String withConnectionName) { + this.connectionName = withConnectionName; + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillInvokeException.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillInvokeException.java new file mode 100644 index 000000000..afda96063 --- /dev/null +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillInvokeException.java @@ -0,0 +1,41 @@ +package com.microsoft.bot.dialogs; + +/** + * Exception used to report issues during the invoke method of the {@link SkillsDialog} class. + */ +public class SkillInvokeException extends RuntimeException { + + /** + * Serial Version for class. + */ + private static final long serialVersionUID = 1L; + + /** + * Construct with exception. + * + * @param t The cause. + */ + public SkillInvokeException(Throwable t) { + super(t); + } + + /** + * Construct with message. + * + * @param message The exception message. + */ + public SkillInvokeException(String message) { + super(message); + } + + /** + * Construct with caught exception and message. + * + * @param message The message. + * @param t The caught exception. + */ + public SkillInvokeException(String message, Throwable t) { + super(message, t); + } + +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java index 3fddb79f7..5bf261f20 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java @@ -47,7 +47,7 @@ public class DialogStateManager implements Map { */ private final String pathTracker = "dialog._tracker.paths"; - private static final char[] SEPARATORS = {',', '['}; + private static final char[] SEPARATORS = {',', '[' }; private final DialogContext dialogContext; private int version; @@ -324,7 +324,7 @@ public T getValue(String pathExpression, T defaultValue, Class clsType) { } ResultPair result = tryGetValue(pathExpression, clsType); - if (result.result()) { + if (result.result()) { return result.value(); } else { return defaultValue; @@ -487,7 +487,7 @@ public CompletableFuture deleteScopesMemory(String name) { return s.getName().toUpperCase() == uCaseName; }).findFirst().get(); if (scope != null) { - scope.delete(dialogContext).join(); + return scope.delete(dialogContext).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } @@ -808,7 +808,6 @@ public final Object remove(Object key) { public final void putAll(Map m) { } - @Override public final Set keySet() { return configuration.getMemoryScopes().stream().map(scope -> scope.getName()).collect(Collectors.toSet()); @@ -817,7 +816,7 @@ public final Set keySet() { @Override public final Collection values() { return configuration.getMemoryScopes().stream().map(scope -> scope.getMemory(dialogContext)) - .collect(Collectors.toSet()); + .collect(Collectors.toSet()); } @Override diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java index 6c30dae1a..f3cb1e230 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ActivityPrompt.java @@ -60,40 +60,42 @@ public ActivityPrompt(String dialogId, PromptValidator validator) { } /** - * Called when a prompt dialog is pushed onto the dialog stack and is being activated. + * Called when a prompt dialog is pushed onto the dialog stack and is being + * activated. * - * @param dc The dialog context for the current turn of the conversation. - * @param options Optional, additional information to pass to the prompt being started. + * @param dc The dialog context for the current turn of the conversation. + * @param options Optional, additional information to pass to the prompt being + * started. * - * @return A {@link CompletableFuture} representing the asynchronous operation. + * @return A {@link CompletableFuture} representing the asynchronous operation. * - * If the task is successful, the result indicates whether the prompt is still active after the - * turn has been processed by the prompt. + * If the task is successful, the result indicates whether the prompt is + * still active after the turn has been processed by the prompt. */ @Override public CompletableFuture beginDialog(DialogContext dc, Object options) { if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "dc cannot be null." - )); + return Async.completeExceptionally(new IllegalArgumentException("dc cannot be null.")); } if (!(options instanceof PromptOptions)) { - return Async.completeExceptionally(new IllegalArgumentException( - "Prompt options are required for Prompt dialogs" - )); + return Async.completeExceptionally( + new IllegalArgumentException("Prompt options are required for Prompt dialogs")); } // Ensure prompts have input hint set - // For Java this code isn't necessary as InputHint is an enumeration, so it's can't be not set to something. + // For Java this code isn't necessary as InputHint is an enumeration, so it's + // can't be not set to something. // PromptOptions opt = (PromptOptions) options; - // if (opt.getPrompt() != null && StringUtils.isBlank(opt.getPrompt().getInputHint().toString())) { - // opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // if (opt.getPrompt() != null && + // StringUtils.isBlank(opt.getPrompt().getInputHint().toString())) { + // opt.getPrompt().setInputHint(InputHints.EXPECTING_INPUT); // } - // if (opt.getRetryPrompt() != null && StringUtils.isBlank(opt.getRetryPrompt().getInputHint().toString())) { - // opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); + // if (opt.getRetryPrompt() != null && + // StringUtils.isBlank(opt.getRetryPrompt().getInputHint().toString())) { + // opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); // } // Initialize prompt state @@ -105,10 +107,10 @@ public CompletableFuture beginDialog(DialogContext dc, Object state.put(persistedState, persistedStateMap); // Send initial prompt - onPrompt(dc.getContext(), (Map) state.get(persistedState), - (PromptOptions) state.get(persistedOptions), false); + onPrompt(dc.getContext(), (Map) state.get(persistedState), + (PromptOptions) state.get(persistedOptions), false); - return CompletableFuture.completedFuture(END_OF_TURN); + return CompletableFuture.completedFuture(END_OF_TURN); } /** @@ -127,37 +129,39 @@ public CompletableFuture beginDialog(DialogContext dc, Object @Override public CompletableFuture continueDialog(DialogContext dc) { if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "dc cannot be null." - )); + return Async.completeExceptionally(new IllegalArgumentException("dc cannot be null.")); } // Perform base recognition DialogInstance instance = dc.getActiveDialog(); Map state = (Map) instance.getState().get(persistedState); PromptOptions options = (PromptOptions) instance.getState().get(persistedOptions); - PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); - - state.put(Prompt.ATTEMPTCOUNTKEY, (int) state.get(Prompt.ATTEMPTCOUNTKEY) + 1); + return onRecognize(dc.getContext(), state, options).thenCompose(recognized -> { + state.put(Prompt.ATTEMPTCOUNTKEY, (int) state.get(Prompt.ATTEMPTCOUNTKEY) + 1); + return validateContext(dc, state, options, recognized).thenCompose(isValid -> { + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } + + return onPrompt(dc.getContext(), state, options, true) + .thenCompose(result -> CompletableFuture.completedFuture(END_OF_TURN)); + }); + }); + } + private CompletableFuture validateContext(DialogContext dc, Map state, + PromptOptions options, PromptRecognizerResult recognized) { // Validate the return value boolean isValid = false; if (validator != null) { PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), recognized, state, options); - isValid = validator.promptValidator(promptContext).join(); + return validator.promptValidator(promptContext); } else if (recognized.getSucceeded()) { isValid = true; } - - // Return recognized value or re-prompt - if (isValid) { - return dc.endDialog(recognized.getValue()); - } - - onPrompt(dc.getContext(), state, options, true); - - return CompletableFuture.completedFuture(END_OF_TURN); + return CompletableFuture.completedFuture(isValid); } /** @@ -221,65 +225,63 @@ public CompletableFuture repromptDialog(TurnContext turnContext, DialogIns */ protected CompletableFuture onPrompt(TurnContext turnContext, Map state, PromptOptions options) { - onPrompt(turnContext, state, options, false).join(); - return CompletableFuture.completedFuture(null); + return onPrompt(turnContext, state, options, false).thenApply(result -> null); } /** * When overridden in a derived class, prompts the user for input. * - * @param turnContext Context for the current turn of conversation with the user. - * @param state Contains state for the current instance of the prompt on the - * dialog stack. - * @param options A prompt options Object constructed from the options initially - * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . - * @param isRetry A {@link Boolean} representing if the prompt is a retry. + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . + * @param isRetry A {@link Boolean} representing if the prompt is a retry. * - * @return A {@link CompletableFuture} representing the result of the asynchronous - * operation. + * @return A {@link CompletableFuture} representing the result of the + * asynchronous operation. */ - protected CompletableFuture onPrompt( - TurnContext turnContext, - Map state, - PromptOptions options, - Boolean isRetry) { + protected CompletableFuture onPrompt(TurnContext turnContext, Map state, + PromptOptions options, Boolean isRetry) { if (turnContext == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "turnContext cannot be null" - )); + return Async.completeExceptionally(new IllegalArgumentException("turnContext cannot be null")); } if (options == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "options cannot be null" - )); + return Async.completeExceptionally(new IllegalArgumentException("options cannot be null")); } if (isRetry && options.getRetryPrompt() != null) { - turnContext.sendActivity(options.getRetryPrompt()).join(); + return turnContext.sendActivity(options.getRetryPrompt()).thenApply(result -> null); } else if (options.getPrompt() != null) { - turnContext.sendActivity(options.getPrompt()).join(); + return turnContext.sendActivity(options.getPrompt()).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } /** - * When overridden in a derived class, attempts to recognize the incoming activity. + * When overridden in a derived class, attempts to recognize the incoming + * activity. * - * @param turnContext Context for the current turn of conversation with the user. - * @param state Contains state for the current instance of the prompt on the - * dialog stack. - * @param options A prompt options Object constructed from the options initially - * provided in the call to {@link DialogContext#prompt(String, PromptOptions)} . + * @param turnContext Context for the current turn of conversation with the + * user. + * @param state Contains state for the current instance of the prompt on + * the dialog stack. + * @param options A prompt options Object constructed from the options + * initially provided in the call to + * {@link DialogContext#prompt(String, PromptOptions)} . * - * @return A {@link CompletableFuture} representing the asynchronous operation. + * @return A {@link CompletableFuture} representing the asynchronous operation. * - * If the task is successful, the result describes the result of the recognition attempt. + * If the task is successful, the result describes the result of the + * recognition attempt. */ protected CompletableFuture> onRecognize(TurnContext turnContext, - Map state, PromptOptions options) { + Map state, PromptOptions options) { PromptRecognizerResult result = new PromptRecognizerResult(); result.setSucceeded(true); result.setValue(turnContext.getActivity()); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java index d7903050f..958c427d4 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/AttachmentPrompt.java @@ -74,9 +74,9 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } else if (options.getPrompt() != null) { - turnContext.sendActivity(options.getPrompt()).join(); + return turnContext.sendActivity(options.getPrompt()).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java index 9b8bb126f..e9c411d0c 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java @@ -245,9 +245,7 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } /** diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java index d9f9f003f..402d0f1a5 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -255,8 +255,7 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } /** diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java index 83cd57306..38c491542 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/DateTimePrompt.java @@ -104,9 +104,9 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } else if (options.getPrompt() != null) { - turnContext.sendActivity(options.getPrompt()).join(); + return turnContext.sendActivity(options.getPrompt()).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java index d95dd2be4..d211d7a17 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/NumberPrompt.java @@ -138,9 +138,9 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } else if (options.getPrompt() != null) { - turnContext.sendActivity(options.getPrompt()).join(); + return turnContext.sendActivity(options.getPrompt()).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java index ee87f2967..b16caa43b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/Prompt.java @@ -32,14 +32,15 @@ * Defines the core behavior of prompt dialogs. * * When the prompt ends, it should return a Object that represents the value - * that was prompted for. Use {@link com.microsoft.bot.dialogs.DialogSet#add(Dialog)} or - * {@link com.microsoft.bot.dialogs.ComponentDialog#addDialog(Dialog)} to add a prompt to - * a dialog set or component dialog, respectively. Use + * that was prompted for. Use + * {@link com.microsoft.bot.dialogs.DialogSet#add(Dialog)} or + * {@link com.microsoft.bot.dialogs.ComponentDialog#addDialog(Dialog)} to add a + * prompt to a dialog set or component dialog, respectively. Use * {@link DialogContext#prompt(String, PromptOptions)} or * {@link DialogContext#beginDialog(String, Object)} to start the prompt. If you * start a prompt from a {@link com.microsoft.bot.dialogs.WaterfallStep} in a - * {@link com.microsoft.bot.dialogs.WaterfallDialog}, then the prompt result will be - * available in the next step of the waterfall. + * {@link com.microsoft.bot.dialogs.WaterfallDialog}, then the prompt result + * will be available in the next step of the waterfall. * * @param Type the prompt is created for. */ @@ -61,8 +62,8 @@ public abstract class Prompt extends Dialog { * * The value of dialogId must be unique within the * {@link com.microsoft.bot.dialogs.DialogSet} or - * {@link com.microsoft.bot.dialogs.ComponentDialog} to which the - * prompt is added. + * {@link com.microsoft.bot.dialogs.ComponentDialog} to which + * the prompt is added. */ public Prompt(String dialogId, PromptValidator validator) { super(dialogId); @@ -89,15 +90,12 @@ public Prompt(String dialogId, PromptValidator validator) { public CompletableFuture beginDialog(DialogContext dc, Object options) { if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "dc cannot be null." - )); + return Async.completeExceptionally(new IllegalArgumentException("dc cannot be null.")); } if (!(options instanceof PromptOptions)) { - return Async.completeExceptionally(new IllegalArgumentException( - "Prompt options are required for Prompt dialogs" - )); + return Async.completeExceptionally( + new IllegalArgumentException("Prompt options are required for Prompt dialogs")); } // Ensure prompts have input hint set @@ -111,7 +109,6 @@ public CompletableFuture beginDialog(DialogContext dc, Object opt.getRetryPrompt().setInputHint(InputHints.EXPECTING_INPUT); } - // Initialize prompt state Map state = dc.getActiveDialog().getState(); state.put(PERSISTED_OPTIONS, opt); @@ -121,10 +118,8 @@ public CompletableFuture beginDialog(DialogContext dc, Object state.put(PERSISTED_STATE, pState); // Send initial prompt - onPrompt(dc.getContext(), - (Map) state.get(PERSISTED_STATE), - (PromptOptions) state.get(PERSISTED_OPTIONS), - false); + onPrompt(dc.getContext(), (Map) state.get(PERSISTED_STATE), + (PromptOptions) state.get(PERSISTED_OPTIONS), false); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } @@ -145,9 +140,7 @@ public CompletableFuture beginDialog(DialogContext dc, Object public CompletableFuture continueDialog(DialogContext dc) { if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "dc cannot be null." - )); + return Async.completeExceptionally(new IllegalArgumentException("dc cannot be null.")); } // Don't do anything for non-message activities @@ -159,30 +152,36 @@ public CompletableFuture continueDialog(DialogContext dc) { DialogInstance instance = dc.getActiveDialog(); Map state = (Map) instance.getState().get(PERSISTED_STATE); PromptOptions options = (PromptOptions) instance.getState().get(PERSISTED_OPTIONS); - PromptRecognizerResult recognized = onRecognize(dc.getContext(), state, options).join(); + return onRecognize(dc.getContext(), state, options).thenCompose(recognized -> { + state.put(ATTEMPTCOUNTKEY, (int) state.get(ATTEMPTCOUNTKEY) + 1); + + // Validate the return value + return validateContext(dc, state, options, recognized).thenCompose(isValid -> { + // Return recognized value or re-prompt + if (isValid) { + return dc.endDialog(recognized.getValue()); + } - state.put(ATTEMPTCOUNTKEY, (int) state.get(ATTEMPTCOUNTKEY) + 1); + if (!dc.getContext().getResponded()) { + return onPrompt(dc.getContext(), state, options, true).thenApply(result -> Dialog.END_OF_TURN); + } - // Validate the return value + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + }); + } + + private CompletableFuture validateContext(DialogContext dc, Map state, + PromptOptions options, PromptRecognizerResult recognized) { Boolean isValid = false; if (validator != null) { - PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), - recognized, state, options); - isValid = validator.promptValidator(promptContext).join(); + PromptValidatorContext promptContext = new PromptValidatorContext(dc.getContext(), recognized, state, + options); + return validator.promptValidator(promptContext); } else if (recognized.getSucceeded()) { isValid = true; } - - // Return recognized value or re-prompt - if (isValid) { - return dc.endDialog(recognized.getValue()); - } - - if (!dc.getContext().getResponded()) { - onPrompt(dc.getContext(), state, options, true); - } - - return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + return CompletableFuture.completedFuture(isValid); } /** @@ -210,8 +209,7 @@ public CompletableFuture resumeDialog(DialogContext dc, Dialog // dialogResume() when the pushed on dialog ends. // To avoid the prompt prematurely ending we need to implement this method and // simply re-prompt the user. - repromptDialog(dc.getContext(), dc.getActiveDialog()).join(); - return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + return repromptDialog(dc.getContext(), dc.getActiveDialog()).thenApply(finalResult -> Dialog.END_OF_TURN); } /** @@ -228,32 +226,31 @@ public CompletableFuture resumeDialog(DialogContext dc, Dialog public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { Map state = (Map) instance.getState().get(PERSISTED_STATE); PromptOptions options = (PromptOptions) instance.getState().get(PERSISTED_OPTIONS); - onPrompt(turnContext, state, options, false).join(); - return CompletableFuture.completedFuture(null); + return onPrompt(turnContext, state, options, false).thenApply(result -> null); } /** * Called before an event is bubbled to its parent. * - * This is a good place to perform interception of an event as returning `true` will prevent - * any further bubbling of the event to the dialogs parents and will also prevent any child - * dialogs from performing their default processing. + * This is a good place to perform interception of an event as returning `true` + * will prevent any further bubbling of the event to the dialogs parents and + * will also prevent any child dialogs from performing their default processing. * - * @param dc The dialog context for the current turn of conversation. - * @param e The event being raised. + * @param dc The dialog context for the current turn of conversation. + * @param e The event being raised. * - * @return Whether the event is handled by the current dialog and further processing - * should stop. + * @return Whether the event is handled by the current dialog and further + * processing should stop. */ @Override protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { - if (e.getName() == DialogEvents.ACTIVITY_RECEIVED - && dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + if (e.getName().equals(DialogEvents.ACTIVITY_RECEIVED) + && dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { // Perform base recognition Map state = dc.getActiveDialog().getState(); - PromptRecognizerResult recognized = onRecognize(dc.getContext(), - (Map) state.get(PERSISTED_STATE), (PromptOptions) state.get(PERSISTED_OPTIONS)).join(); - return CompletableFuture.completedFuture(recognized.getSucceeded()); + return onRecognize(dc.getContext(), (Map) state.get(PERSISTED_STATE), + (PromptOptions) state.get(PERSISTED_OPTIONS)) + .thenCompose(recognized -> CompletableFuture.completedFuture(recognized.getSucceeded())); } return CompletableFuture.completedFuture(false); @@ -311,55 +308,57 @@ protected abstract CompletableFuture> onRecognize(Turn * * @return A {@link CompletableFuture} representing the asynchronous operation. * - * If the task is successful, the result contains the updated activity. + * If the task is successful, the result contains the updated activity. */ - protected Activity appendChoices(Activity prompt, String channelId, List choices, - ListStyle style, ChoiceFactoryOptions options) { + protected Activity appendChoices(Activity prompt, String channelId, List choices, ListStyle style, + ChoiceFactoryOptions options) { // Get base prompt text (if any) String text = ""; if (prompt != null && prompt.getText() != null && StringUtils.isNotBlank(prompt.getText())) { - text = prompt.getText(); + text = prompt.getText(); } // Create temporary msg Activity msg; switch (style) { - case INLINE: - msg = ChoiceFactory.inline(choices, text, null, options); - break; - - case LIST: - msg = ChoiceFactory.list(choices, text, null, options); - break; - - case SUGGESTED_ACTION: - msg = ChoiceFactory.suggestedAction(choices, text); - break; - - case HEROCARD: - msg = ChoiceFactory.heroCard(choices, text); - break; - - case NONE: - msg = Activity.createMessageActivity(); - msg.setText(text); - break; - - default: - msg = ChoiceFactory.forChannel(channelId, choices, text, null, options); - break; + case INLINE: + msg = ChoiceFactory.inline(choices, text, null, options); + break; + + case LIST: + msg = ChoiceFactory.list(choices, text, null, options); + break; + + case SUGGESTED_ACTION: + msg = ChoiceFactory.suggestedAction(choices, text); + break; + + case HEROCARD: + msg = ChoiceFactory.heroCard(choices, text); + break; + + case NONE: + msg = Activity.createMessageActivity(); + msg.setText(text); + break; + + default: + msg = ChoiceFactory.forChannel(channelId, choices, text, null, options); + break; } // Update prompt with text, actions and attachments if (prompt != null) { - // clone the prompt the set in the options (note ActivityEx has Properties so this is the safest mechanism) - //prompt = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(prompt)); + // clone the prompt the set in the options (note ActivityEx has Properties so + // this is the safest mechanism) + // prompt = + // JsonConvert.DeserializeObject(JsonConvert.SerializeObject(prompt)); prompt = Activity.clone(prompt); prompt.setText(msg.getText()); if (msg.getSuggestedActions() != null && msg.getSuggestedActions().getActions() != null - && msg.getSuggestedActions().getActions().size() > 0) { + && msg.getSuggestedActions().getActions().size() > 0) { prompt.setSuggestedActions(msg.getSuggestedActions()); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java index 20869b2a7..a41e04a50 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/TextPrompt.java @@ -73,9 +73,9 @@ protected CompletableFuture onPrompt(TurnContext turnContext, Map null); } else if (options.getPrompt() != null) { - turnContext.sendActivity(options.getPrompt()).join(); + return turnContext.sendActivity(options.getPrompt()).thenApply(result -> null); } return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java index a2f19afb5..490bf7c9b 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -470,7 +470,7 @@ class TestSendActivities implements SendActivitiesHandler { public CompletableFuture invoke(TurnContext context, List activities, Supplier> next) { for (Activity activity : activities) { - if (activity.getType() == ActivityTypes.END_OF_CONVERSATION) { + if (activity.getType().equals(ActivityTypes.END_OF_CONVERSATION)) { _eocSent = activity; break; } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogTestClient.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogTestClient.java new file mode 100644 index 000000000..7f865da1c --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogTestClient.java @@ -0,0 +1,217 @@ +package com.microsoft.bot.dialogs; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotCallbackHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.Middleware; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.schema.Activity; + +public class DialogTestClient { + + private DialogContext dialogContext; + + private DialogTurnResult dialogTurnResult; + + private ConversationState conversationState; + + private final BotCallbackHandler _callback; + private final TestAdapter testAdapter; + + /** + * Initializes a new instance of the {@link DialogTestClient} class. + * + * @param channelId The channelId (see {@link Channels} ) to be used for the test. Use + * {@link Channels#emulator} or {@link Channels#test} if you are uncertain + * of the channel you are targeting. Otherwise, it is recommended that you + * use the id for the channel(s) your bot will be using. Consider writing a + * test case for each channel. + * @param targetDialog The dialog to be tested. This will + * be the root dialog for the test client. + * @param initialDialogOptions (Optional) additional argument(s) to + * pass to the dialog being started. + * @param middlewares (Optional) A list of middlewares to + * be added to the test adapter. + * @param conversationState (Optional) A + * {@link ConversationState} to use in the test client. + */ + public DialogTestClient( + String channelId, + Dialog targetDialog, + Object initialDialogOptions, + List middlewares, + ConversationState conversationState + ) { + if (conversationState == null) { + this.conversationState = new ConversationState(new MemoryStorage()); + } else { + this.conversationState = conversationState; + } + this.testAdapter = new TestAdapter(channelId).use(new AutoSaveStateMiddleware(conversationState)); + + addUserMiddlewares(middlewares); + + StatePropertyAccessor dialogState = getConversationState().createProperty("DialogState"); + + _callback = getDefaultCallback(targetDialog, initialDialogOptions, dialogState); + } + + /** + * Initializes a new instance of the {@link DialogTestClient} class. + * + * @param testAdapter The {@link TestAdapter} to use. + * @param targetDialog The dialog to be tested. This will + * be the root dialog for the test client. + * @param initialDialogOptions (Optional) additional argument(s) to + * pass to the dialog being started. + * @param middlewares (Optional) A list of middlewares to + * be added to the test adapter. + * * @param conversationState (Optional) A + * {@link ConversationState} to use in the test client. + */ + public DialogTestClient( + TestAdapter testAdapter, + Dialog targetDialog, + Object initialDialogOptions, + List middlewares, + ConversationState conversationState + ) { + + if (conversationState == null) { + this.conversationState = new ConversationState(new MemoryStorage()); + } else { + this.conversationState = conversationState; + } + this.testAdapter = testAdapter.use(new AutoSaveStateMiddleware(conversationState)); + + addUserMiddlewares(middlewares); + + StatePropertyAccessor dialogState = getConversationState().createProperty("DialogState"); + + _callback = getDefaultCallback(targetDialog, initialDialogOptions, dialogState); + } + + /** + * Sends an {@link Activity} to the target dialog. + * + * @param activity The activity to send. + * + * @return A {@link CompletableFuture} representing the result of + * the asynchronous operation. + */ + public CompletableFuture sendActivity(Activity activity) { + testAdapter.processActivity(activity, _callback).join(); + return CompletableFuture.completedFuture(getNextReply()); + } + + /** + * Sends a message activity to the target dialog. + * + * @param text The text of the message to send. + * + * @return A {@link CompletableFuture} representing the result of + * the asynchronous operation. + */ + public CompletableFuture sendActivity(String text){ + testAdapter.sendTextToBot(text, _callback).join(); + return CompletableFuture.completedFuture(getNextReply()); + } + + /** + * Gets the next bot response. + * + * @return The next activity in the queue; or null, if the queue + * is empty. + * @param the type. + */ + public T getNextReply() { + return (T) testAdapter.getNextReply(); + } + + private BotCallbackHandler getDefaultCallback( + Dialog targetDialog, + Object initialDialogOptions, + StatePropertyAccessor dialogState + ) { + BotCallbackHandler handler = + (turnContext) -> { + // Ensure dialog state instanceof created and pass it to DialogSet. + dialogState.get(turnContext, () -> new DialogState()); + DialogSet dialogs = new DialogSet(dialogState); + dialogs.add(targetDialog); + + dialogContext = dialogs.createContext(turnContext).join(); + dialogTurnResult = dialogContext.continueDialog().join(); + switch (dialogTurnResult.getStatus()) { + case EMPTY: + dialogTurnResult = dialogContext.beginDialog(targetDialog.getId(), initialDialogOptions).join(); + break; + case COMPLETE: + default: + // Dialog has ended + break; + } + return CompletableFuture.completedFuture(null); + }; + return handler; + } + + private void addUserMiddlewares(List middlewares) { + if (middlewares != null) { + for (Middleware middleware : middlewares) { + testAdapter.use(middleware); + } + } + } + /** + * Gets a reference for the {@link DialogContext} . + * This property will be null until at least one activity is sent to + * {@link DialogTestClient} . + * @return the DialogContext value as a getDialogContext(). + */ + public DialogContext getDialogContext() { + return this.dialogContext; + } + + /** + * Gets a reference for the {@link DialogContext} . + * This property will be null until at least one activity is sent to + * {@link DialogTestClient} . + * @param withDialogContext The DialogContext value. + */ + private void setDialogContext(DialogContext withDialogContext) { + this.dialogContext = withDialogContext; + } + + /** + * Gets the latest {@link DialogTurnResult} for the dialog being tested. + * @return the DialogTurnResult value as a getDialogTurnResult(). + */ + public DialogTurnResult getDialogTurnResult() { + return this.dialogTurnResult; + } + + /** + * Gets the latest {@link DialogTurnResult} for the dialog being tested. + * @param withDialogTurnResult The DialogTurnResult value. + */ + private void setDialogTurnResult(DialogTurnResult withDialogTurnResult) { + this.dialogTurnResult = withDialogTurnResult; + } + + /** + * Gets the latest {@link ConversationState} for + * {@link DialogTestClient} . + * @return the ConversationState value as a getConversationState(). + */ + public ConversationState getConversationState() { + return this.conversationState; + } + +} + diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java index e425793ab..9c84babec 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/LamdbaDialog.java @@ -39,8 +39,6 @@ public LamdbaDialog(String testName, DialogTestFunction function) { */ @Override public CompletableFuture beginDialog(DialogContext dc, Object options) { - func.runTest(dc).join(); - return dc.endDialog(); + return func.runTest(dc).thenCompose(result -> dc.endDialog()); } - } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java index f4b02fb00..579c1336c 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ReplaceDialogTests.java @@ -137,7 +137,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class ReplaceAction implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - if ((String) stepContext.getResult() == "replace") { + if (((String)stepContext.getResult()).equals("replace")) { return stepContext.replaceDialog("SecondDialog"); } else { return stepContext.next(null); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java new file mode 100644 index 000000000..6c56f035b --- /dev/null +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java @@ -0,0 +1,705 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.dialogs; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TypedInvokeResponse; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.skills.BotFrameworkClient; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.DeliveryModes; +import com.microsoft.bot.schema.ExpectedReplies; +import com.microsoft.bot.schema.OAuthCard; +import com.microsoft.bot.schema.TokenExchangeResource; + +import org.junit.Assert; +import org.junit.Test; + +/** + * Tests for SkillsDialog. + */ +public class SkillDialogTests { + + @Test + public void ConstructorValidationTests() { + Assert.assertThrows(IllegalArgumentException.class, () -> new SkillDialog(null, null)); + } + + @Test + public void BeginDialogOptionsValidation() { + SkillDialogOptions dialogOptions = new SkillDialogOptions(); + SkillDialog sut = new SkillDialog(dialogOptions, null); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + DialogTestClient client = new DialogTestClient(Channels.TEST, sut, null, null, null); + client.sendActivity("irrelevant").join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + DialogTestClient client = new DialogTestClient(Channels.TEST, sut, new HashMap(), null, + null); + client.sendActivity("irrelevant").join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + + Assert.assertThrows(IllegalArgumentException.class, () -> { + try { + DialogTestClient client = new DialogTestClient(Channels.TEST, sut, new BeginSkillDialogOptions(), null, + null); + client.sendActivity("irrelevant").join(); + } catch (CompletionException ex) { + throw ex.getCause(); + } + }); + } + + @Test + public void BeginDialogCallsSkill_null() { + beginDialogCallsSkill(null); + } + + @Test + public void BeginDialogCallsSkill_Expect_Replies() { + beginDialogCallsSkill(DeliveryModes.EXPECT_REPLIES.toString()); + } + + class MockFrameworkClient extends BotFrameworkClient { + + int returnStatus = 200; + ExpectedReplies expectedReplies = null;; + + MockFrameworkClient() { + + } + + MockFrameworkClient(int returnStatus) { + this.returnStatus = returnStatus; + } + + MockFrameworkClient(int returnStatus, ExpectedReplies expectedReplies) { + this.returnStatus = returnStatus; + this.expectedReplies = expectedReplies; + } + + @Override + public CompletableFuture> postActivity( + String fromBotId, + String toBotId, + URI toUri, + URI serviceUrl, + String conversationId, + Activity activity, + Class type + ) { + fromBotIdSent = fromBotId; + toBotIdSent = toBotId; + toUriSent = toUri; + activitySent = activity; + List activities = new ArrayList(); + activities.add(MessageFactory.text("dummy activity")); + ExpectedReplies activityList = new ExpectedReplies(activities); + if (expectedReplies != null) { + TypedInvokeResponse response = new TypedInvokeResponse(returnStatus, expectedReplies); + return CompletableFuture.completedFuture(response); + } else { + TypedInvokeResponse response = new TypedInvokeResponse(returnStatus, activityList); + return CompletableFuture.completedFuture(response); + } + } + + public String fromBotIdSent; + public String toBotIdSent; + public URI toUriSent; + public Activity activitySent; + } + + class MockFrameworkClientExtended extends BotFrameworkClient { + + int returnStatus = 200; + ExpectedReplies expectedReplies = null; + int iterationCount = 0; + + MockFrameworkClientExtended(int returnStatus, ExpectedReplies expectedReplies) { + this.returnStatus = returnStatus; + this.expectedReplies = expectedReplies; + } + + @Override + public CompletableFuture> postActivity( + String fromBotId, + String toBotId, + URI toUri, + URI serviceUrl, + String conversationId, + Activity activity, + Class type + ) { + fromBotIdSent = fromBotId; + toBotIdSent = toBotId; + toUriSent = toUri; + activitySent = activity; + List activities = new ArrayList(); + activities.add(MessageFactory.text("dummy activity")); + ExpectedReplies activityList = new ExpectedReplies(activities); + if (iterationCount == 0) { + TypedInvokeResponse response = new TypedInvokeResponse(200, expectedReplies); + iterationCount++; + return CompletableFuture.completedFuture(response); + } else { + TypedInvokeResponse response = new TypedInvokeResponse(returnStatus, null); + return CompletableFuture.completedFuture(response); + } + } + + public String fromBotIdSent; + public String toBotIdSent; + public URI toUriSent; + public Activity activitySent; + } + + + + + public void beginDialogCallsSkill(String deliveryMode) { + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(); + + // Use Memory for conversation state + ConversationState conversationState = new ConversationState(new MemoryStorage()); + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + + // Create the SkillDialogInstance and the activity to send. + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = Activity.createMessageActivity(); + activityToSend.setDeliveryMode(deliveryMode); + activityToSend.setText(UUID.randomUUID().toString()); + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + Channels.TEST, + sut, + skillDialogOptions, + null, + conversationState); + + Assert.assertEquals(0, ((SimpleConversationIdFactory) dialogOptions.getConversationIdFactory()).createCount); + + // Send something to the dialog to start it + client.sendActivity("irrelevant").join(); + + // Assert results and data sent to the SkillClient for fist turn + Assert.assertEquals(1, ((SimpleConversationIdFactory) dialogOptions.getConversationIdFactory()).createCount); + Assert.assertEquals(dialogOptions.getBotId(), mockSkillClient.fromBotIdSent); + Assert.assertEquals(dialogOptions.getSkill().getAppId(), mockSkillClient.toBotIdSent); + Assert.assertEquals(dialogOptions.getSkill().getSkillEndpoint().toString(), + mockSkillClient.toUriSent.toString()); + Assert.assertEquals(activityToSend.getText(), mockSkillClient.activitySent.getText()); + Assert.assertEquals(DialogTurnStatus.WAITING, client.getDialogTurnResult().getStatus()); + + // Send a second message to continue the dialog + client.sendActivity("Second message").join(); + Assert.assertEquals(1, ((SimpleConversationIdFactory) dialogOptions.getConversationIdFactory()).createCount); + + // Assert results for second turn + Assert.assertEquals("Second message", mockSkillClient.activitySent.getText()); + Assert.assertEquals(DialogTurnStatus.WAITING, client.getDialogTurnResult().getStatus()); + + // Send EndOfConversation to the dialog + client.sendActivity(Activity.createEndOfConversationActivity()).join(); + + // Assert we are done. + Assert.assertEquals(DialogTurnStatus.COMPLETE, client.getDialogTurnResult().getStatus()); + } + + @Test + public void ShouldHandleInvokeActivities() { + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(); + + // Use Memory for conversation state + ConversationState conversationState = new ConversationState(new MemoryStorage()); + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + + Activity activityToSend = Activity.createInvokeActivity(); + activityToSend.setName(UUID.randomUUID().toString()); + + // Create the SkillDialogInstance and the activity to send. + SkillDialog sut = new SkillDialog(dialogOptions, null); + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + Channels.TEST, + sut, + skillDialogOptions, + null, + conversationState); + + // Send something to the dialog to start it + client.sendActivity("irrelevant").join(); + + // Assert results and data sent to the SkillClient for fist turn + Assert.assertEquals(dialogOptions.getBotId(), mockSkillClient.fromBotIdSent); + Assert.assertEquals(dialogOptions.getSkill().getAppId(), mockSkillClient.toBotIdSent); + Assert.assertEquals(dialogOptions.getSkill().getSkillEndpoint().toString(), + mockSkillClient.toUriSent.toString()); + Assert.assertEquals(activityToSend.getName(), mockSkillClient.activitySent.getName()); + Assert.assertEquals(DeliveryModes.EXPECT_REPLIES.toString(), mockSkillClient.activitySent.getDeliveryMode()); + Assert.assertEquals(activityToSend.getText(), mockSkillClient.activitySent.getText()); + Assert.assertEquals(DialogTurnStatus.WAITING, client.getDialogTurnResult().getStatus()); + + // Send a second message to continue the dialog + client.sendActivity("Second message").join(); + + // Assert results for second turn + Assert.assertEquals("Second message", mockSkillClient.activitySent.getText()); + Assert.assertEquals(DialogTurnStatus.WAITING, client.getDialogTurnResult().getStatus()); + + // Send EndOfConversation to the dialog + client.sendActivity(Activity.createEndOfConversationActivity()).join(); + + // Assert we are done. + Assert.assertEquals(DialogTurnStatus.COMPLETE, client.getDialogTurnResult().getStatus()); + } + + @Test + public void CancelDialogSendsEoC() { + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(); + + // Use Memory for conversation state + ConversationState conversationState = new ConversationState(new MemoryStorage()); + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + + Activity activityToSend = Activity.createMessageActivity(); + activityToSend.setName(UUID.randomUUID().toString()); + + // Create the SkillDialogInstance and the activity to send. + SkillDialog sut = new SkillDialog(dialogOptions, null); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + Channels.TEST, + sut, + skillDialogOptions, + null, + conversationState); + + // Send something to the dialog to start it + client.sendActivity("irrelevant").join(); + + // Cancel the dialog so it sends an EoC to the skill + client.getDialogContext().cancelAllDialogs(); + + Assert.assertEquals(ActivityTypes.END_OF_CONVERSATION, mockSkillClient.activitySent.getType()); + } + + @Test + public void ShouldThrowHttpExceptionOnPostFailure() { + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(500); + + // Use Memory for conversation state + ConversationState conversationState = new ConversationState(new MemoryStorage()); + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + + Activity activityToSend = Activity.createMessageActivity(); + activityToSend.setName(UUID.randomUUID().toString()); + + // Create the SkillDialogInstance and the activity to send. + SkillDialog sut = new SkillDialog(dialogOptions, null); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + Channels.TEST, + sut, + skillDialogOptions, + null, + conversationState); + + // Send something to the dialog + Assert.assertThrows(Exception.class, () -> client.sendActivity("irrelevant")); + } + + @Test + public void ShouldInterceptOAuthCardsForSso() { + String connectionName = "connectionName"; + List replyList = new ArrayList(); + replyList.add(createOAuthCardAttachmentActivity("https://test")); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(200, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + TestAdapter testAdapter = new TestAdapter(Channels.TEST) + .use(new AutoSaveStateMiddleware(conversationState)); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, connectionName); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = createSendActivity(); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + testAdapter, + sut, + skillDialogOptions, + null, + conversationState); + + testAdapter.addExchangeableToken(connectionName, Channels.TEST, "user1", "https://test", "https://test1"); + Activity finalActivity = client.sendActivity("irrelevant").join(); + Assert.assertNull(finalActivity); + } + + @Test + public void ShouldNotInterceptOAuthCardsForEmptyConnectionName() { + String connectionName = "connectionName"; + List replyList = new ArrayList(); + replyList.add(createOAuthCardAttachmentActivity("https://test")); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(200, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + TestAdapter testAdapter = new TestAdapter(Channels.TEST) + .use(new AutoSaveStateMiddleware(conversationState)); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = createSendActivity(); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + testAdapter, + sut, + skillDialogOptions, + null, + conversationState); + + testAdapter.addExchangeableToken(connectionName, Channels.TEST, "user1", "https://test", "https://test1"); + Activity finalActivity = client.sendActivity("irrelevant").join(); + Assert.assertNotNull(finalActivity); + Assert.assertTrue(finalActivity.getAttachments().size() == 1); + } + + @Test + public void ShouldNotInterceptOAuthCardsForEmptyToken() { + List replyList = new ArrayList(); + replyList.add(createOAuthCardAttachmentActivity("https://test")); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(200, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + TestAdapter testAdapter = new TestAdapter(Channels.TEST) + .use(new AutoSaveStateMiddleware(conversationState)); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = createSendActivity(); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + testAdapter, + sut, + skillDialogOptions, + null, + conversationState); + + Activity finalActivity = client.sendActivity("irrelevant").join(); + Assert.assertNotNull(finalActivity); + Assert.assertTrue(finalActivity.getAttachments().size() == 1); + } + + @Test + public void ShouldNotInterceptOAuthCardsForTokenException() { + String connectionName = "connectionName"; + List replyList = new ArrayList(); + replyList.add(createOAuthCardAttachmentActivity("https://test")); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(200, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + TestAdapter testAdapter = new TestAdapter(Channels.TEST) + .use(new AutoSaveStateMiddleware(conversationState)); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = createSendActivity(); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + testAdapter, + sut, + skillDialogOptions, + null, + conversationState); + + testAdapter.throwOnExchangeRequest(connectionName, Channels.TEST, "user1", "https://test"); + Activity finalActivity = client.sendActivity("irrelevant").join(); + Assert.assertNotNull(finalActivity); + Assert.assertTrue(finalActivity.getAttachments().size() == 1); + } + + @Test + public void ShouldNotInterceptOAuthCardsForBadRequest() { + List replyList = new ArrayList(); + replyList.add(createOAuthCardAttachmentActivity("https://test")); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClientExtended mockSkillClient = new MockFrameworkClientExtended(409, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + TestAdapter testAdapter = new TestAdapter(Channels.TEST) + .use(new AutoSaveStateMiddleware(conversationState)); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, null); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = createSendActivity(); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + testAdapter, + sut, + skillDialogOptions, + null, + conversationState); + + Activity finalActivity = client.sendActivity("irrelevant").join(); + Assert.assertNotNull(finalActivity); + Assert.assertTrue(finalActivity.getAttachments().size() == 1); + } + + @Test + public void EndOfConversationFromExpectRepliesCallsDeleteConversationReference() { + List replyList = new ArrayList(); + replyList.add(Activity.createEndOfConversationActivity()); + ExpectedReplies firstResponse = new ExpectedReplies(); + firstResponse.setActivities(replyList); + + // Create a mock skill client to intercept calls and capture what is sent. + MockFrameworkClient mockSkillClient = new MockFrameworkClient(200, firstResponse); + + ConversationState conversationState = new ConversationState(new MemoryStorage()); + + SkillDialogOptions dialogOptions = createSkillDialogOptions(conversationState, mockSkillClient, ""); + SkillDialog sut = new SkillDialog(dialogOptions, null); + Activity activityToSend = Activity.createMessageActivity(); + activityToSend.setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); + activityToSend.setText(UUID.randomUUID().toString()); + + BeginSkillDialogOptions skillDialogOptions = new BeginSkillDialogOptions(); + skillDialogOptions.setActivity(activityToSend); + DialogTestClient client = new DialogTestClient( + Channels.TEST, + sut, + skillDialogOptions, + null, + conversationState); + + // Send something to the dialog to start it + client.sendActivity("hello"); + + SimpleConversationIdFactory factory = null; + if (dialogOptions.getConversationIdFactory() != null + && dialogOptions.getConversationIdFactory() instanceof SimpleConversationIdFactory){ + factory = (SimpleConversationIdFactory) dialogOptions.getConversationIdFactory(); + } + Assert.assertNotNull(factory); + Assert.assertEquals(factory.getConversationRefs().size(), 0); + Assert.assertEquals(1, factory.getCreateCount()); + } + + + private static Activity createOAuthCardAttachmentActivity(String uri) { + + OAuthCard oauthCard = new OAuthCard(); + TokenExchangeResource tokenExchangeResource = new TokenExchangeResource(); + tokenExchangeResource.setUri(uri); + oauthCard.setTokenExchangeResource(tokenExchangeResource); + Attachment attachment = new Attachment(); + attachment.setContentType(OAuthCard.CONTENTTYPE); + attachment.setContent(oauthCard); + + Activity attachmentActivity = MessageFactory.attachment(attachment); + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId(UUID.randomUUID().toString()); + attachmentActivity.setConversation(conversationAccount); + attachmentActivity.setFrom(new ChannelAccount("blah", "name")); + + return attachmentActivity; + } + + /** + * Helper to create a {@link SkillDialogOptions} for the skillDialog. + * + * @param conversationState The conversation state Object. + * @param mockSkillClient The skill client mock. + * + * @return A Skill Dialog Options Object. + */ + private SkillDialogOptions createSkillDialogOptions(ConversationState conversationState, + BotFrameworkClient mockSkillClient, String connectionName) { + SkillDialogOptions dialogOptions = new SkillDialogOptions(); + dialogOptions.setBotId(UUID.randomUUID().toString()); + try { + dialogOptions.setSkillHostEndpoint(new URI("http://test.contoso.com/skill/messages")); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + dialogOptions.setConversationIdFactory(new SimpleConversationIdFactory()); + dialogOptions.setConversationState(conversationState); + dialogOptions.setSkillClient(mockSkillClient); + BotFrameworkSkill skill = new BotFrameworkSkill(); + skill.setAppId(UUID.randomUUID().toString()); + try { + skill.setSkillEndpoint(new URI("http://testskill.contoso.com/api/messages")); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + dialogOptions.setSkill(skill); + dialogOptions.setConnectionName(connectionName); + return dialogOptions; + } + + // private static Mock CreateMockSkillClient( + // Action + // captureAction, + // int returnStatus=200,ListexpectedReplies) { + // var mockSkillClient=new Mock();var activityList=new + // ExpectedReplies(expectedReplies??new + // List{MessageFactory.Text("dummy activity")}); + + // if(captureAction!=null){mockSkillClient.Setup(x->x.PostActivity(It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny())).Returns(Task.FromResult(new + // InvokeResponse{Status=returnStatus,Body=activityList})).Callback(captureAction);}else{mockSkillClient.Setup(x->x.PostActivity(It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny(),It.IsAny())).Returns(Task.FromResult(new + // InvokeResponse{Status=returnStatus,Body=activityList}));} + + // return mockSkillClient; + // } + + private Activity createSendActivity() { + Activity activityToSend = Activity.createMessageActivity(); + activityToSend.setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); + activityToSend.setText(UUID.randomUUID().toString()); + return activityToSend; + } + + /** + * Simple factory to that extends SkillConversationIdFactoryBase. + */ + protected class SimpleConversationIdFactory extends SkillConversationIdFactoryBase { + + // Helper property to assert how many times instanceof + // CreateSkillConversationIdAsync called. + private int createCount; + private Map conversationRefs = new HashMap(); + + protected SimpleConversationIdFactory() { + + } + + @Override + public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { + createCount++; + + String key = Integer.toString(String.format("%s%s", options.getActivity().getConversation().getId(), + options.getActivity().getServiceUrl()).hashCode()); + SkillConversationReference skillConversationReference = new SkillConversationReference(); + skillConversationReference.setConversationReference(options.getActivity().getConversationReference()); + skillConversationReference.setOAuthScope(options.getFromBotOAuthScope()); + conversationRefs.put(key, skillConversationReference); + return CompletableFuture.completedFuture(key); + } + + @Override + public CompletableFuture getSkillConversationReference(String skillConversationId) { + return CompletableFuture.completedFuture(conversationRefs.get(skillConversationId)); + } + + @Override + public CompletableFuture deleteConversationReference(String skillConversationId) { + + conversationRefs.remove(skillConversationId); + return CompletableFuture.completedFuture(null); + } + + /** + * @return the ConversationRefs value as a Map. + */ + public Map getConversationRefs() { + return this.conversationRefs; + } + + /** + * @param withConversationRefs The ConversationRefs value. + */ + private void setConversationRefs(Map withConversationRefs) { + this.conversationRefs = withConversationRefs; + } + + /** + * @return the CreateCount value as a int. + */ + public int getCreateCount() { + return this.createCount; + } + + /** + * @param withCreateCount The CreateCount value. + */ + private void setCreateCount(int withCreateCount) { + this.createCount = withCreateCount; + } + } +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java index e0038d656..85ba09df3 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java @@ -279,7 +279,7 @@ public CompletableFuture promptValidator(PromptValidatorContext 0); Activity activity = promptContext.getRecognized().getValue(); - if (activity.getType() == ActivityTypes.EVENT) { + if (activity.getType().equals(ActivityTypes.EVENT)) { if ((int) activity.getValue() == 2) { promptContext.getRecognized().setValue(MessageFactory.text(activity.getValue().toString())); return CompletableFuture.completedFuture(true); diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpAdapter.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpAdapter.java index 007f461ad..7ab0b0a9d 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpAdapter.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpAdapter.java @@ -6,6 +6,7 @@ import com.microsoft.bot.builder.Bot; import com.microsoft.bot.builder.BotFrameworkAdapter; import com.microsoft.bot.builder.InvokeResponse; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; import com.microsoft.bot.connector.authentication.ChannelProvider; import com.microsoft.bot.connector.authentication.ChannelValidation; import com.microsoft.bot.connector.authentication.CredentialProvider; @@ -43,6 +44,35 @@ public BotFrameworkHttpAdapter(Configuration withConfiguration) { } } + /** + * Construct with a Configuration. This will create a CredentialProvider and + * ChannelProvider based on configuration values. + * + * @param withConfiguration The Configuration to use. + * @param withAuthenticationConfiguration The AuthenticationConfiguration to use. + * + * @see ClasspathPropertiesConfiguration + */ + public BotFrameworkHttpAdapter( + Configuration withConfiguration, + AuthenticationConfiguration withAuthenticationConfiguration + ) { + super( + new ConfigurationCredentialProvider(withConfiguration), + withAuthenticationConfiguration, + new ConfigurationChannelProvider(withConfiguration), + null, + null + ); + + String openIdEndPoint = withConfiguration.getProperty("BotOpenIdMetadata"); + if (!StringUtils.isEmpty(openIdEndPoint)) { + // Indicate which Cloud we are using, for example, Public or Sovereign. + ChannelValidation.setOpenIdMetaDataUrl(openIdEndPoint); + GovernmentChannelValidation.setOpenIdMetaDataUrl(openIdEndPoint); + } + } + /** * Constructs with CredentialProvider and ChannelProvider. * diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpClient.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpClient.java new file mode 100644 index 000000000..e7cea3c96 --- /dev/null +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/BotFrameworkHttpClient.java @@ -0,0 +1,303 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.integration; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; +import com.microsoft.bot.connector.authentication.MicrosoftGovernmentAppCredentials; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; + +import okhttp3.HttpUrl; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.bot.builder.TypedInvokeResponse; +import com.microsoft.bot.builder.skills.BotFrameworkClient; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.RoleTypes; +import com.microsoft.bot.schema.Serialization; + +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.net.URI; + +/** + * Class for posting activities securely to a bot using BotFramework HTTP + * protocol. + * + * This class can be used to securely post activities to a bot using the Bot + * Framework HTTP protocol. There are 2 usage patterns:* Forwarding activity to + * a Skill (Bot -> Bot as a Skill) which is done via PostActivity(fromBotId, + * toBotId, endpoint, serviceUrl, activity);* Posting an activity to yourself + * (External service -> Bot) which is done via PostActivity(botId, endpoint, + * activity)The latter is used by external services such as webjobs that need to + * post activities to the bot using the bots own credentials. + */ +public class BotFrameworkHttpClient extends BotFrameworkClient { + + private static Map appCredentialMapCache = new HashMap();; + + private ChannelProvider channelProvider; + + private CredentialProvider credentialProvider; + + private OkHttpClient httpClient; + + /** + * Initializes a new instance of the {@link BotFrameworkHttpClient} class. + * + * @param credentialProvider An instance of {@link CredentialProvider} . + * @param channelProvider An instance of {@link ChannelProvider} . + */ + public BotFrameworkHttpClient(CredentialProvider credentialProvider, ChannelProvider channelProvider) { + + if (credentialProvider == null) { + throw new IllegalArgumentException("credentialProvider cannot be null."); + } + this.credentialProvider = credentialProvider; + this.channelProvider = channelProvider; + this.httpClient = new OkHttpClient(); + } + + /** + * Forwards an activity to a skill (bot). + * + * NOTE: Forwarding an activity to a skill will flush UserState and + * ConversationState changes so that skill has accurate state. + * + * @param fromBotId The MicrosoftAppId of the bot sending the activity. + * @param toBotId The MicrosoftAppId of the bot receiving the activity. + * @param toUrl The URL of the bot receiving the activity. + * @param serviceUrl The callback Url for the skill host. + * @param conversationId A conversation D to use for the conversation with the + * skill. + * @param activity activity to forward. + * + * @return task with optional invokeResponse. + */ + @Override + public CompletableFuture> postActivity(String fromBotId, String toBotId, + URI toUrl, URI serviceUrl, String conversationId, Activity activity, Class type) { + + return getAppCredentials(fromBotId, toBotId).thenCompose(appCredentials -> { + if (appCredentials == null) { + return Async.completeExceptionally( + new Exception(String.format("Unable to get appCredentials to connect to the skill"))); + } + + // Get token for the skill call + return getToken(appCredentials).thenCompose(token -> { + // Clone the activity so we can modify it before sending without impacting the + // original Object. + Activity activityClone = Activity.clone(activity); + + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId(activityClone.getConversation().getId()); + conversationAccount.setName(activityClone.getConversation().getName()); + conversationAccount.setConversationType(activityClone.getConversation().getConversationType()); + conversationAccount.setAadObjectId(activityClone.getConversation().getAadObjectId()); + conversationAccount.setIsGroup(activityClone.getConversation().isGroup()); + for (String key : conversationAccount.getProperties().keySet()) { + activityClone.setProperties(key, conversationAccount.getProperties().get(key)); + } + conversationAccount.setRole(activityClone.getConversation().getRole()); + conversationAccount.setTenantId(activityClone.getConversation().getTenantId()); + + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setServiceUrl(activityClone.getServiceUrl()); + conversationReference.setActivityId(activityClone.getId()); + conversationReference.setChannelId(activityClone.getChannelId()); + conversationReference.setLocale(activityClone.getLocale()); + conversationReference.setConversation(conversationAccount); + + activityClone.setRelatesTo(conversationReference); + activityClone.getConversation().setId(conversationId); + activityClone.setServiceUrl(serviceUrl.toString()); + if (activityClone.getRecipient() == null) { + activityClone.setRecipient(new ChannelAccount()); + } + activityClone.getRecipient().setRole(RoleTypes.SKILL); + + return securePostActivity(toUrl, activityClone, token, type); + }); + }); + } + + private CompletableFuture getToken(AppCredentials appCredentials) { + // Get token for the skill call + if (appCredentials == MicrosoftAppCredentials.empty()) { + return CompletableFuture.completedFuture(null); + } else { + return appCredentials.getToken(); + } + } + + /** + * Post Activity to the bot using the bot's credentials. + * + * @param botId The MicrosoftAppId of the bot. + * @param botEndpoint The URL of the bot. + * @param activity Activity to post. + * @param type Type of . + * @param Type of expected TypedInvokeResponse. + * + * @return InvokeResponse. + */ + public CompletableFuture> postActivity(String botId, URI botEndpoint, + Activity activity, Class type) { + + // From BotId -> BotId + return getAppCredentials(botId, botId).thenCompose(appCredentials -> { + if (appCredentials == null) { + return Async.completeExceptionally( + new Exception(String.format("Unable to get appCredentials for the bot Id=%s", botId))); + } + + return getToken(appCredentials).thenCompose(token -> { + // post the activity to the url using the bot's credentials. + return securePostActivity(botEndpoint, activity, token, type); + }); + }); + } + + /** + * Logic to build an {@link AppCredentials} Object to be used to acquire tokens + * for this getHttpClient(). + * + * @param appId The application id. + * @param oAuthScope The optional OAuth scope. + * + * @return The app credentials to be used to acquire tokens. + */ + protected CompletableFuture buildCredentials(String appId, String oAuthScope) { + return getCredentialProvider().getAppPassword(appId).thenCompose(appPassword -> { + AppCredentials appCredentials = channelProvider != null && getChannelProvider().isGovernment() + ? new MicrosoftGovernmentAppCredentials(appId, appPassword, null, oAuthScope) + : new MicrosoftAppCredentials(appId, appPassword, null, oAuthScope); + return CompletableFuture.completedFuture(appCredentials); + }); + } + + private CompletableFuture> securePostActivity(URI toUrl, + Activity activity, String token, Class type) { + String jsonContent = ""; + try { + ObjectMapper mapper = new JacksonAdapter().serializer(); + jsonContent = mapper.writeValueAsString(activity); + } catch (JsonProcessingException e) { + return Async.completeExceptionally( + new RuntimeException("securePostActivity: Unable to serialize the Activity")); + } + + try { + RequestBody body = RequestBody.create(MediaType.parse("application/json; charset=utf-8"), jsonContent); + Request request = buildRequest(toUrl, body, token); + Response response = httpClient.newCall(request).execute(); + + T result = Serialization.getAs(response.body().string(), type); + TypedInvokeResponse returnValue = new TypedInvokeResponse(response.code(), result); + return CompletableFuture.completedFuture(returnValue); + } catch (IOException e) { + return Async.completeExceptionally(e); + } + } + + private Request buildRequest(URI url, RequestBody body, String token) { + + HttpUrl.Builder httpBuilder = HttpUrl.parse(url.toString()).newBuilder(); + + Request.Builder requestBuilder = new Request.Builder().url(httpBuilder.build()); + if (token != null) { + requestBuilder.addHeader("Authorization", String.format("Bearer %s", token)); + } + requestBuilder.post(body); + return requestBuilder.build(); + } + + /** + * Gets the application credentials. App Credentials are cached so as to ensure + * we are not refreshing token every time. + * + * @param appId The application identifier (AAD Id for the bot). + * @param oAuthScope The scope for the token, skills will use the Skill App Id. + * + * @return App credentials. + */ + private CompletableFuture getAppCredentials(String appId, String oAuthScope) { + if (StringUtils.isEmpty(appId)) { + return CompletableFuture.completedFuture(MicrosoftAppCredentials.empty()); + } + + // If the credentials are in the cache, retrieve them from there + String cacheKey = String.format("%s%s", appId, oAuthScope); + AppCredentials appCredentials = null; + appCredentials = appCredentialMapCache.get(cacheKey); + if (appCredentials != null) { + return CompletableFuture.completedFuture(appCredentials); + } + + // Credentials not found in cache, build them + return buildCredentials(appId, String.format("%s/.default", oAuthScope)).thenCompose(credentials -> { + // Cache the credentials for later use + appCredentialMapCache.put(cacheKey, credentials); + return CompletableFuture.completedFuture(credentials); + }); + } + + /** + * Gets the Cache for appCredentials to speed up token acquisition (a token is + * not requested unless is expired). AppCredentials are cached using appId + + * scope (this last parameter is only used if the app credentials are used to + * call a skill). + * + * @return the AppCredentialMapCache value as a static + * ConcurrentDictionary. + */ + protected static Map getAppCredentialMapCache() { + return appCredentialMapCache; + } + + /** + * Gets the channel provider for this adapter. + * + * @return the ChannelProvider value as a getChannelProvider(). + */ + protected ChannelProvider getChannelProvider() { + return this.channelProvider; + } + + /** + * Gets the credential provider for this adapter. + * + * @return the CredentialProvider value as a getCredentialProvider(). + */ + protected CredentialProvider getCredentialProvider() { + return this.credentialProvider; + } + + /** + * Gets the HttpClient for this adapter. + * + * @return the OkhttpClient value as a getHttpClient(). + */ + public OkHttpClient getHttpClient() { + return httpClient; + } +} diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java index cb0ff880d..ecf3fdb2f 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/ClasspathPropertiesConfiguration.java @@ -53,4 +53,20 @@ public String getProperty(String key) { public Properties getProperties() { return this.properties; } + + /** + * Returns an array of values from an entry that is comma delimited. + * @param key The property name. + * @return The property values as a String array. + */ + @Override + public String[] getProperties(String key) { + String baseProperty = properties.getProperty(key); + if (baseProperty != null) { + String[] splitProperties = baseProperty.split(","); + return splitProperties; + } else { + return null; + } + } } diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java index dda35a4a8..7a8d67fde 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/Configuration.java @@ -23,4 +23,11 @@ public interface Configuration { * @return The Properties in the Configuration. */ Properties getProperties(); + + /** + * Returns an Array of Properties that are in the Configuration. + * @param key The property name. + * @return The property values. + */ + String[] getProperties(String key); } diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/SkillHttpClient.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/SkillHttpClient.java new file mode 100644 index 000000000..2d42006f5 --- /dev/null +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/SkillHttpClient.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.integration; + +import java.net.URI; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.TypedInvokeResponse; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; + +/** + * A {@link BotFrameworkHttpClient} specialized for Skills that encapsulates + * Conversation ID generation. + */ +public class SkillHttpClient extends BotFrameworkHttpClient { + + private final SkillConversationIdFactoryBase conversationIdFactory; + + /** + * Initializes a new instance of the {@link SkillHttpClient} class. + * + * @param credentialProvider An instance of {@link CredentialProvider}. + * @param conversationIdFactory An instance of a class derived from + * {@link SkillConversationIdFactoryBase}. + * @param channelProvider An instance of {@link ChannelProvider}. + */ + public SkillHttpClient(CredentialProvider credentialProvider, SkillConversationIdFactoryBase conversationIdFactory, + ChannelProvider channelProvider) { + super(credentialProvider, channelProvider); + this.conversationIdFactory = conversationIdFactory; + } + + /** + * Uses the SkillConversationIdFactory to create or retrieve a Skill + * Conversation Id, and sends the activity. + * + * @param originatingAudience The oauth audience scope, used during token + * retrieval. (Either + * https://api.getbotframework().com or bot app id.) + * @param fromBotId The MicrosoftAppId of the bot sending the + * activity. + * @param toSkill The skill to create the conversation Id for. + * @param callbackUrl The callback Url for the skill host. + * @param activity The activity to send. + * @param type Type of T required due to type erasure of generics + * in Java. + * @param Type of expected TypedInvokeResponse. + * + * @return task with invokeResponse. + */ + public CompletableFuture> postActivity(String originatingAudience, + String fromBotId, BotFrameworkSkill toSkill, URI callbackUrl, Activity activity, Class type) { + return getSkillConversationId(originatingAudience, fromBotId, toSkill, activity) + .thenCompose(skillConversationId -> { + return postActivity(fromBotId, toSkill.getAppId(), toSkill.getSkillEndpoint(), callbackUrl, + skillConversationId, activity, type); + }); + + } + + private CompletableFuture getSkillConversationId(String originatingAudience, String fromBotId, + BotFrameworkSkill toSkill, Activity activity) { + try { + SkillConversationIdFactoryOptions options = new SkillConversationIdFactoryOptions(); + options.setFromBotOAuthScope(originatingAudience); + options.setFromBotId(fromBotId); + options.setActivity(activity); + options.setBotFrameworkSkill(toSkill); + return conversationIdFactory.createSkillConversationId(options); + } catch (Exception ex) { + // Attempt to create the ID using deprecated method. + return conversationIdFactory.createSkillConversationId(activity.getConversationReference()); + } + } + + /** + * Forwards an activity to a skill (bot). + * + * @param fromBotId The MicrosoftAppId of the bot sending the activity. + * @param toSkill An instance of {@link BotFrameworkSkill} . + * @param callbackUrl The callback Uri. + * @param activity activity to forward. + * @param type type of T + * @param Type of expected TypedInvokeResponse. + * + * @return task with optional invokeResponse of type T. + */ + public CompletableFuture> postActivity(String fromBotId, + BotFrameworkSkill toSkill, URI callbackUrl, Activity activity, Class type) { + String originatingAudience = getChannelProvider() != null && getChannelProvider().isGovernment() + ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + : AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + return postActivity(originatingAudience, fromBotId, toSkill, callbackUrl, activity, type); + } +} diff --git a/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/BotDependencyConfiguration.java b/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/BotDependencyConfiguration.java index adccd057e..025a7de73 100644 --- a/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/BotDependencyConfiguration.java +++ b/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/BotDependencyConfiguration.java @@ -9,6 +9,7 @@ import com.microsoft.bot.builder.UserState; import com.microsoft.bot.builder.inspection.InspectionState; import com.microsoft.bot.connector.ExecutorFactory; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; import com.microsoft.bot.connector.authentication.ChannelProvider; import com.microsoft.bot.connector.authentication.CredentialProvider; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; @@ -64,6 +65,20 @@ public Configuration getConfiguration() { return new ClasspathPropertiesConfiguration(); } + + /** + * Returns the AuthenticationConfiguration for the application. + * + * By default, it uses the {@link AuthenticationConfiguration} class. + * Default scope of Singleton. + * @param configuration The Configuration object to read from. + * @return An AuthenticationConfiguration object. + */ + @Bean + public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { + return new AuthenticationConfiguration(); + } + /** * Returns the CredentialProvider for the application. * diff --git a/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/ChannelServiceController.java b/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/ChannelServiceController.java new file mode 100644 index 000000000..4ee1fdb1c --- /dev/null +++ b/libraries/bot-integration-spring/src/main/java/com/microsoft/bot/integration/spring/ChannelServiceController.java @@ -0,0 +1,546 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.integration.spring; + +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; + +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.connector.authentication.AuthenticationException; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.AttachmentData; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationParameters; +import com.microsoft.bot.schema.ConversationResourceResponse; +import com.microsoft.bot.schema.ConversationsResult; +import com.microsoft.bot.schema.PagedMembersResult; +import com.microsoft.bot.schema.ResourceResponse; +import com.microsoft.bot.schema.Transcript; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; + +/** + * A super.class for a skill controller. + */ +// Note: this class instanceof marked as abstract to prevent the ASP runtime from registering it as a controller. +public abstract class ChannelServiceController { + + /** + * The slf4j Logger to use. Note that slf4j is configured by providing Log4j + * dependencies in the POM, and corresponding Log4j configuration in the + * 'resources' folder. + */ + private Logger logger = LoggerFactory.getLogger(BotController.class); + + private final ChannelServiceHandler handler; + + /** + * Initializes a new instance of the {@link ChannelServiceController} + * class. + * + * @param handler A {@link ChannelServiceHandler} that will handle + * the incoming request. + */ + protected ChannelServiceController(ChannelServiceHandler handler) { + this.handler = handler; + } + + /** + * SendToConversation. + * + * @param conversationId Conversation Id. + * @param activity Activity to send. + * @param authHeader Authentication header. + * + * @return A ResourceResponse. + */ + @PostMapping("v3/conversations/{conversationId}/activities") + public CompletableFuture> sendToConversation( + @PathVariable String conversationId, + @RequestBody Activity activity, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + + return handler.handleSendToConversation(authHeader, conversationId, activity) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * ReplyToActivity. + * + * @param conversationId Conversation Id. + * @param activityId activityId the reply is to (OPTONAL). + * @param activity Activity to send. + * @param authHeader Authentication header. + * + * @return A ResourceResponse. + */ + @PostMapping("v3/conversations/{conversationId}/activities/{activityId}") + public CompletableFuture> replyToActivity( + @PathVariable String conversationId, + @PathVariable String activityId, + @RequestBody Activity activity, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleReplyToActivity(authHeader, conversationId, activityId, activity) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * UpdateActivity. + * + * @param conversationId Conversation Id. + * @param activityId activityId to update. + * @param activity replacement Activity. + * @param authHeader Authentication header. + * + * @return A ResourceResponse. + */ + @PutMapping("v3/conversations/{conversationId}/activities/{activityId}") + public CompletableFuture> updateActivity( + @PathVariable String conversationId, + @PathVariable String activityId, + @RequestBody Activity activity, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleUpdateActivity(authHeader, conversationId, activityId, activity) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * DeleteActivity. + * + * @param conversationId Conversation Id. + * @param activityId activityId to delete. + * @param authHeader Authentication header. + * + * @return A void result if successful. + */ + @DeleteMapping("v3/conversations/{conversationId}/activities/{activityId}") + public CompletableFuture> deleteActivity( + @PathVariable String conversationId, + @PathVariable String activityId, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleDeleteActivity(authHeader, conversationId, activityId) + .handle((result, exception) -> { + if (exception == null) { + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * GetActivityMembers. + * + * Markdown=Content\Methods\GetActivityMembers.getmd(). + * + * @param conversationId Conversation Id. + * @param activityId Activity Id. + * @param authHeader Authentication header. + * + * @return A list of ChannelAccount. + */ + @GetMapping("v3/conversations/{conversationId}/activities/{activityId}/members") + public CompletableFuture>> getActivityMembers( + @PathVariable String conversationId, + @PathVariable String activityId, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleGetActivityMembers(authHeader, conversationId, activityId) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity>( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * CreateConversation. + * + * @param parameters Parameters to create the conversation from. + * @param authHeader Authentication header. + * + * @return A ConversationResourceResponse. + */ + @PostMapping("v3/conversations") + public CompletableFuture> createConversation( + @RequestBody ConversationParameters parameters, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleCreateConversation(authHeader, parameters) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * GetConversations. + * + * @param conversationId the conversation id to get conversations for. + * @param continuationToken skip or continuation token. + * @param authHeader Authentication header. + * + * @return A ConversationsResult. + */ + @GetMapping("v3/conversations") + public CompletableFuture> getConversations( + @RequestParam String conversationId, + @RequestParam String continuationToken, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleGetConversations(authHeader, conversationId, continuationToken) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * GetConversationMembers. + * + * @param conversationId Conversation Id. + * @param authHeader Authentication header. + * + * @return A List of ChannelAccount. + */ + @GetMapping("v3/conversations/{conversationId}/members") + public CompletableFuture>> getConversationMembers( + @PathVariable String conversationId, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleGetConversationMembers(authHeader, conversationId) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity>( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * GetConversationPagedMembers. + * + * @param conversationId Conversation Id. + * @param pageSize Suggested page size. + * @param continuationToken Continuation Token. + * @param authHeader Authentication header. + * + * @return A PagedMembersResult. + */ + @GetMapping("v3/conversations/{conversationId}/pagedmembers") + public CompletableFuture> getConversationPagedMembers( + @PathVariable String conversationId, + @RequestParam(name = "pageSize", defaultValue = "-1") int pageSize, + @RequestParam(name = "continuationToken") String continuationToken, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleGetConversationPagedMembers(authHeader, conversationId, pageSize, continuationToken) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * DeleteConversationMember. + * + * @param conversationId Conversation Id. + * @param memberId D of the member to delete from this + * conversation. + * @param authHeader Authentication header. + * + * @return A void result. + */ + @DeleteMapping("v3/conversations/{conversationId}/members/{memberId}") + public CompletableFuture> deleteConversationMember( + @PathVariable String conversationId, + @PathVariable String memberId, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleDeleteConversationMember(authHeader, conversationId, memberId) + .handle((result, exception) -> { + if (exception == null) { + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * SendConversationHistory. + * + * @param conversationId Conversation Id. + * @param history Historic activities. + * @param authHeader Authentication header. + * + * @return A ResourceResponse. + */ + @PostMapping("v3/conversations/{conversationId}/activities/history") + public CompletableFuture> sendConversationHistory( + @PathVariable String conversationId, + @RequestBody Transcript history, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleSendConversationHistory(authHeader, conversationId, history) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } + + /** + * UploadAttachment. + * + * @param conversationId Conversation Id. + * @param attachmentUpload Attachment data. + * @param authHeader Authentication header. + * + * @return A ResourceResponse. + */ + @PostMapping("v3/conversations/{conversationId}/attachments") + public CompletableFuture> uploadAttachment( + @PathVariable String conversationId, + @RequestBody AttachmentData attachmentUpload, + @RequestHeader(value = "Authorization", defaultValue = "") String authHeader + ) { + return handler.handleUploadAttachment(authHeader, conversationId, attachmentUpload) + .handle((result, exception) -> { + if (exception == null) { + if (result != null) { + return new ResponseEntity( + result, + HttpStatus.OK + ); + } + return new ResponseEntity<>(HttpStatus.ACCEPTED); + } + + logger.error("Exception handling message", exception); + + if (exception instanceof CompletionException) { + if (exception.getCause() instanceof AuthenticationException) { + return new ResponseEntity<>(HttpStatus.UNAUTHORIZED); + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + } else { + return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR); + } + }); + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/EndOfConversationCodes.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/EndOfConversationCodes.java index 2b4d3bb81..43162b2d7 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/EndOfConversationCodes.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/EndOfConversationCodes.java @@ -38,7 +38,17 @@ public enum EndOfConversationCodes { /** * Enum value channelFailed. */ - CHANNEL_FAILED("channelFailed"); + CHANNEL_FAILED("channelFailed"), + + /** + * Enum value skillError. + */ + SKILL_ERROR("skillError"), + + /** + * Enum value channelFailed. + */ + ROOT_SKILL_ERROR("rootSkillError"); /** * The actual serialized value for a EndOfConversationCodes instance. @@ -47,7 +57,7 @@ public enum EndOfConversationCodes { /** * Creates a ActionTypes enum from a string. - * + * * @param withValue The string value. Should be a valid enum value. * @throws IllegalArgumentException If the string doesn't match a valid value. */ diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/RoleTypes.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/RoleTypes.java index e5683adba..0e38b5b29 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/RoleTypes.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/RoleTypes.java @@ -18,7 +18,12 @@ public enum RoleTypes { /** * Enum value bot. */ - BOT("bot"); + BOT("bot"), + + /** + * Enum value skill. + */ + SKILL("skill"); /** * The actual serialized value for a RoleTypes instance. @@ -27,7 +32,7 @@ public enum RoleTypes { /** * Creates a ActionTypes enum from a string. - * + * * @param withValue The string value. Should be a valid enum value. * @throws IllegalArgumentException If the string doesn't match a valid value. */ diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java index d8455fcad..aa1ff6b80 100644 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java @@ -74,7 +74,7 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next // Translate messages sent to the user to user language if (shouldTranslate) { ArrayList> tasks = new ArrayList>(); - for (Activity activity : activities.stream().filter(a -> a.getType() == ActivityTypes.MESSAGE).collect(Collectors.toList())) { + for (Activity activity : activities.stream().filter(a -> a.getType().equals(ActivityTypes.MESSAGE)).collect(Collectors.toList())) { tasks.add(this.translateMessageActivity(activity, userLanguage)); } @@ -92,7 +92,7 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next Boolean shouldTranslate = !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); // Translate messages sent to the user to user language - if (activity.getType() == ActivityTypes.MESSAGE) { + if (activity.getType().equals(ActivityTypes.MESSAGE)) { if (shouldTranslate) { this.translateMessageActivity(activity, userLanguage); } @@ -107,7 +107,7 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next } private CompletableFuture translateMessageActivity(Activity activity, String targetLocale) { - if (activity.getType() == ActivityTypes.MESSAGE) { + if (activity.getType().equals(ActivityTypes.MESSAGE)) { return this.translator.translate(activity.getText(), targetLocale).thenAccept(text -> { activity.setText(text); }); diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/LICENSE b/samples/80.skills-simple-bot-to-bot/DialogRootBot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/README.md b/samples/80.skills-simple-bot-to-bot/DialogRootBot/README.md new file mode 100644 index 000000000..66752bfb8 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/README.md @@ -0,0 +1,3 @@ +# SimpleRootBot + +See [80.skills-simple-bot-to-bot](../Readme.md) for details on how to configure and run this sample. diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-new-rg.json b/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-preexisting-rg.json b/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml new file mode 100644 index 000000000..5bc83ef58 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml @@ -0,0 +1,238 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + simpleRootBot + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Simple Root Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.simplerootbot.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.simplerootbot.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java new file mode 100644 index 000000000..55cb47636 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.simplerootbot; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.simplerootbot.authentication.AllowedSkillsClaimsValidator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + SkillsConfiguration skillsConfig, + SkillHttpClient skillClient, + Configuration configuration + ) { + return new RootBot(conversationState, skillsConfig, skillClient, configuration); + } + + @Override + public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { + AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); + authenticationConfiguration.setClaimsValidator( + new AllowedSkillsClaimsValidator(getSkillsConfiguration(configuration))); + return authenticationConfiguration; + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new SkillAdapterWithErrorHandler( + configuration, + getConversationState(new MemoryStorage()), + getSkillHttpClient( + getCredentialProvider(configuration), + getSkillConversationIdFactoryBase(), + getChannelProvider(configuration)), + getSkillsConfiguration(configuration)); + } + + @Bean + public SkillsConfiguration getSkillsConfiguration(Configuration configuration) { + return new SkillsConfiguration(configuration); + } + + @Bean + public SkillHttpClient getSkillHttpClient( + CredentialProvider credentialProvider, + SkillConversationIdFactoryBase conversationIdFactory, + ChannelProvider channelProvider + ) { + return new SkillHttpClient(credentialProvider, conversationIdFactory, channelProvider); + } + + @Bean + public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() { + return new SkillConversationIdFactory(); + } + + @Bean public ChannelServiceHandler getChannelServiceHandler( + BotAdapter botAdapter, + Bot bot, + SkillConversationIdFactoryBase conversationIdFactory, + CredentialProvider credentialProvider, + AuthenticationConfiguration authConfig, + ChannelProvider channelProvider + ) { + return new SkillHandler( + botAdapter, + bot, + conversationIdFactory, + credentialProvider, + authConfig, + channelProvider); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java new file mode 100644 index 000000000..d85a3dddc --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.simplerootbot; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TypedInvokeResponse; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; + +import org.apache.commons.lang3.StringUtils; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + * + *

+ * This is where application specific logic for interacting with the users would be added. For this + * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link + * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. + *

+ */ +public class RootBot extends ActivityHandler { + + public static final String ActiveSkillPropertyName = "com.microsoft.bot.sample.simplerootbot.ActiveSkillProperty"; + private StatePropertyAccessor activeSkillProperty; + private String botId; + private ConversationState conversationState; + private SkillHttpClient skillClient; + private SkillsConfiguration skillsConfig; + private BotFrameworkSkill targetSkill; + + public RootBot( + ConversationState conversationState, + SkillsConfiguration skillsConfig, + SkillHttpClient skillClient, + Configuration configuration + ) { + if (conversationState == null) { + throw new IllegalArgumentException("conversationState cannot be null."); + } + + if (skillsConfig == null) { + throw new IllegalArgumentException("skillsConfig cannot be null."); + } + + if (skillClient == null) { + throw new IllegalArgumentException("skillsConfig cannot be null."); + } + + if (configuration == null) { + throw new IllegalArgumentException("configuration cannot be null."); + } + + + this.conversationState = conversationState; + this.skillsConfig = skillsConfig; + this.skillClient = skillClient; + + botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID); + + if (StringUtils.isEmpty(botId)) { + throw new IllegalArgumentException(String.format("%s instanceof not set in configuration", + MicrosoftAppCredentials.MICROSOFTAPPID)); + } + + // We use a single skill in this example. + String targetSkillId = "EchoSkillBot"; + if (!skillsConfig.getSkills().containsKey(targetSkillId)) { + throw new IllegalArgumentException( + String.format("Skill with D \"%s\" not found in configuration", targetSkillId) + ); + } else { + targetSkill = (BotFrameworkSkill) skillsConfig.getSkills().get(targetSkillId); + } + + // Create state property to track the active skill + activeSkillProperty = conversationState.createProperty(ActiveSkillPropertyName); + } + + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + // Forward all activities except EndOfConversation to the skill. + if (!turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)) { + // Try to get the active skill + BotFrameworkSkill activeSkill = activeSkillProperty.get(turnContext).join(); + if (activeSkill != null) { + // Send the activity to the skill + sendToSkill(turnContext, activeSkill).join(); + return CompletableFuture.completedFuture(null); + } + } + + super.onTurn(turnContext); + + // Save any state changes that might have occured during the turn. + return conversationState.saveChanges(turnContext, false); + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + if (turnContext.getActivity().getText().contains("skill")) { + return turnContext.sendActivity(MessageFactory.text("Got it, connecting you to the skill...")) + .thenCompose(result -> { + activeSkillProperty.set(turnContext, targetSkill); + // Send the activity to the skill + return sendToSkill(turnContext, targetSkill); + }); + } + + // just respond + return turnContext.sendActivity( + MessageFactory.text("Me no nothin'. Say \"skill\" and I'll patch you through")) + .thenCompose(result -> conversationState.saveChanges(turnContext, true)); + } + + @Override + protected CompletableFuture onEndOfConversationActivity(TurnContext turnContext) { + // forget skill invocation + return activeSkillProperty.delete(turnContext).thenAccept(result -> { + // Show status message, text and value returned by the skill + String eocActivityMessage = String.format("Received %s.\n\nCode: %s", + ActivityTypes.END_OF_CONVERSATION, + turnContext.getActivity().getCode()); + + if (!StringUtils.isEmpty(turnContext.getActivity().getText())) { + eocActivityMessage += String.format("\n\nText: %s", turnContext.getActivity().getText()); + } + + if (turnContext.getActivity() != null && turnContext.getActivity().getValue() != null) { + eocActivityMessage += String.format("\n\nValue: %s", turnContext.getActivity().getValue()); + } + + turnContext.sendActivity(MessageFactory.text(eocActivityMessage)).thenCompose(sendResult ->{ + // We are back at the root + return turnContext.sendActivity( + MessageFactory.text("Back in the root bot. Say \"skill\" and I'll patch you through")) + .thenCompose(secondSendResult-> conversationState.saveChanges(turnContext)); + }); + }); + } + + + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + return membersAdded.stream() + .filter( + member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) + ).map(channel -> turnContext.sendActivity(MessageFactory.text("Hello and welcome!"))) + .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); + } + + private CompletableFuture sendToSkill(TurnContext turnContext, BotFrameworkSkill targetSkill) { + // NOTE: Always SaveChanges() before calling a skill so that any activity generated by the skill + // will have access to current accurate state. + return conversationState.saveChanges(turnContext, true) + .thenAccept(result -> { + // route the activity to the skill + skillClient.postActivity(botId, + targetSkill, + skillsConfig.getSkillHostEndpoint(), + turnContext.getActivity(), + Object.class) + .thenApply(response -> { + // Check response status + if (!(response.getStatus() >= 200 && response.getStatus() <= 299)) { + throw new RuntimeException( + String.format( + "Error invoking the skill id: \"%s\" at \"%s\" (status instanceof %s). \r\n %s", + targetSkill.getId(), + targetSkill.getSkillEndpoint(), + response.getStatus(), + response.getBody())); + } + return CompletableFuture.completedFuture(null); + }); + }); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillAdapterWithErrorHandler.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillAdapterWithErrorHandler.java new file mode 100644 index 000000000..5e751193f --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillAdapterWithErrorHandler.java @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.sample.simplerootbot; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.OnTurnErrorHandler; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.EndOfConversationCodes; +import com.microsoft.bot.schema.InputHints; + +public class SkillAdapterWithErrorHandler extends BotFrameworkHttpAdapter { + + private ConversationState conversationState = null; + private SkillHttpClient skillHttpClient = null; + private SkillsConfiguration skillsConfiguration = null; + private Configuration configuration = null; + + public SkillAdapterWithErrorHandler( + Configuration configuration, + ConversationState conversationState, + SkillHttpClient skillHttpClient, + SkillsConfiguration skillsConfiguration + ) { + super(configuration); + this.configuration = configuration; + this.conversationState = conversationState; + this.skillHttpClient = skillHttpClient; + this.skillsConfiguration = skillsConfiguration; + setOnTurnError(new SkillAdapterErrorHandler()); + } + + private class SkillAdapterErrorHandler implements OnTurnErrorHandler { + + @Override + public CompletableFuture invoke(TurnContext turnContext, Throwable exception) { + return sendErrorMessage(turnContext, exception).thenAccept(result -> { + endSkillConversation(turnContext); + }).thenAccept(endResult -> { + clearConversationState(turnContext); + }); + } + + private CompletableFuture sendErrorMessage(TurnContext turnContext, Throwable exception) { + try { + // Send a message to the user. + String errorMessageText = "The bot encountered an error or bug."; + Activity errorMessage = + MessageFactory.text(errorMessageText, errorMessageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(errorMessage).thenAccept(result -> { + String secondLineMessageText = "To continue to run this bot, please fix the bot source code."; + Activity secondErrorMessage = + MessageFactory.text(secondLineMessageText, secondLineMessageText, InputHints.EXPECTING_INPUT); + turnContext.sendActivity(secondErrorMessage) + .thenApply( + sendResult -> { + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + // Note: we return the entire exception in the value property to help the + // developer; + // this should not be done in production. + return TurnContext.traceActivity( + turnContext, + String.format("OnTurnError Trace %s", exception.toString()) + ); + } + ); + }).thenApply(finalResult -> null); + + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + private CompletableFuture endSkillConversation(TurnContext turnContext) { + if (skillHttpClient == null || skillsConfiguration == null) { + return CompletableFuture.completedFuture(null); + } + + // Inform the active skill that the conversation instanceof ended so that it has + // a chance to clean up. + // Note: ActiveSkillPropertyName instanceof set by the RooBot while messages are + // being + StatePropertyAccessor skillAccessor = + conversationState.createProperty(RootBot.ActiveSkillPropertyName); + // forwarded to a Skill. + return skillAccessor.get(turnContext, () -> null).thenApply(activeSkill -> { + if (activeSkill != null) { + String botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID); + + Activity endOfConversation = Activity.createEndOfConversationActivity(); + endOfConversation.setCode(EndOfConversationCodes.ROOT_SKILL_ERROR); + endOfConversation + .applyConversationReference(turnContext.getActivity().getConversationReference(), true); + + return conversationState.saveChanges(turnContext, true).thenCompose(saveResult -> { + return skillHttpClient.postActivity( + botId, + activeSkill, + skillsConfiguration.getSkillHostEndpoint(), + endOfConversation, + Object.class + ); + + }); + } + return CompletableFuture.completedFuture(null); + }).thenApply(result -> null); + } + + private CompletableFuture clearConversationState(TurnContext turnContext) { + try { + return conversationState.delete(turnContext); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java new file mode 100644 index 000000000..1aa0fd42a --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.simplerootbot; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.builder.skills.SkillConversationReference; + +/** + * A {@link SkillConversationIdFactory} that uses an in memory + * {@link Map{TKey,TValue}} to store and retrieve {@link ConversationReference} + * instances. + */ +public class SkillConversationIdFactory extends SkillConversationIdFactoryBase { + + private final Map _conversationRefs = + new HashMap(); + + @Override + public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { + SkillConversationReference skillConversationReference = new SkillConversationReference(); + skillConversationReference.setConversationReference(options.getActivity().getConversationReference()); + skillConversationReference.setOAuthScope(options.getFromBotOAuthScope()); + String key = String.format( + "%s-%s-%s-%s-skillconvo", + options.getFromBotId(), + options.getBotFrameworkSkill().getAppId(), + skillConversationReference.getConversationReference().getConversation().getId(), + skillConversationReference.getConversationReference().getChannelId() + ); + _conversationRefs.put(key, skillConversationReference); + return CompletableFuture.completedFuture(key); + } + + @Override + public CompletableFuture getSkillConversationReference(String skillConversationId) { + SkillConversationReference conversationReference = _conversationRefs.get(skillConversationId); + return CompletableFuture.completedFuture(conversationReference); + } + + @Override + public CompletableFuture deleteConversationReference(String skillConversationId) { + _conversationRefs.remove(skillConversationId); + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java new file mode 100644 index 000000000..b73e64762 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.simplerootbot; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.integration.Configuration; + +import org.apache.commons.lang3.StringUtils; + +/** + * A helper class that loads Skills information from configuration. + */ +public class SkillsConfiguration { + + private URI skillHostEndpoint; + + private Map skills = new HashMap(); + + public SkillsConfiguration(Configuration configuration) { + + boolean noMoreEntries = false; + int indexCount = 0; + while (!noMoreEntries) { + String botID = configuration.getProperty(String.format("BotFrameworkSkills[%d].Id", indexCount)); + String botAppId = configuration.getProperty(String.format("BotFrameworkSkills[%d].AppId", indexCount)); + String skillEndPoint = + configuration.getProperty(String.format("BotFrameworkSkills[%d].SkillEndpoint", indexCount)); + if ( + StringUtils.isNotBlank(botID) && StringUtils.isNotBlank(botAppId) + && StringUtils.isNotBlank(skillEndPoint) + ) { + BotFrameworkSkill newSkill = new BotFrameworkSkill(); + newSkill.setId(botID); + newSkill.setAppId(botAppId); + try { + newSkill.setSkillEndpoint(new URI(skillEndPoint)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + skills.put(botID, newSkill); + indexCount++; + } else { + noMoreEntries = true; + } + } + + String skillHost = configuration.getProperty("SkillhostEndpoint"); + if (!StringUtils.isEmpty(skillHost)) { + try { + skillHostEndpoint = new URI(skillHost); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + } + + /** + * @return the SkillHostEndpoint value as a Uri. + */ + public URI getSkillHostEndpoint() { + return this.skillHostEndpoint; + } + + /** + * @return the Skills value as a Dictionary. + */ + public Map getSkills() { + return this.skills; + } + +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/authentication/AllowedSkillsClaimsValidator.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/authentication/AllowedSkillsClaimsValidator.java new file mode 100644 index 000000000..7f12718c6 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/authentication/AllowedSkillsClaimsValidator.java @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.simplerootbot.authentication; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsValidator; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.sample.simplerootbot.SkillsConfiguration; + +/** + * Sample claims validator that loads an allowed list from configuration if + * presentand checks that requests are coming from allowed parent bots. + */ +public class AllowedSkillsClaimsValidator extends ClaimsValidator { + + private final List allowedSkills; + + public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) { + if (skillsConfig == null) { + throw new IllegalArgumentException("config cannot be null."); + } + + // Load the appIds for the configured skills (we will only allow responses from skills we have configured). + allowedSkills = new ArrayList(); + for (Map.Entry configuration : skillsConfig.getSkills().entrySet()) { + allowedSkills.add(configuration.getValue().getAppId()); + } + } + + @Override + public CompletableFuture validateClaims(Map claims) { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request instanceof in the list of callers + // configured for this bot. + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedSkills.contains(appId)) { + return Async.completeExceptionally( + new RuntimeException( + String.format("Received a request from an application with an appID of \"%s\". " + + "To enable requests from this skill, add the skill to your configuration file.", appId) + ) + ); + } + } + + return CompletableFuture.completedFuture(null); + } +} + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java new file mode 100644 index 000000000..4b3e4e4bd --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java @@ -0,0 +1,16 @@ +package com.microsoft.bot.sample.simplerootbot.controller; + +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.integration.spring.ChannelServiceController; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = {"/api/skills"}) +public class SkillController extends ChannelServiceController { + + public SkillController(ChannelServiceHandler handler) { + super(handler); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/application.properties b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/application.properties new file mode 100644 index 000000000..611908690 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/application.properties @@ -0,0 +1,8 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 +SkillhostEndpoint=http://localhost:3978/api/skills/ +#replicate these three entries, incrementing the index value [0] for each successive Skill that is added. +BotFrameworkSkills[0].Id=EchoSkillBot +BotFrameworkSkills[0].AppId= "Add the App ID for the skill here" +BotFrameworkSkills[0].SkillEndpoint=http://localhost:39783/api/messages diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/log4j2.json b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/META-INF/MANIFEST.MF b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/WEB-INF/web.xml b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/index.html b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/test/java/com/microsoft/bot/sample/simplerootbot/ApplicationTest.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/test/java/com/microsoft/bot/sample/simplerootbot/ApplicationTest.java new file mode 100644 index 000000000..a8cead7bf --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/test/java/com/microsoft/bot/sample/simplerootbot/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.simplerootbot; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/LICENSE b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/README.md b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/README.md new file mode 100644 index 000000000..b2c9ea098 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/README.md @@ -0,0 +1,3 @@ +# EchoSkillBot + +See [80.skills-simple-bot-to-bot](../Readme.md) for details on how to configure and run this sample. diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-new-rg.json b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-preexisting-rg.json b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml new file mode 100644 index 000000000..16077c086 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml @@ -0,0 +1,238 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + echoSkillbot + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Echo Skill Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.echoskillbot.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.echoskillbot.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java new file mode 100644 index 000000000..52995f61c --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.echoskillbot; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.echoskillbot.authentication.AllowedCallersClaimsValidator; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should override + * methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this + * method with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new EchoBot(); + } + + @Override + public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { + AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); + authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration)); + return authenticationConfiguration; + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new SkillAdapterWithErrorHandler(configuration, getAuthenticationConfiguration(configuration)); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/EchoBot.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/EchoBot.java new file mode 100644 index 000000000..e3204236a --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/EchoBot.java @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.echoskillbot; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.EndOfConversationCodes; +import com.microsoft.bot.schema.InputHints; + +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + * + *

+ * This is where application specific logic for interacting with the users would + * be added. For this sample, the {@link #onMessageActivity(TurnContext)} echos + * the text back to the user. The {@link #onMembersAdded(List, TurnContext)} + * will send a greeting to new conversation participants. + *

+ */ +public class EchoBot extends ActivityHandler { + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + if ( + turnContext.getActivity().getText().contains("end") || turnContext.getActivity().getText().contains("stop") + ) { + String messageText = "ending conversation from the skill..."; + return turnContext.sendActivity(MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT)) + .thenApply(result -> { + Activity endOfConversation = Activity.createEndOfConversationActivity(); + endOfConversation.setCode(EndOfConversationCodes.COMPLETED_SUCCESSFULLY); + return turnContext.sendActivity(endOfConversation); + }) + .thenApply(finalResult -> null); + } else { + String messageText = String.format("Echo: %s", turnContext.getActivity().getText()); + return turnContext.sendActivity(MessageFactory.text(messageText, messageText, InputHints.IGNORING_INPUT)) + .thenApply(result -> { + String nextMessageText = + "Say \"end\" or \"stop\" and I'll end the conversation and back to the parent."; + return turnContext.sendActivity( + MessageFactory.text(nextMessageText, nextMessageText, InputHints.EXPECTING_INPUT) + ); + }) + .thenApply(result -> null); + } + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/SkillAdapterWithErrorHandler.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/SkillAdapterWithErrorHandler.java new file mode 100644 index 000000000..a392cc52b --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/SkillAdapterWithErrorHandler.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.sample.echoskillbot; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.OnTurnErrorHandler; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.EndOfConversationCodes; +import com.microsoft.bot.schema.InputHints; + +public class SkillAdapterWithErrorHandler extends BotFrameworkHttpAdapter { + + public SkillAdapterWithErrorHandler( + Configuration configuration, + AuthenticationConfiguration authenticationConfiguration + ) { + super(configuration, authenticationConfiguration); + setOnTurnError(new SkillAdapterErrorHandler()); + } + + private class SkillAdapterErrorHandler implements OnTurnErrorHandler { + + @Override + public CompletableFuture invoke(TurnContext turnContext, Throwable exception) { + return sendErrorMessage(turnContext, exception).thenAccept(result -> { + sendEoCToParent(turnContext, exception); + }); + } + + private CompletableFuture sendErrorMessage(TurnContext turnContext, Throwable exception) { + try { + // Send a message to the user. + String errorMessageText = "The skill encountered an error or bug."; + Activity errorMessage = + MessageFactory.text(errorMessageText, errorMessageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(errorMessage).thenAccept(result -> { + String secondLineMessageText = "To continue to run this bot, please fix the bot source code."; + Activity secondErrorMessage = + MessageFactory.text(secondLineMessageText, secondLineMessageText, InputHints.EXPECTING_INPUT); + turnContext.sendActivity(secondErrorMessage) + .thenApply( + sendResult -> { + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + // Note: we return the entire exception in the value property to help the + // developer; + // this should not be done in production. + return TurnContext.traceActivity( + turnContext, + String.format("OnTurnError Trace %s", exception.toString()) + ); + + } + ); + }); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + private CompletableFuture sendEoCToParent(TurnContext turnContext, Throwable exception) { + try { + // Send an EndOfConversation activity to the skill caller with the error to end + // the conversation, + // and let the caller decide what to do. + Activity endOfConversation = Activity.createEndOfConversationActivity(); + endOfConversation.setCode(EndOfConversationCodes.SKILL_ERROR); + endOfConversation.setText(exception.getMessage()); + return turnContext.sendActivity(endOfConversation).thenApply(result -> null); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java new file mode 100644 index 000000000..e85d547c7 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.echoskillbot.authentication; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsValidator; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.integration.Configuration; + +/** + * Sample claims validator that loads an allowed list from configuration if + * presentand checks that requests are coming from allowed parent bots. + */ +public class AllowedCallersClaimsValidator extends ClaimsValidator { + + private final String configKey = "AllowedCallers"; + private final List allowedCallers; + + public AllowedCallersClaimsValidator(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config cannot be null."); + } + + // AllowedCallers instanceof the setting in the application.properties file + // that consists of the list of parent bot Ds that are allowed to access the + // skill. + // To add a new parent bot, simply edit the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + // In this sample, we allow all callers if AllowedCallers contains an "*". + String[] appsList = config.getProperties(configKey); + if (appsList == null) { + throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey)); + } + + allowedCallers = Arrays.asList(appsList); + } + + @Override + public CompletableFuture validateClaims(Map claims) { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) { + // Check that the appId claim in the skill request instanceof in the list of + // callers configured for this bot. + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedCallers.contains(appId)) { + return Async.completeExceptionally( + new RuntimeException( + String.format( + "Received a request from a bot with an app ID of \"%s\". " + + "To enable requests from this caller, add the app ID to your configuration file.", + appId + ) + ) + ); + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/application.properties b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/application.properties new file mode 100644 index 000000000..54a3053bd --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/application.properties @@ -0,0 +1,9 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=39783 +# This is a comma separate list with the App IDs that will have access to the skill. +# This setting is used in AllowedCallersClaimsValidator. +# Examples: +# * allows all callers. +# AppId1,AppId2 only allows access to parent bots with "AppId1" and "AppId2". +AllowedCallers=* diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/log4j2.json b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/META-INF/MANIFEST.MF b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/WEB-INF/web.xml b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/index.html b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json new file mode 100644 index 000000000..924b68e6f --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json", + "$id": "EchoSkillBot", + "name": "Echo Skill bot", + "version": "1.0", + "description": "This is a sample echo skill", + "publisherName": "Microsoft", + "privacyUrl": "https://echoskillbot.contoso.com/privacy.html", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "", + "iconUrl": "https://echoskillbot.contoso.com/icon.png", + "tags": [ + "sample", + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Default endpoint for the skill", + "endpointUrl": "http://echoskillbot.contoso.com/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] + } \ No newline at end of file diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/test/java/com/microsoft/bot/sample/echoskillbot/ApplicationTest.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/test/java/com/microsoft/bot/sample/echoskillbot/ApplicationTest.java new file mode 100644 index 000000000..1a6fb0e7b --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/test/java/com/microsoft/bot/sample/echoskillbot/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.echoskillbot; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/80.skills-simple-bot-to-bot/README.md b/samples/80.skills-simple-bot-to-bot/README.md new file mode 100644 index 000000000..b8de835a0 --- /dev/null +++ b/samples/80.skills-simple-bot-to-bot/README.md @@ -0,0 +1,59 @@ +# SimpleBotToBot Echo Skill + +Bot Framework v4 skills echo sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple skill consumer (SimpleRootBot) that sends message activities to a skill (EchoSkillBot) that echoes it back. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## Key concepts in this sample + +The solution includes a parent bot (`SimpleRootBot`) and a skill bot (`EchoSkillBot`) and shows how the parent bot can post activities to the skill bot and returns the skill responses to the user. + +- `SimpleRootBot`: this project shows how to consume an echo skill and includes: + - A [RootBot](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java) that calls the echo skill and keeps the conversation active until the user says "end" or "stop". [RootBot](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java) also keeps track of the conversation with the skill and handles the `EndOfConversation` activity received from the skill to terminate the conversation + - A simple [SkillConversationIdFactory](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java) based on an in memory `Map` that creates and maintains conversation IDs used to interact with a skill + - A [SkillsConfiguration](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java) class that can load skill definitions from `appsettings` + - A [SkillController](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java) that handles skill responses + - An [AllowedSkillsClaimsValidator](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Authentication/AllowedSkillsClaimsValidator.java) class that is used to authenticate that responses sent to the bot are coming from the configured skills + - A [Application](SimpleRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java) class that shows how to register the different skill components for dependency injection +- `EchoSkillBot`: this project shows a simple echo skill that receives message activities from the parent bot and echoes what the user said. This project includes: + - A sample [EchoBot](EchoSkillBot/src/main/java/com/microsoft/echoskillbot/EchoBot.java) that shows how to send EndOfConversation based on the message sent to the skill and yield control back to the parent bot + - A sample [AllowedCallersClaimsValidator](EchoSkillBot/src/main/java/com/microsoft/echoskillbot/authentication/AllowedCallersClaimsValidator.java) that shows how validate that the skill is only invoked from a list of allowed callers + - A [sample skill manifest](EchoSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json) that describes what the skill can do + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/microsoft/botbuilder-samples.git + ``` + +- Create a bot registration in the azure portal for the `EchoSkillBot` and update [EchoSkillBot/application.properties](EchoSkillBot/src/main/resources/application.properties) with the `MicrosoftAppId` and `MicrosoftAppPassword` of the new bot registration +- Create a bot registration in the azure portal for the `SimpleRootBot` and update [SimpleRootBot/application.properties](SimpleRootBot/src/main/resources/application.properties) with the `MicrosoftAppId` and `MicrosoftAppPassword` of the new bot registration +- Update the `BotFrameworkSkills` section in [SimpleRootBot/application.properties](SimpleRootBot/src/main/resources/application.properties) with the app ID for the skill you created in the previous step +- (Optionally) Add the `SimpleRootBot` `MicrosoftAppId` to the `AllowedCallers` list in [EchoSkillBot/application.properties](EchoSkillBot/src/main/resources/application.properties) +- Open the `SimpleBotToBot` project and start it for debugging +- Open the `EchoSkillsBot` project and start it for debugging + + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.7.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages`, the `MicrosoftAppId` and `MicrosoftAppPassword` for the `SimpleRootBot` + +## Deploy the bots to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. diff --git a/samples/81.skills-skilldialog/README.md b/samples/81.skills-skilldialog/README.md new file mode 100644 index 000000000..c450052cb --- /dev/null +++ b/samples/81.skills-skilldialog/README.md @@ -0,0 +1,97 @@ +# SkillDialog + +Bot Framework v4 Skills with Dialogs sample. + +This bot has been created using the [Bot Framework](https://dev.botframework.com); it shows how to use a skill dialog from a root bot. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## Key concepts in this sample + +The solution uses dialogs, within both a parent bot (`dialog-root-bot`) and a skill bot (`dialog-skill-bot`). +It demonstrates how to post activities from the parent bot to the skill bot and return the skill responses to the user. + +- `dialog-root-bot`: this project shows how to consume a skill bot using a `SkillDialog`. It includes: + - A [Main Dialog](dialog-root-bot/dialogs/main_dialog.py) that can call different actions on a skill using a `SkillDialog`: + - To send events activities. + - To send message activities. + - To cancel a `SkillDialog` using `CancelAllDialogsAsync` that automatically sends an `EndOfConversation` activity to remotely let a skill know that it needs to end a conversation. + - A sample [AdapterWithErrorHandler](dialog-root-bot/adapter_with_error_handler.py) adapter that shows how to handle errors, terminate skills and send traces back to the emulator to help debugging the bot. + - A sample [AllowedSkillsClaimsValidator](dialog-root-bot/authentication/allowed_skills_claims_validator.py) class that shows how to validate that responses sent to the bot are coming from the configured skills. + - A [Logger Middleware](dialog-root-bot/middleware/logger_middleware.py) that shows how to handle and log activities coming from a skill. + - A [SkillConversationIdFactory](dialog-root-bot/skill_conversation_id_factory.py) based on `Storage` used to create and maintain conversation IDs to interact with a skill. + - A [SkillConfiguration](dialog-root-bot/config.py) class that can load skill definitions from the `DefaultConfig` class. + - An [app.py](dialog-root-bot/app.py) class that shows how to register the different root bot components. This file also creates a `SkillHandler` and `aiohttp_channel_service_routes` which are used to handle responses sent from the skills. +- `dialog_skill_bot`: this project shows a modified CoreBot that acts as a skill. It receives event and message activities from the parent bot and executes the requested tasks. This project includes: + - An [ActivityRouterDialog](dialog-skill-bot/dialogs/activity_router_dialog.py) that handles Event and Message activities coming from a parent and performs different tasks. + - Event activities are routed to specific dialogs using the parameters provided in the `Values` property of the activity. + - Message activities are sent to LUIS if configured and trigger the desired tasks if the intent is recognized. + - A sample [ActivityHandler](dialog-skill-bot/bots/skill_bot.py) that uses the `run_dialog` method on `DialogExtensions`. + + Note: Starting in Bot Framework 4.8, the `DialogExtensions` class was introduced to provide a `run_dialog` method wich adds support to automatically send `EndOfConversation` with return values when the bot is running as a skill and the current dialog ends. It also handles reprompt messages to resume a skill where it left of. + - A sample [SkillAdapterWithErrorHandler](dialog-skill-bot/skill_adapter_with_error_handler.py) adapter that shows how to handle errors, terminate the skills, send traces back to the emulator to help debugging the bot and send `EndOfConversation` messages to the parent bot with details of the error. + - A sample [AllowedCallersClaimsValidator](dialog-skill-bot/authentication/allow_callers_claims_validation.py) that shows how to validate that the skill is only invoked from a list of allowed callers + - An [app.py](dialog-skill-bot/app.py) class that shows how to register the different skill components. + - A [sample skill manifest](dialog-skill-bot/wwwroot/manifest/dialogchildbot-manifest-1.0.json) that describes what the skill can do. + + + +## To try this sample + +- Clone the repository + + ```bash + git clone https://github.com/microsoft/botbuilder-samples.git + ``` + +- Create a bot registration in the azure portal for the `dialog-skill-bot` and update [dialog-skill-bot/config.py](dialog-skill-bot/config.py) with the `MicrosoftAppId` and `MicrosoftAppPassword` of the new bot registration +- Create a bot registration in the azure portal for the `dialog-root-bot` and update [dialog-root-bot/config.py](dialog-root-bot/config.py) with the `MicrosoftAppId` and `MicrosoftAppPassword` of the new bot registration +- Update the `SKILLS.app_id` in [dialog-root-bot/config.py](dialog-root-bot/config.py) with the `MicrosoftAppId` for the skill you created in the previous step +- (Optionally) Add the `dialog-root-bot` `MicrosoftAppId` to the `AllowedCallers` comma separated list in [dialog-skill-bot/config.py](dialog-skill-bot/config.py) + +## Running the sample + +- In a terminal, navigate to `samples\python\81.skills-skilldialog\dialog-skill-bot` + + ```bash + cd samples\python\81.skills-skilldialog\dialog-skill-bot + ``` + +- Activate your desired virtual environment + +- Run `pip install -r requirements.txt` to install all dependencies + +- Run your bot with `python app.py` + +- Open a **second** terminal window and navigate to `samples\python\81.skills-skilldialog\dialog-root-bot` + + ```bash + cd samples\python\81.skills-skilldialog\dialog-root-bot + ``` + +- Activate your desired virtual environment + +- Run `pip install -r requirements.txt` to install all dependencies + +- Run your bot with `python app.py` + + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.7.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages`, the `MicrosoftAppId` and `MicrosoftAppPassword` for the `dialog-root-bot` + +## Deploy the bots to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. diff --git a/samples/81.skills-skilldialog/dialog-root-bot/.vscode/settings.json b/samples/81.skills-skilldialog/dialog-root-bot/.vscode/settings.json new file mode 100644 index 000000000..e0f15db2e --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/samples/81.skills-skilldialog/dialog-root-bot/LICENSE b/samples/81.skills-skilldialog/dialog-root-bot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/81.skills-skilldialog/dialog-root-bot/README.md b/samples/81.skills-skilldialog/dialog-root-bot/README.md new file mode 100644 index 000000000..275225592 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/README.md @@ -0,0 +1,3 @@ +# SimpleRootBot + +See [81.skills-skilldialog](../Readme.md) for details on how to configure and run this sample. diff --git a/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-new-rg.json b/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/pom.xml b/samples/81.skills-skilldialog/dialog-root-bot/pom.xml new file mode 100644 index 000000000..071b39b52 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/pom.xml @@ -0,0 +1,249 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + dialogrootbot + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Root Dialog Skill bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.dialogrootbot.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-builder + 4.6.0-preview9 + compile + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.dialogrootbot.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdapterWithErrorHandler.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdapterWithErrorHandler.java new file mode 100644 index 000000000..9846c6a9f --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdapterWithErrorHandler.java @@ -0,0 +1,137 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.sample.dialogrootbot; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.OnTurnErrorHandler; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog; +import com.microsoft.bot.sample.dialogrootbot.middleware.ConsoleLogger; +import com.microsoft.bot.sample.dialogrootbot.middleware.LoggerMiddleware; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.EndOfConversationCodes; +import com.microsoft.bot.schema.InputHints; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class AdapterWithErrorHandler extends BotFrameworkHttpAdapter { + + private ConversationState conversationState = null; + private SkillHttpClient skillHttpClient = null; + private SkillsConfiguration skillsConfiguration = null; + private Configuration configuration = null; + private Logger logger = LoggerFactory.getLogger(AdapterWithErrorHandler.class); + + public AdapterWithErrorHandler( + Configuration configuration, + ConversationState conversationState, + SkillHttpClient skillHttpClient, + SkillsConfiguration skillsConfiguration + ) { + super(configuration); + this.configuration = configuration; + this.conversationState = conversationState; + this.skillHttpClient = skillHttpClient; + this.skillsConfiguration = skillsConfiguration; + setOnTurnError(new AdapterErrorHandler()); + use(new LoggerMiddleware(new ConsoleLogger())); + } + + private class AdapterErrorHandler implements OnTurnErrorHandler { + + @Override + public CompletableFuture invoke(TurnContext turnContext, Throwable exception) { + return sendErrorMessage(turnContext, exception).thenAccept(result -> { + endSkillConversation(turnContext); + }).thenAccept(endResult -> { + clearConversationState(turnContext); + }); + } + + private CompletableFuture sendErrorMessage(TurnContext turnContext, Throwable exception) { + try { + // Send a message to the user. + String errorMessageText = "The bot encountered an error or bug."; + Activity errorMessage = + MessageFactory.text(errorMessageText, errorMessageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(errorMessage).thenAccept(result -> { + String secondLineMessageText = "To continue to run this bot, please fix the bot source code."; + Activity secondErrorMessage = + MessageFactory.text(secondLineMessageText, secondLineMessageText, InputHints.EXPECTING_INPUT); + turnContext.sendActivity(secondErrorMessage) + .thenApply( + sendResult -> { + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + // Note: we return the entire exception in the value property to help the + // developer; + // this should not be done in production. + return TurnContext.traceActivity( + turnContext, + String.format("OnTurnError Trace %s", exception.getMessage()) + ); + } + ); + }).thenApply(finalResult -> null); + + } catch (Exception ex) { + logger.error("Exception caught in sendErrorMessage", ex); + return Async.completeExceptionally(ex); + } + } + + private CompletableFuture endSkillConversation(TurnContext turnContext) { + if (skillHttpClient == null || skillsConfiguration == null) { + return CompletableFuture.completedFuture(null); + } + + // Inform the active skill that the conversation is ended so that it has a chance to clean up. + // Note: the root bot manages the ActiveSkillPropertyName, which has a value while the root bot + // has an active conversation with a skill. + StatePropertyAccessor skillAccessor = + conversationState.createProperty(MainDialog.ActiveSkillPropertyName); + // forwarded to a Skill. + return skillAccessor.get(turnContext, () -> null).thenApply(activeSkill -> { + if (activeSkill != null) { + String botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID); + + Activity endOfConversation = Activity.createEndOfConversationActivity(); + endOfConversation.setCode(EndOfConversationCodes.ROOT_SKILL_ERROR); + endOfConversation + .applyConversationReference(turnContext.getActivity().getConversationReference(), true); + + return conversationState.saveChanges(turnContext, true).thenCompose(saveResult -> { + return skillHttpClient.postActivity( + botId, + activeSkill, + skillsConfiguration.getSkillHostEndpoint(), + endOfConversation, + Object.class + ); + + }); + } + return CompletableFuture.completedFuture(null); + }).thenApply(result -> null); + } + + private CompletableFuture clearConversationState(TurnContext turnContext) { + try { + return conversationState.delete(turnContext); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdaptiveCard.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdaptiveCard.java new file mode 100644 index 000000000..fd8724919 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdaptiveCard.java @@ -0,0 +1,83 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class AdaptiveCard { + @JsonProperty("$schema") + private String schema = null; + + @JsonProperty("type") + private String type = null; + + @JsonProperty("version") + private String version = null; + + @JsonProperty("body") + private List body = null; + + @JsonIgnore + private Map additionalProperties = new HashMap(); + + @JsonProperty("$schema") + public String getSchema() { + return schema; + } + + @JsonProperty("$schema") + public void setSchema(String schema) { + this.schema = schema; + } + + @JsonProperty("type") + public String getType() { + return type; + } + + @JsonProperty("type") + public void setType(String type) { + this.type = type; + } + + @JsonProperty("version") + public String getVersion() { + return version; + } + + @JsonProperty("version") + public void setVersion(String version) { + this.version = version; + } + + @JsonProperty("body") + public List getBody() { + return body; + } + + @JsonProperty("body") + public void setBody(List body) { + this.body = body; + } + + + + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java new file mode 100644 index 000000000..bb2331ac1 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java @@ -0,0 +1,158 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.connector.authentication.ChannelProvider; +import com.microsoft.bot.connector.authentication.CredentialProvider; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.dialogrootbot.Bots.RootBot; +import com.microsoft.bot.sample.dialogrootbot.authentication.AllowedSkillsClaimsValidator; +import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; +import org.springframework.context.annotation.Primary; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + ConversationState conversationState, + SkillsConfiguration skillsConfig, + SkillHttpClient skillClient, + Configuration configuration, + MainDialog mainDialog + ) { + return new RootBot(conversationState, mainDialog); + } + + @Bean + public MainDialog getMainDialog( + ConversationState conversationState, + SkillConversationIdFactoryBase conversationIdFactory, + SkillHttpClient skillClient, + SkillsConfiguration skillsConfig, + Configuration configuration + ) { + return new MainDialog(conversationState, conversationIdFactory, skillClient, skillsConfig, configuration); + } + + @Primary + @Bean + public AuthenticationConfiguration getAuthenticationConfiguration( + Configuration configuration, + AllowedSkillsClaimsValidator allowedSkillsClaimsValidator + ) { + AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); + authenticationConfiguration.setClaimsValidator(allowedSkillsClaimsValidator); + return authenticationConfiguration; + } + + @Bean + public AllowedSkillsClaimsValidator getAllowedSkillsClaimsValidator(SkillsConfiguration skillsConfiguration) { + return new AllowedSkillsClaimsValidator(skillsConfiguration); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Bean + @Primary + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor( + Configuration configuration, + ConversationState conversationState, + SkillHttpClient skillHttpClient, + SkillsConfiguration skillsConfiguration + ) { + return new AdapterWithErrorHandler( + configuration, + conversationState, + skillHttpClient, + skillsConfiguration); + } + + @Bean + public SkillsConfiguration getSkillsConfiguration(Configuration configuration) { + return new SkillsConfiguration(configuration); + } + + @Bean + public SkillHttpClient getSkillHttpClient( + CredentialProvider credentialProvider, + SkillConversationIdFactoryBase conversationIdFactory, + ChannelProvider channelProvider + ) { + return new SkillHttpClient(credentialProvider, conversationIdFactory, channelProvider); + } + + @Bean + public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase(Storage storage) { + return new SkillConversationIdFactory(storage); + } + + @Bean public ChannelServiceHandler getChannelServiceHandler( + BotAdapter botAdapter, + Bot bot, + SkillConversationIdFactoryBase conversationIdFactory, + CredentialProvider credentialProvider, + AuthenticationConfiguration authConfig, + ChannelProvider channelProvider + ) { + return new SkillHandler( + botAdapter, + bot, + conversationIdFactory, + credentialProvider, + authConfig, + channelProvider); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Body.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Body.java new file mode 100644 index 000000000..95c09213b --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Body.java @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import com.fasterxml.jackson.annotation.JsonAnyGetter; +import com.fasterxml.jackson.annotation.JsonAnySetter; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.util.HashMap; +import java.util.Map; + +public class Body { + + @JsonProperty("type") + private String type; + + @JsonProperty("url") + private String url; + + @JsonProperty("size") + private String size; + + @JsonProperty("spacing") + private String spacing; + + @JsonProperty("weight") + private String weight; + + @JsonProperty("text") + private String text; + + @JsonProperty("wrap") + private String wrap; + + @JsonProperty("maxLines") + private Integer maxLines; + + @JsonProperty("color") + private String color; + + @JsonIgnore + private Map additionalProperties = new HashMap(); + + @JsonProperty("type") + public String getType() { + return type; + } + + @JsonProperty("type") + public void setType(String type) { + this.type = type; + } + + @JsonProperty("url") + public String getUrl() { + return url; + } + + @JsonProperty("url") + public void setUrl(String url) { + this.url = url; + } + + @JsonProperty("size") + public String getSize() { + return size; + } + + @JsonProperty("size") + public void setSize(String size) { + this.size = size; + } + + @JsonProperty("spacing") + public String getSpacing() { + return spacing; + } + + @JsonProperty("spacing") + public void setSpacing(String spacing) { + this.spacing = spacing; + } + + @JsonProperty("weight") + public String getWeight() { + return weight; + } + + @JsonProperty("weight") + public void setWeight(String weight) { + this.weight = weight; + } + + @JsonProperty("text") + public String getText() { + return text; + } + + @JsonProperty("text") + public void setText(String text) { + this.text = text; + } + + @JsonProperty("wrap") + public String getWrap() { + return wrap; + } + + @JsonProperty("wrap") + public void setWrap(String wrap) { + this.wrap = wrap; + } + + @JsonProperty("maxLines") + public Integer getMaxLines() { + return maxLines; + } + + @JsonProperty("MaxLines") + public void setMaxLines(Integer maxLines) { + this.maxLines = maxLines; + } + + @JsonAnyGetter + public Map getAdditionalProperties() { + return this.additionalProperties; + } + + @JsonAnySetter + public void setAdditionalProperty(String name, Object value) { + this.additionalProperties.put(name, value); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java new file mode 100644 index 000000000..227afb8c4 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogrootbot.Bots; + +import java.io.IOException; +import java.io.InputStream; +import java.io.StringWriter; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import com.microsoft.bot.sample.dialogrootbot.AdaptiveCard; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.io.input.BOMInputStream; +import org.apache.commons.lang3.StringUtils; +import org.slf4j.LoggerFactory; + +public class RootBot extends ActivityHandler { + private final ConversationState conversationState; + private final Dialog mainDialog; + + public RootBot(ConversationState conversationState, T mainDialog) { + this.conversationState = conversationState; + this.mainDialog = mainDialog; + } + + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return handleTurn(turnContext).thenCompose(result -> conversationState.saveChanges(turnContext, false)); + } + + private CompletableFuture handleTurn(TurnContext turnContext) { + if (!turnContext.getActivity().getType().equals(ActivityTypes.CONVERSATION_UPDATE)) { + // Run the Dialog with the Activity. + return Dialog.run(mainDialog, turnContext, conversationState.createProperty("DialogState")); + } else { + // Let the super.class handle the activity. + return super.onTurn(turnContext); + } + } + + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + + Attachment welcomeCard = createAdaptiveCardAttachment("welcomeCard.json"); + Activity activity = MessageFactory.attachment(welcomeCard); + activity.setSpeak("Welcome to the Dialog Skill Prototype!"); + + return membersAdded.stream() + .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> runWelcome(turnContext, activity)) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponses -> null); + } + + private CompletableFuture runWelcome(TurnContext turnContext, Activity activity) { + return turnContext.sendActivity(activity).thenAccept(resourceResponses -> { + Dialog.run(mainDialog, turnContext, conversationState.createProperty("DialogState")); + }); + } + + // Load attachment from embedded resource. + private Attachment createAdaptiveCardAttachment(String fileName) { + try { + InputStream input = getClass().getClassLoader().getResourceAsStream(fileName); + BOMInputStream bomIn = new BOMInputStream(input); + String content; + StringWriter writer = new StringWriter(); + IOUtils.copy(bomIn, writer, StandardCharsets.UTF_8); + content = writer.toString(); + bomIn.close(); + return new Attachment() { + { + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(new JacksonAdapter().serializer().readValue(content, AdaptiveCard.class)); + } + }; + } catch (IOException e) { + LoggerFactory.getLogger(RootBot.class).error("createAdaptiveCardAttachment", e); + } + return new Attachment(); + } + +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java new file mode 100644 index 000000000..f9e8cb23c --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.schema.ConversationReference; + +import org.apache.commons.lang3.StringUtils; + +/** + * A {@link SkillConversationIdFactory} that uses an in memory + * {@link Map{TKey,TValue}} to store and retrieve {@link ConversationReference} + * instances. + */ +public class SkillConversationIdFactory extends SkillConversationIdFactoryBase { + + private Storage storage; + + public SkillConversationIdFactory(Storage storage) { + if (storage == null) { + throw new IllegalArgumentException("Storage cannot be null."); + } + this.storage = storage; + } + + @Override + public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { + if (options == null) { + Async.completeExceptionally(new IllegalArgumentException("options cannot be null.")); + } + ConversationReference conversationReference = options.getActivity().getConversationReference(); + String skillConversationId = String.format( + "%s-%s-%s-skillconvo", + conversationReference.getConversation().getId(), + options.getBotFrameworkSkill().getId(), + conversationReference.getChannelId() + ); + + SkillConversationReference skillConversationReference = new SkillConversationReference(); + skillConversationReference.setConversationReference(conversationReference); + skillConversationReference.setOAuthScope(options.getFromBotOAuthScope()); + Map skillConversationInfo = new HashMap(); + skillConversationInfo.put(skillConversationId, skillConversationReference); + return storage.write(skillConversationInfo) + .thenCompose(result -> CompletableFuture.completedFuture(skillConversationId)); + } + + @Override + public CompletableFuture getSkillConversationReference(String skillConversationId) { + if (StringUtils.isAllBlank(skillConversationId)) { + Async.completeExceptionally(new IllegalArgumentException("skillConversationId cannot be null.")); + } + + return storage.read(new String[] {skillConversationId}).thenCompose(skillConversationInfo -> { + if (skillConversationInfo.size() > 0) { + return CompletableFuture + .completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId)); + } else { + return CompletableFuture.completedFuture(null); + } + }); + } + + @Override + public CompletableFuture deleteConversationReference(String skillConversationId) { + return storage.delete(new String[] {skillConversationId}); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillsConfiguration.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillsConfiguration.java new file mode 100644 index 000000000..8ec38a9e7 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillsConfiguration.java @@ -0,0 +1,77 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.integration.Configuration; + +import org.apache.commons.lang3.StringUtils; + +/** + * A helper class that loads Skills information from configuration. + */ +public class SkillsConfiguration { + + private URI skillHostEndpoint; + + private Map skills = new HashMap(); + + public SkillsConfiguration(Configuration configuration) { + + boolean noMoreEntries = false; + int indexCount = 0; + while (!noMoreEntries) { + String botID = configuration.getProperty(String.format("BotFrameworkSkills[%d].Id", indexCount)); + String botAppId = configuration.getProperty(String.format("BotFrameworkSkills[%d].AppId", indexCount)); + String skillEndPoint = + configuration.getProperty(String.format("BotFrameworkSkills[%d].SkillEndpoint", indexCount)); + if ( + StringUtils.isNotBlank(botID) && StringUtils.isNotBlank(botAppId) + && StringUtils.isNotBlank(skillEndPoint) + ) { + BotFrameworkSkill newSkill = new BotFrameworkSkill(); + newSkill.setId(botID); + newSkill.setAppId(botAppId); + try { + newSkill.setSkillEndpoint(new URI(skillEndPoint)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + skills.put(botID, newSkill); + indexCount++; + } else { + noMoreEntries = true; + } + } + + String skillHost = configuration.getProperty("SkillhostEndpoint"); + if (!StringUtils.isEmpty(skillHost)) { + try { + skillHostEndpoint = new URI(skillHost); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + } + } + + /** + * @return the SkillHostEndpoint value as a Uri. + */ + public URI getSkillHostEndpoint() { + return this.skillHostEndpoint; + } + + /** + * @return the Skills value as a Dictionary. + */ + public Map getSkills() { + return this.skills; + } + +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/authentication/AllowedSkillsClaimsValidator.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/authentication/AllowedSkillsClaimsValidator.java new file mode 100644 index 000000000..312ce5e14 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/authentication/AllowedSkillsClaimsValidator.java @@ -0,0 +1,58 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogrootbot.authentication; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsValidator; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.sample.dialogrootbot.SkillsConfiguration; + +/** + * Sample claims validator that loads an allowed list from configuration if + * presentand checks that requests are coming from allowed parent bots. + */ +public class AllowedSkillsClaimsValidator extends ClaimsValidator { + + private final List allowedSkills; + + public AllowedSkillsClaimsValidator(SkillsConfiguration skillsConfig) { + if (skillsConfig == null) { + throw new IllegalArgumentException("config cannot be null."); + } + + // Load the appIds for the configured skills (we will only allow responses from skills we have configured). + allowedSkills = new ArrayList(); + for (Map.Entry configuration : skillsConfig.getSkills().entrySet()) { + allowedSkills.add(configuration.getValue().getAppId()); + } + } + + @Override + public CompletableFuture validateClaims(Map claims) { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.isSkillClaim(claims)) { + // Check that the appId claim in the skill request instanceof in the list of callers + // configured for this bot. + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedSkills.contains(appId)) { + return Async.completeExceptionally( + new RuntimeException( + String.format("Received a request from an application with an appID of \"%s\". " + + "To enable requests from this skill, add the skill to your configuration file.", appId) + ) + ); + } + } + + return CompletableFuture.completedFuture(null); + } +} + diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/controller/SkillController.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/controller/SkillController.java new file mode 100644 index 000000000..031d2e109 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/controller/SkillController.java @@ -0,0 +1,16 @@ +package com.microsoft.bot.sample.dialogrootbot.controller; + +import com.microsoft.bot.builder.ChannelServiceHandler; +import com.microsoft.bot.integration.spring.ChannelServiceController; + +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping(value = {"/api/skills"}) +public class SkillController extends ChannelServiceController { + + public SkillController(ChannelServiceHandler handler) { + super(handler); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/dialogs/MainDialog.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/dialogs/MainDialog.java new file mode 100644 index 000000000..936c5936f --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/dialogs/MainDialog.java @@ -0,0 +1,365 @@ +package com.microsoft.bot.sample.dialogrootbot.dialogs; + +import com.microsoft.bot.dialogs.BeginSkillDialogOptions; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.SkillDialog; +import com.microsoft.bot.dialogs.SkillDialogOptions; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.FoundChoice; +import com.microsoft.bot.dialogs.prompts.ChoicePrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.SkillHttpClient; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; +import com.microsoft.bot.connector.authentication.MicrosoftAppCredentials; +import com.microsoft.bot.sample.dialogrootbot.SkillsConfiguration; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; + +import org.apache.commons.lang3.StringUtils; + +public class MainDialog extends ComponentDialog { + + // Constants used for selecting actions on the skill. + private final String SkillActionBookFlight = "BookFlight"; + private final String SkillActionBookFlightWithInputParameters = "BookFlight with input parameters"; + private final String SkillActionGetWeather = "GetWeather"; + private final String SkillActionMessage = "Message"; + + public static final String ActiveSkillPropertyName = + "com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog.ActiveSkillProperty"; + private final StatePropertyAccessor activeSkillProperty; + private final String _selectedSkillKey = + "com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog.SelectedSkillKey"; + private final SkillsConfiguration _skillsConfig; + + // Dependency injection uses this constructor to instantiate MainDialog. + public MainDialog( + ConversationState conversationState, + SkillConversationIdFactoryBase conversationIdFactory, + SkillHttpClient skillClient, + SkillsConfiguration skillsConfig, + Configuration configuration + ) { + super("MainDialog"); + String botId = configuration.getProperty(MicrosoftAppCredentials.MICROSOFTAPPID); + if (StringUtils.isEmpty(botId)) { + throw new IllegalArgumentException( + String.format("%s is not in configuration", MicrosoftAppCredentials.MICROSOFTAPPID) + ); + } + + if (skillsConfig == null) { + throw new IllegalArgumentException("skillsConfig cannot be null"); + } + + if (skillClient == null) { + throw new IllegalArgumentException("skillClient cannot be null"); + } + + if (conversationState == null) { + throw new IllegalArgumentException("conversationState cannot be null"); + } + + _skillsConfig = skillsConfig; + + // Use helper method to add SkillDialog instances for the configured skills. + addSkillDialogs(conversationState, conversationIdFactory, skillClient, skillsConfig, botId); + + // Add ChoicePrompt to render available skills. + addDialog(new ChoicePrompt("SkillPrompt")); + + // Add ChoicePrompt to render skill actions. + addDialog(new ChoicePrompt("SkillActionPrompt", (promptContext) -> { + if (!promptContext.getRecognized().getSucceeded()) { + // Assume the user wants to send a message if an item in the list is not + // selected. + FoundChoice foundChoice = new FoundChoice(); + foundChoice.setValue(SkillActionMessage); + promptContext.getRecognized().setValue(foundChoice); + } + return CompletableFuture.completedFuture(true); + }, "")); + + // Add main waterfall dialog for this bot. + WaterfallStep[] waterfallSteps = + {this::selectSkillStep, this::selectSkillActionStep, this::callSkillActionStep, this::finalStep}; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // Create state property to track the active skill. + activeSkillProperty = conversationState.createProperty(ActiveSkillPropertyName); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + // This instanceof an example on how to cancel a SkillDialog that instanceof + // currently in progress from the parent bot. + return activeSkillProperty.get(innerDc.getContext(), null).thenCompose(activeSkill -> { + Activity activity = innerDc.getContext().getActivity(); + if ( + activeSkill != null && activity.getType().equals(ActivityTypes.MESSAGE) + && activity.getText().equals("abort") + ) { + // Cancel all dialogs when the user says abort. + // The SkillDialog automatically sends an EndOfConversation message to the skill + // to let the + // skill know that it needs to end its current dialogs, too. + return innerDc.cancelAllDialogs() + .thenCompose( + result -> innerDc + .replaceDialog(getInitialDialogId(), "Canceled! \n\n What skill would you like to call?") + ); + } + + return super.onContinueDialog(innerDc); + }); + } + + // Render a prompt to select the skill to call. + public CompletableFuture selectSkillStep(WaterfallStepContext stepContext) { + String messageText = "What skill would you like to call?"; + // Create the PromptOptions from the skill configuration which contain the list + // of configured skills. + if (stepContext.getOptions() != null) { + messageText = stepContext.getOptions().toString(); + } + String repromptMessageText = "That was not a valid choice, please select a valid skill."; + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT)); + options + .setRetryPrompt(MessageFactory.text(repromptMessageText, repromptMessageText, InputHints.EXPECTING_INPUT)); + + List choicesList = new ArrayList(); + for (BotFrameworkSkill skill : _skillsConfig.getSkills().values()) { + choicesList.add(new Choice(skill.getId())); + } + options.setChoices(choicesList); + + // Prompt the user to select a skill. + return stepContext.prompt("SkillPrompt", options); + } + + // Render a prompt to select the action for the skill. + public CompletableFuture selectSkillActionStep(WaterfallStepContext stepContext) { + // Get the skill info super. on the selected skill. + String selectedSkillId = ((FoundChoice) stepContext.getResult()).getValue(); + BotFrameworkSkill selectedSkill = _skillsConfig.getSkills() + .values() + .stream() + .filter(x -> x.getId().equals(selectedSkillId)) + .findFirst() + .get(); + + // Remember the skill selected by the user. + stepContext.getValues().put(_selectedSkillKey, selectedSkill); + + // Create the PromptOptions with the actions supported by the selected skill. + String messageText = String.format( + "Select an action # to send to **%n** or just type in a " + "message and it will be forwarded to the skill", + selectedSkill.getId() + ); + PromptOptions options = new PromptOptions(); + options.setPrompt(MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT)); + options.setChoices(getSkillActions(selectedSkill)); + + // Prompt the user to select a skill action. + return stepContext.prompt("SkillActionPrompt", options); + } + + // Starts the SkillDialog super. on the user's selections. + public CompletableFuture callSkillActionStep(WaterfallStepContext stepContext) { + BotFrameworkSkill selectedSkill = (BotFrameworkSkill) stepContext.getValues().get(_selectedSkillKey); + + Activity skillActivity; + switch (selectedSkill.getId()) { + case "DialogSkillBot": + skillActivity = createDialogSkillBotActivity( + ((FoundChoice) stepContext.getResult()).getValue(), + stepContext.getContext() + ); + break; + + // We can add other case statements here if we support more than one skill. + default: + throw new RuntimeException(String.format("Unknown target skill id: %s.", selectedSkill.getId())); + } + + // Create the BeginSkillDialogOptions and assign the activity to send. + BeginSkillDialogOptions skillDialogArgs = new BeginSkillDialogOptions(); + skillDialogArgs.setActivity(skillActivity); + + // Save active skill in state. + activeSkillProperty.set(stepContext.getContext(), selectedSkill); + + // Start the skillDialog instance with the arguments. + return stepContext.beginDialog(selectedSkill.getId(), skillDialogArgs); + } + + // The SkillDialog has ended, render the results (if any) and restart + // MainDialog. + public CompletableFuture finalStep(WaterfallStepContext stepContext) { + return activeSkillProperty.get(stepContext.getContext(), () -> null).thenCompose(activeSkill -> { + if (stepContext.getResult() != null) { + String jsonResult = ""; + try { + jsonResult = + new JacksonAdapter().serialize(stepContext.getResult()).replace("{", "").replace("}", ""); + } catch (IOException e) { + e.printStackTrace(); + } + String message = + String.format("Skill \"%s\" invocation complete. Result: %s", activeSkill.getId(), jsonResult); + stepContext.getContext().sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT)); + } + + // Clear the skill selected by the user. + stepContext.getValues().put(_selectedSkillKey, null); + + // Clear active skill in state. + activeSkillProperty.delete(stepContext.getContext()); + + // Restart the main dialog with a different message the second time around. + return stepContext.replaceDialog( + getInitialDialogId(), + String.format("Done with \"%s\". \n\n What skill would you like to call?", activeSkill.getId()) + ); + }); + // Check if the skill returned any results and display them. + } + + // Helper method that creates and adds SkillDialog instances for the configured + // skills. + private void addSkillDialogs( + ConversationState conversationState, + SkillConversationIdFactoryBase conversationIdFactory, + SkillHttpClient skillClient, + SkillsConfiguration skillsConfig, + String botId + ) { + for (BotFrameworkSkill skillInfo : _skillsConfig.getSkills().values()) { + // Create the dialog options. + SkillDialogOptions skillDialogOptions = new SkillDialogOptions(); + skillDialogOptions.setBotId(botId); + skillDialogOptions.setConversationIdFactory(conversationIdFactory); + skillDialogOptions.setSkillClient(skillClient); + skillDialogOptions.setSkillHostEndpoint(skillsConfig.getSkillHostEndpoint()); + skillDialogOptions.setConversationState(conversationState); + skillDialogOptions.setSkill(skillInfo); + // Add a SkillDialog for the selected skill. + addDialog(new SkillDialog(skillDialogOptions, skillInfo.getId())); + } + } + + // Helper method to create Choice elements for the actions supported by the + // skill. + private List getSkillActions(BotFrameworkSkill skill) { + // Note: the bot would probably render this by reading the skill manifest. + // We are just using hardcoded skill actions here for simplicity. + + List choices = new ArrayList(); + switch (skill.getId()) { + case "DialogSkillBot": + choices.add(new Choice(SkillActionBookFlight)); + choices.add(new Choice(SkillActionBookFlightWithInputParameters)); + choices.add(new Choice(SkillActionGetWeather)); + break; + } + + return choices; + } + + // Helper method to create the activity to be sent to the DialogSkillBot using + // selected type and values. + private Activity createDialogSkillBotActivity(String selectedOption, TurnContext turnContext) { + + // Note: in a real bot, the dialogArgs will be created dynamically super. on the + // conversation + // and what each action requires; here we hardcode the values to make things + // simpler. + ObjectMapper mapper = new ObjectMapper(); + Activity activity = null; + + // Just forward the message activity to the skill with whatever the user said. + if (selectedOption.equalsIgnoreCase(SkillActionMessage)) { + // Note message activities also support input parameters but we are not using + // them in this example. + // Return a deep clone of the activity so we don't risk altering the original + // one + activity = Activity.clone(turnContext.getActivity()); + } + + // Send an event activity to the skill with "BookFlight" in the name. + if (selectedOption.equalsIgnoreCase(SkillActionBookFlight)) { + activity = Activity.createEventActivity(); + activity.setName(SkillActionBookFlight); + } + + // Send an event activity to the skill with "BookFlight" in the name and some + // testing values. + if (selectedOption.equalsIgnoreCase(SkillActionBookFlightWithInputParameters)) { + activity = Activity.createEventActivity(); + activity.setName(SkillActionBookFlight); + try { + activity.setValue( + mapper.readValue("{ \"origin\": \"New York\", \"destination\": \"Seattle\"}", Object.class) + ); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + + // Send an event activity to the skill with "GetWeather" in the name and some + // testing values. + if (selectedOption.equalsIgnoreCase(SkillActionGetWeather)) { + activity = Activity.createEventActivity(); + activity.setName(SkillActionGetWeather); + try { + activity + .setValue(mapper.readValue("{ \"latitude\": 47.614891, \"longitude\": -122.195801}", Object.class)); + } catch (JsonProcessingException e) { + e.printStackTrace(); + } + } + + if (activity == null) { + throw new RuntimeException(String.format("Unable to create a skill activity for \"%s\".", selectedOption)); + } + + // We are manually creating the activity to send to the skill; ensure we add the + // ChannelData and Properties + // from the original activity so the skill gets them. + // Note: this instanceof not necessary if we are just forwarding the current + // activity from context. + activity.setChannelData(turnContext.getActivity().getChannelData()); + for (String key : turnContext.getActivity().getProperties().keySet()) { + activity.setProperties(key, turnContext.getActivity().getProperties().get(key)); + } + + return activity; + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/ConsoleLogger.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/ConsoleLogger.java new file mode 100644 index 000000000..f2391d63a --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/ConsoleLogger.java @@ -0,0 +1,11 @@ +package com.microsoft.bot.sample.dialogrootbot.middleware; + +import java.util.concurrent.CompletableFuture; + +public class ConsoleLogger extends Logger { + + @Override + public CompletableFuture logEntry(String entryToLog) { + return super.logEntry(entryToLog); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/Logger.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/Logger.java new file mode 100644 index 000000000..ed980f188 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/Logger.java @@ -0,0 +1,11 @@ +package com.microsoft.bot.sample.dialogrootbot.middleware; + +import java.util.concurrent.CompletableFuture; + +public abstract class Logger { + + public CompletableFuture logEntry(String entryToLog) { + System.out.println(entryToLog); + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/LoggerMiddleware.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/LoggerMiddleware.java new file mode 100644 index 000000000..3593d6c3a --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/LoggerMiddleware.java @@ -0,0 +1,70 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.sample.dialogrootbot.middleware; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.Middleware; +import com.microsoft.bot.builder.NextDelegate; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + + +/** + * Uses an Logger instance to log user and bot messages. It filters out + * ContinueConversation events coming from skill responses. + */ +public class LoggerMiddleware implements Middleware { + + private final Logger _logger; + + public LoggerMiddleware(Logger logger) { + if (logger == null) { + throw new IllegalArgumentException("Logger cannot be null"); + } + _logger = logger; + } + + public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { + // Note: skill responses will show as ContinueConversation events; we don't log those. + // We only log incoming messages from users. + if (!turnContext.getActivity().getType().equals(ActivityTypes.EVENT) + && turnContext.getActivity().getName() != null + && turnContext.getActivity().getName().equals("ContinueConversation")) { + String message = String.format("User said: %s Type: \"%s\" Name: \"%s\"", + turnContext.getActivity().getText(), + turnContext.getActivity().getType(), + turnContext.getActivity().getName()); + _logger.logEntry(message); + } + + // Register outgoing handler. + // hook up onSend pipeline + turnContext.onSendActivities( + (ctx, activities, nextSend) -> { + // run full pipeline + return nextSend.get().thenApply(responses -> { + for (Activity activity : activities) { + String message = String.format("Bot said: %s Type: \"%s\" Name: \"%s\"", + activity.getText(), + activity.getType(), + activity.getName()); + _logger.logEntry(message); + } + return responses; + }); + } + ); + + if (next != null) { + return next.next(); + } + + return CompletableFuture.completedFuture(null); + } +} + diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/application.properties b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/application.properties new file mode 100644 index 000000000..2feae73a0 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/application.properties @@ -0,0 +1,8 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 +SkillhostEndpoint=http://localhost:3978/api/skills/ +#replicate these three entries, incrementing the index value [0] for each successive Skill that is added. +BotFrameworkSkills[0].Id=DialogSkillBot +BotFrameworkSkills[0].AppId= +BotFrameworkSkills[0].SkillEndpoint=http://localhost:39783/api/messages diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/log4j2.json b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/welcomeCard.json b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/welcomeCard.json new file mode 100644 index 000000000..eab77b256 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/welcomeCard.json @@ -0,0 +1,29 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "Medium", + "size": "Medium", + "weight": "Bolder", + "text": "Welcome to the Skill Dialog Sample!", + "wrap": true, + "maxLines": 0, + "color": "Accent" + }, + { + "type": "TextBlock", + "size": "default", + "text": "This sample allows you to connect to a skill using a SkillDialog and invoke several actions.", + "wrap": true, + "maxLines": 0 + } + ] +} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/WEB-INF/web.xml b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/index.html b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/test/java/com/microsoft/bot/sample/dialogrootbot/ApplicationTest.java b/samples/81.skills-skilldialog/dialog-root-bot/src/test/java/com/microsoft/bot/sample/dialogrootbot/ApplicationTest.java new file mode 100644 index 000000000..b633cebc7 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/test/java/com/microsoft/bot/sample/dialogrootbot/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogrootbot; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/.vscode/settings.json b/samples/81.skills-skilldialog/dialog-skill-bot/.vscode/settings.json new file mode 100644 index 000000000..e0f15db2e --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/LICENSE b/samples/81.skills-skilldialog/dialog-skill-bot/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/README.md b/samples/81.skills-skilldialog/dialog-skill-bot/README.md new file mode 100644 index 000000000..19c717938 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/README.md @@ -0,0 +1,3 @@ +# EchoSkillBot + +See [81.skills-skilldialog](../Readme.md) for details on how to configure and run this sample. diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-new-rg.json b/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml b/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml new file mode 100644 index 000000000..06077f356 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml @@ -0,0 +1,248 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + dialogSkillbot + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Dialog Skill Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.dialogskillbot.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.dialogskillbot.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java new file mode 100644 index 000000000..61f160d22 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogskillbot; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.dialogskillbot.authentication.AllowedCallersClaimsValidator; +import com.microsoft.bot.sample.dialogskillbot.bots.SkillBot; +import com.microsoft.bot.sample.dialogskillbot.dialogs.ActivityRouterDialog; +import com.microsoft.bot.sample.dialogskillbot.dialogs.DialogSkillBotRecognizer; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should override + * methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this + * method with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Configuration configuration, ConversationState converationState) { + + return new SkillBot( + converationState, + new ActivityRouterDialog(new DialogSkillBotRecognizer(configuration)) + ); + } + + @Override + public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { + AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); + authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration)); + return authenticationConfiguration; + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new SkillAdapterWithErrorHandler(configuration, getAuthenticationConfiguration(configuration)); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/SkillAdapterWithErrorHandler.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/SkillAdapterWithErrorHandler.java new file mode 100644 index 000000000..363326726 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/SkillAdapterWithErrorHandler.java @@ -0,0 +1,82 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.sample.dialogskillbot; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.OnTurnErrorHandler; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.EndOfConversationCodes; +import com.microsoft.bot.schema.InputHints; + +public class SkillAdapterWithErrorHandler extends BotFrameworkHttpAdapter { + + public SkillAdapterWithErrorHandler( + Configuration configuration, + AuthenticationConfiguration authenticationConfiguration + ) { + super(configuration, authenticationConfiguration); + setOnTurnError(new SkillAdapterErrorHandler()); + } + + private class SkillAdapterErrorHandler implements OnTurnErrorHandler { + + @Override + public CompletableFuture invoke(TurnContext turnContext, Throwable exception) { + return sendErrorMessage(turnContext, exception).thenAccept(result -> { + sendEoCToParent(turnContext, exception); + }); + } + + private CompletableFuture sendErrorMessage(TurnContext turnContext, Throwable exception) { + try { + // Send a message to the user. + String errorMessageText = "The skill encountered an error or bug."; + Activity errorMessage = + MessageFactory.text(errorMessageText, errorMessageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(errorMessage).thenAccept(result -> { + String secondLineMessageText = "To continue to run this bot, please fix the bot source code."; + Activity secondErrorMessage = + MessageFactory.text(secondLineMessageText, secondLineMessageText, InputHints.EXPECTING_INPUT); + turnContext.sendActivity(secondErrorMessage) + .thenApply( + sendResult -> { + // Send a trace activity, which will be displayed in the Bot Framework Emulator. + // Note: we return the entire exception in the value property to help the + // developer; + // this should not be done in production. + return TurnContext.traceActivity( + turnContext, + String.format("OnTurnError Trace %s", exception.toString()) + ); + + } + ); + }); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + private CompletableFuture sendEoCToParent(TurnContext turnContext, Throwable exception) { + try { + // Send an EndOfConversation activity to the skill caller with the error to end + // the conversation, + // and let the caller decide what to do. + Activity endOfConversation = Activity.createEndOfConversationActivity(); + endOfConversation.setCode(EndOfConversationCodes.SKILL_ERROR); + endOfConversation.setText(exception.getMessage()); + return turnContext.sendActivity(endOfConversation).thenApply(result -> null); + } catch (Exception ex) { + return Async.completeExceptionally(ex); + } + } + + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java new file mode 100644 index 000000000..c15c81cff --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.authentication; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.authentication.ClaimsValidator; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.integration.Configuration; + +/** + * Sample claims validator that loads an allowed list from configuration if + * presentand checks that requests are coming from allowed parent bots. + */ +public class AllowedCallersClaimsValidator extends ClaimsValidator { + + private final String configKey = "AllowedCallers"; + private final List allowedCallers; + + public AllowedCallersClaimsValidator(Configuration config) { + if (config == null) { + throw new IllegalArgumentException("config cannot be null."); + } + + // AllowedCallers instanceof the setting in the application.properties file + // that consists of the list of parent bot Ds that are allowed to access the + // skill. + // To add a new parent bot, simply edit the AllowedCallers and add + // the parent bot's Microsoft app ID to the list. + // In this sample, we allow all callers if AllowedCallers contains an "*". + String[] appsList = config.getProperties(configKey); + if (appsList == null) { + throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey)); + } + + allowedCallers = Arrays.asList(appsList); + } + + @Override + public CompletableFuture validateClaims(Map claims) { + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) { + // Check that the appId claim in the skill request instanceof in the list of + // callers configured for this bot. + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedCallers.contains(appId)) { + return Async.completeExceptionally( + new RuntimeException( + String.format( + "Received a request from a bot with an app ID of \"%s\". " + + "To enable requests from this caller, add the app ID to your configuration file.", + appId + ) + ) + ); + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/bots/SkillBot.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/bots/SkillBot.java new file mode 100644 index 000000000..2c124cb86 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/bots/SkillBot.java @@ -0,0 +1,31 @@ +package com.microsoft.bot.sample.dialogskillbot.bots; + +import java.util.concurrent.CompletableFuture; + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.Dialog; + +public class SkillBot extends ActivityHandler { + + private ConversationState conversationState; + private Dialog dialog; + + public SkillBot(ConversationState conversationState, T mainDialog) { + this.conversationState = conversationState; + this.dialog = mainDialog; + } + + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")) + .thenAccept(result -> { + conversationState.saveChanges(turnContext, false); + }); + } + +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/cognitivemodels/flightbooking.json b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/cognitivemodels/flightbooking.json new file mode 100644 index 000000000..8e25098df --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/cognitivemodels/flightbooking.json @@ -0,0 +1,339 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "FlightBooking", + "desc": "Luis Model for CoreBot", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "BookFlight" + }, + { + "name": "Cancel" + }, + { + "name": "GetWeather" + }, + { + "name": "None" + } + ], + "entities": [], + "composites": [ + { + "name": "From", + "children": [ + "Airport" + ], + "roles": [] + }, + { + "name": "To", + "children": [ + "Airport" + ], + "roles": [] + } + ], + "closedLists": [ + { + "name": "Airport", + "subLists": [ + { + "canonicalForm": "Paris", + "list": [ + "paris", + "cdg" + ] + }, + { + "canonicalForm": "London", + "list": [ + "london", + "lhr" + ] + }, + { + "canonicalForm": "Berlin", + "list": [ + "berlin", + "txl" + ] + }, + { + "canonicalForm": "New York", + "list": [ + "new york", + "jfk" + ] + }, + { + "canonicalForm": "Seattle", + "list": [ + "seattle", + "sea" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "datetimeV2", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [], + "utterances": [ + { + "text": "book a flight", + "intent": "BookFlight", + "entities": [] + }, + { + "text": "book a flight from new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 26 + } + ] + }, + { + "text": "book a flight from seattle", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 25 + } + ] + }, + { + "text": "book a hotel in new york", + "intent": "None", + "entities": [] + }, + { + "text": "book a restaurant", + "intent": "None", + "entities": [] + }, + { + "text": "book flight from london to paris on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 17, + "endPos": 22 + }, + { + "entity": "To", + "startPos": 27, + "endPos": 31 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 22, + "endPos": 27 + }, + { + "entity": "To", + "startPos": 32, + "endPos": 36 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "find an airport near me", + "intent": "None", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + }, + { + "entity": "From", + "startPos": 21, + "endPos": 26 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 9, + "endPos": 14 + }, + { + "entity": "To", + "startPos": 19, + "endPos": 23 + } + ] + }, + { + "text": "go to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 6, + "endPos": 10 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 11, + "endPos": 15 + }, + { + "entity": "To", + "startPos": 20, + "endPos": 25 + } + ] + }, + { + "text": "i'd like to rent a car", + "intent": "None", + "entities": [] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel from new york to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 12, + "endPos": 19 + }, + { + "entity": "To", + "startPos": 24, + "endPos": 28 + } + ] + }, + { + "text": "travel to new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 17 + } + ] + }, + { + "text": "travel to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "what's the forecast for this friday?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like for tomorrow", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like in new york", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "winter is coming", + "intent": "None", + "entities": [] + } + ], + "settings": [] + } diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/ActivityRouterDialog.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/ActivityRouterDialog.java new file mode 100644 index 000000000..a0a046dd1 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/ActivityRouterDialog.java @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.bot.schema.Serialization; + +/** + * A root dialog that can route activities sent to the skill to different + * sub-dialogs. + */ +public class ActivityRouterDialog extends ComponentDialog { + + private final DialogSkillBotRecognizer luisRecognizer; + + public ActivityRouterDialog(DialogSkillBotRecognizer luisRecognizer) { + super("ActivityRouterDialog"); + this.luisRecognizer = luisRecognizer; + + addDialog(new BookingDialog()); + List stepList = new ArrayList(); + stepList.add(this::processActivity); + addDialog(new WaterfallDialog("WaterfallDialog", stepList)); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture processActivity(WaterfallStepContext stepContext) { + // A skill can send trace activities, if needed. + TurnContext.traceActivity( + stepContext.getContext(), + String.format( + "{%s}.processActivity() Got ActivityType: %s", + this.getClass().getName(), + stepContext.getContext().getActivity().getType() + ) + ); + + switch (stepContext.getContext().getActivity().getType()) { + case ActivityTypes.EVENT: + return onEventActivity(stepContext); + + case ActivityTypes.MESSAGE: + return onMessageActivity(stepContext); + + default: + String defaultMessage = String + .format("Unrecognized ActivityType: \"%s\".", stepContext.getContext().getActivity().getType()); + // We didn't get an activity type we can handle. + return stepContext.getContext() + .sendActivity(MessageFactory.text(defaultMessage, defaultMessage, InputHints.IGNORING_INPUT)) + .thenCompose(result -> { + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE)); + }); + + } + } + + // This method performs different tasks super. on the event name. + private CompletableFuture onEventActivity(WaterfallStepContext stepContext) { + Activity activity = stepContext.getContext().getActivity(); + TurnContext.traceActivity( + stepContext.getContext(), + String.format( + "%s.onEventActivity(), label: %s, Value: %s", + this.getClass().getName(), + activity.getName(), + GetObjectAsJsonString(activity.getValue()) + ) + ); + + // Resolve what to execute super. on the event name. + switch (activity.getName()) { + case "BookFlight": + return beginBookFlight(stepContext); + + case "GetWeather": + return beginGetWeather(stepContext); + + default: + String message = String.format("Unrecognized EventName: \"%s\".", activity.getName()); + // We didn't get an event name we can handle. + stepContext.getContext().sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT)); + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE)); + } + } + + // This method just gets a message activity and runs it through LUS. + private CompletableFuture onMessageActivity(WaterfallStepContext stepContext) { + Activity activity = stepContext.getContext().getActivity(); + TurnContext.traceActivity( + stepContext.getContext(), + String.format( + "%s.onMessageActivity(), label: %s, Value: %s", + this.getClass().getName(), + activity.getName(), + GetObjectAsJsonString(activity.getValue()) + ) + ); + + if (!luisRecognizer.getIsConfigured()) { + String message = "NOTE: LUIS instanceof not configured. To enable all capabilities, add 'LuisAppId'," + + " 'LuisAPKey' and 'LuisAPHostName' to the appsettings.json file."; + return stepContext.getContext() + .sendActivity(MessageFactory.text(message, message, InputHints.IGNORING_INPUT)) + .thenCompose( + result -> CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE)) + ); + } else { + // Call LUIS with the utterance. + return luisRecognizer.recognize(stepContext.getContext(), RecognizerResult.class) + .thenCompose(luisResult -> { + // Create a message showing the LUS results. + StringBuilder sb = new StringBuilder(); + sb.append(String.format("LUIS results for \"%s\":", activity.getText())); + + sb.append( + String.format( + "Intent: \"%s\" Score: %s", + luisResult.getTopScoringIntent().intent, + luisResult.getTopScoringIntent().score + ) + ); + + return stepContext.getContext() + .sendActivity(MessageFactory.text(sb.toString(), sb.toString(), InputHints.IGNORING_INPUT)) + .thenCompose(result -> { + switch (luisResult.getTopScoringIntent().intent.toLowerCase()) { + case "bookflight": + return beginBookFlight(stepContext); + + case "getweather": + return beginGetWeather(stepContext); + + default: + // Catch all for unhandled intents. + String didntUnderstandMessageText = String.format( + "Sorry, I didn't get that. Please try asking in a different " + + "way (intent was %s)", + luisResult.getTopScoringIntent().intent + ); + Activity didntUnderstandMessage = MessageFactory.text( + didntUnderstandMessageText, + didntUnderstandMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext() + .sendActivity(didntUnderstandMessage) + .thenCompose( + stepResult -> CompletableFuture + .completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE)) + ); + + } + }); + // Start a dialog if we recognize the intent. + }); + } + } + + private static CompletableFuture beginGetWeather(WaterfallStepContext stepContext) { + Activity activity = stepContext.getContext().getActivity(); + Location location = new Location(); + if (activity.getValue() != null) { + try { + location = Serialization.safeGetAs(activity.getValue(), Location.class); + } catch (JsonProcessingException e) { + // something went wrong, so we create an empty Location so we won't get a null + // reference below when we acess location. + location = new Location(); + } + + } + + // We haven't implemented the GetWeatherDialog so we just display a TODO + // message. + String getWeatherMessageText = String + .format("TODO: get weather for here (lat: %s, long: %s)", location.getLatitude(), location.getLongitude()); + Activity getWeatherMessage = + MessageFactory.text(getWeatherMessageText, getWeatherMessageText, InputHints.IGNORING_INPUT); + return stepContext.getContext().sendActivity(getWeatherMessage).thenCompose(result -> { + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.COMPLETE)); + }); + + } + + private CompletableFuture beginBookFlight(WaterfallStepContext stepContext) { + Activity activity = stepContext.getContext().getActivity(); + BookingDetails bookingDetails = new BookingDetails(); + if (activity.getValue() != null) { + try { + bookingDetails = Serialization.safeGetAs(activity.getValue(), BookingDetails.class); + } catch (JsonProcessingException e) { + // we already initialized bookingDetails above, so the flow will run as if + // no details were sent. + } + } + + // Start the booking dialog. + Dialog bookingDialog = findDialog("BookingDialog"); + return stepContext.beginDialog(bookingDialog.getId(), bookingDetails); + } + + private String GetObjectAsJsonString(Object value) { + try { + return new JacksonAdapter().serialize(value); + } catch (IOException e) { + return null; + } + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDetails.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDetails.java new file mode 100644 index 000000000..549c6dac0 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDetails.java @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class BookingDetails { + + @JsonProperty(value = "destination") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String destination; + + @JsonProperty(value = "origin") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String origin; + + @JsonProperty(value = "travelDate") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String travelDate; + + /** + * @return the Destination value as a String. + */ + public String getDestination() { + return this.destination; + } + + /** + * @param withDestination The Destination value. + */ + public void setDestination(String withDestination) { + this.destination = withDestination; + } + + /** + * @return the Origin value as a String. + */ + public String getOrigin() { + return this.origin; + } + + /** + * @param withOrigin The Origin value. + */ + public void setOrigin(String withOrigin) { + this.origin = withOrigin; + } + + /** + * @return the TravelDate value as a String. + */ + public String getTravelDate() { + return this.travelDate; + } + + /** + * @param withTravelDate The TravelDate value. + */ + public void setTravelDate(String withTravelDate) { + this.travelDate = withTravelDate; + } + +} + diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDialog.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDialog.java new file mode 100644 index 000000000..29cbc0fd4 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDialog.java @@ -0,0 +1,112 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +public class BookingDialog extends CancelAndHelpDialog { + + private final String DestinationStepMsgText = "Where would you like to travel to?"; + private final String OriginStepMsgText = "Where are you traveling from?"; + + public BookingDialog() { + super("BookingDialog"); + addDialog(new TextPrompt("TextPrompt")); + addDialog(new ConfirmPrompt("ConfirmPrompt")); + addDialog(new DateResolverDialog(null)); + WaterfallStep[] waterfallSteps = { this::destinationStep, this::originStep, this::travelDateStep, + this::confirmStep, this::finalStep }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private static boolean IsAmbiguous(String timex) { + TimexProperty timexProperty = new TimexProperty(timex); + return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); + } + + private CompletableFuture destinationStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + if (bookingDetails.getDestination() == null) { + Activity promptMessage = MessageFactory.text(DestinationStepMsgText, DestinationStepMsgText, + InputHints.EXPECTING_INPUT); + + PromptOptions options = new PromptOptions(); + options.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", options); + } + + return stepContext.next(bookingDetails.getDestination()); + } + + private CompletableFuture originStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setDestination((String) stepContext.getResult()); + + if (bookingDetails.getOrigin() == null) { + Activity promptMessage = MessageFactory.text(OriginStepMsgText, OriginStepMsgText, + InputHints.EXPECTING_INPUT); + PromptOptions options = new PromptOptions(); + options.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", options); + } + + return stepContext.next(bookingDetails.getOrigin()); + } + + private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setOrigin((String) stepContext.getResult()); + + if (bookingDetails.getTravelDate() == null || IsAmbiguous(bookingDetails.getTravelDate())) { + return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); + } + + return stepContext.next(bookingDetails.getTravelDate()); + } + + private CompletableFuture confirmStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setTravelDate((String) stepContext.getResult()); + + String messageText = String.format( + "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", + bookingDetails.getDestination(), bookingDetails.getOrigin(), bookingDetails.getTravelDate()); + Activity promptMessage = MessageFactory.text(messageText, messageText, InputHints.EXPECTING_INPUT); + + PromptOptions options = new PromptOptions(); + options.setPrompt(promptMessage); + return stepContext.prompt("ConfirmPrompt", options); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + if ((Boolean) stepContext.getResult()) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + return stepContext.endDialog(bookingDetails); + } + + return stepContext.endDialog(null); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/CancelAndHelpDialog.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/CancelAndHelpDialog.java new file mode 100644 index 000000000..c7566bbdc --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/CancelAndHelpDialog.java @@ -0,0 +1,62 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; + +public class CancelAndHelpDialog extends ComponentDialog { + + private final String HelpMsgText = "Show help here"; + private final String CancelMsgText = "Canceling..."; + + public CancelAndHelpDialog(String id) { + super(id); + } + + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return interrupt(innerDc).thenCompose(result -> { + if (result != null && result instanceof DialogTurnResult) { + return CompletableFuture.completedFuture((DialogTurnResult) result); + } + return super.onContinueDialog(innerDc); + }); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + switch (text) { + case "help": + case "?": + Activity helpMessage = MessageFactory.text(HelpMsgText, HelpMsgText, InputHints.EXPECTING_INPUT); + return innerDc.getContext() + .sendActivity(helpMessage) + .thenCompose( + result -> CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING)) + ); + + case "cancel": + case "quit": + Activity cancelMessage = + MessageFactory.text(CancelMsgText, CancelMsgText, InputHints.IGNORING_INPUT); + return innerDc.getContext().sendActivity(cancelMessage).thenCompose(result -> { + return innerDc.cancelAllDialogs(); + }); + + } + } + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DateResolverDialog.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DateResolverDialog.java new file mode 100644 index 000000000..9ead84b08 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DateResolverDialog.java @@ -0,0 +1,100 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.applicationinsights.core.dependencies.apachecommons.lang3.StringUtils; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.DateTimeResolution; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidator; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +public class DateResolverDialog extends CancelAndHelpDialog { + + private final String PromptMsgText = "When would you like to travel?"; + private final String RepromptMsgText = "I'm sorry, to make your booking please enter a full travel date, including Day, Month, and Year."; + + public DateResolverDialog(String id) { + super(!StringUtils.isAllBlank(id) ? id : "DateResolverDialog"); + addDialog(new DateTimePrompt("DateTimePrompt", new dateTimePromptValidator(), null)); + WaterfallStep[] waterfallSteps = { this::initialStep, this::finalStep }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + class dateTimePromptValidator implements PromptValidator> { + + @Override + public CompletableFuture promptValidator( + PromptValidatorContext> promptContext) { + if (promptContext.getRecognized().getSucceeded()) { + // This value will be a TMEX. We are only interested in the Date part, so grab + // the first result and drop the Time part. + // TMEX instanceof a format that represents DateTime expressions that include + // some ambiguity, such as a missing Year. + String timex = promptContext.getRecognized().getValue().get(0).getTimex().split("T")[0]; + + // If this instanceof a definite Date that includes year, month and day we are + // good; otherwise, reprompt. + // A better solution might be to let the user know what part instanceof actually + // missing. + Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); + + return CompletableFuture.completedFuture(isDefinite); + } + return CompletableFuture.completedFuture(false); + } + + } + + public CompletableFuture initialStep(WaterfallStepContext stepContext) { + String timex = (String) stepContext.getOptions(); + + Activity promptMessage = MessageFactory.text(PromptMsgText, PromptMsgText, InputHints.EXPECTING_INPUT); + Activity repromptMessage = MessageFactory.text(RepromptMsgText, RepromptMsgText, InputHints.EXPECTING_INPUT); + + if (timex == null) { + // We were not given any date at all so prompt the user. + PromptOptions options = new PromptOptions(); + options.setPrompt(promptMessage); + options.setRetryPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", options); + } + + // We have a Date we just need to check it instanceof unambiguous. + TimexProperty timexProperty = new TimexProperty(timex); + if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { + // This instanceof essentially a "reprompt" of the data we were given up front. + PromptOptions options = new PromptOptions(); + options.setPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", options); + } + List resolutionList = new ArrayList(); + DateTimeResolution resolution = new DateTimeResolution(); + resolution.setTimex(timex); + resolutionList.add(resolution); + return stepContext.next(resolutionList); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + String timex = ((List) stepContext.getResult()).get(0).getTimex(); + return stepContext.endDialog(timex); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DialogSkillBotRecognizer.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DialogSkillBotRecognizer.java new file mode 100644 index 000000000..b1b3d73be --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DialogSkillBotRecognizer.java @@ -0,0 +1,54 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.builder.Recognizer; +import com.microsoft.bot.builder.RecognizerConvert; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.integration.Configuration; + +import org.apache.commons.lang3.StringUtils; + +public class DialogSkillBotRecognizer implements Recognizer { + + private final LuisRecognizer _recognizer; + public DialogSkillBotRecognizer(Configuration configuration) { + boolean luisIsConfigured = !StringUtils.isAllBlank(configuration.getProperty("LuisAppId")) + && !StringUtils.isAllBlank(configuration.getProperty("LuisAPIKey")) + && !StringUtils.isAllBlank(configuration.getProperty("LuisAPIHostName")); + if (luisIsConfigured) { + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + String.format("https://%s", configuration.getProperty("LuisAPIHostName"))); + LuisRecognizerOptionsV3 luisOptions = new LuisRecognizerOptionsV3(luisApplication); + + _recognizer = new LuisRecognizer(luisOptions); + } else { + _recognizer = null; + } + } + + // Returns true if LUS instanceof configured in the appsettings.json and initialized. + public boolean getIsConfigured() { + return _recognizer != null; + } + + public CompletableFuture recognize(TurnContext turnContext) { + return _recognizer.recognize(turnContext); + } + + public CompletableFuture recognize( + TurnContext turnContext, + Class c + ) { + return _recognizer.recognize(turnContext, c); + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/Location.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/Location.java new file mode 100644 index 000000000..b668540fb --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/Location.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.dialogskillbot.dialogs; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class Location { + + @JsonProperty(value = "latitude") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double latitude; + + @JsonProperty(value = "longitude") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Double longitude; + + @JsonProperty(value = "postalCode") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String postalCode; + + /** + * @return the Latitude value as a double?. + */ + public Double getLatitude() { + return this.latitude; + } + + /** + * @param withLatitude The Latitude value. + */ + public void setLatitude(Double withLatitude) { + this.latitude = withLatitude; + } + + /** + * @return the Longitude value as a double?. + */ + public Double getLongitude() { + return this.longitude; + } + + /** + * @param withLongitude The Longitude value. + */ + public void setLongitude(Double withLongitude) { + this.longitude = withLongitude; + } + + /** + * @return the PostalCode value as a String. + */ + public String getPostalCode() { + return this.postalCode; + } + + /** + * @param withPostalCode The PostalCode value. + */ + public void setPostalCode(String withPostalCode) { + this.postalCode = withPostalCode; + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/application.properties b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/application.properties new file mode 100644 index 000000000..f86b40a07 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/application.properties @@ -0,0 +1,13 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=39783 + +LuisAppId= +LuisAPIKey= +LuisAPIHostName= +# This is a comma separate list with the App IDs that will have access to the skill. +# This setting is used in AllowedCallersClaimsValidator. +# Examples: +# * allows all callers. +# AppId1,AppId2 only allows access to parent bots with "AppId1" and "AppId2". +AllowedCallers=* diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/log4j2.json b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/WEB-INF/web.xml b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/index.html b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json new file mode 100644 index 000000000..924b68e6f --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://schemas.botframework.com/schemas/skills/skill-manifest-2.0.0.json", + "$id": "EchoSkillBot", + "name": "Echo Skill bot", + "version": "1.0", + "description": "This is a sample echo skill", + "publisherName": "Microsoft", + "privacyUrl": "https://echoskillbot.contoso.com/privacy.html", + "copyright": "Copyright (c) Microsoft Corporation. All rights reserved.", + "license": "", + "iconUrl": "https://echoskillbot.contoso.com/icon.png", + "tags": [ + "sample", + "echo" + ], + "endpoints": [ + { + "name": "default", + "protocol": "BotFrameworkV3", + "description": "Default endpoint for the skill", + "endpointUrl": "http://echoskillbot.contoso.com/api/messages", + "msAppId": "00000000-0000-0000-0000-000000000000" + } + ] + } \ No newline at end of file diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/test/java/com/microsoft/bot/sample/dialogskillbot/ApplicationTest.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/test/java/com/microsoft/bot/sample/dialogskillbot/ApplicationTest.java new file mode 100644 index 000000000..a211ca163 --- /dev/null +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/test/java/com/microsoft/bot/sample/dialogskillbot/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.dialogskillbot; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 2d4ef624aa92457245e8a85c1d33977848aea00d Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 11 Mar 2021 14:35:57 -0600 Subject: [PATCH 098/221] Remove health check feature (#1052) Co-authored-by: tracyboehrer --- .../bot/builder/ActivityHandler.java | 18 --- .../microsoft/bot/builder/HealthCheck.java | 51 ------- .../bot/builder/ActivityHandlerTests.java | 101 ------------- .../bot/schema/HealthCheckResponse.java | 40 ----- .../microsoft/bot/schema/HealthResults.java | 139 ------------------ 5 files changed, 349 deletions(-) delete mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java delete mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java delete mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index 58b32a75f..e671f9edb 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -11,11 +11,9 @@ import org.apache.commons.lang3.StringUtils; -import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.HealthCheckResponse; import com.microsoft.bot.schema.MessageReaction; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.SignInConstants; @@ -415,10 +413,6 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon } return new InvokeResponse(HttpURLConnection.HTTP_INTERNAL_ERROR, null); }); - } else if (StringUtils.equals(turnContext.getActivity().getName(), "healthCheck")) { - CompletableFuture result = new CompletableFuture<>(); - result.complete(new InvokeResponse(HttpURLConnection.HTTP_OK, onHealthCheck(turnContext))); - return result; } CompletableFuture result = new CompletableFuture<>(); @@ -450,18 +444,6 @@ protected CompletableFuture onSignInInvoke(TurnContext turnContext) { return result; } - /** - * Invoked when a 'healthCheck' event is - * received when the base behavior of onInvokeActivity is used. - * - * @param turnContext The current TurnContext. - * @return A task that represents a HealthCheckResponse. - */ - protected CompletableFuture onHealthCheck(TurnContext turnContext) { - ConnectorClient client = turnContext.getTurnState().get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY); - return CompletableFuture.completedFuture(HealthCheck.createHealthCheckResponse(client)); - } - /** * Creates a success InvokeResponse with the specified body. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java deleted file mode 100644 index 3fe4ef890..000000000 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/HealthCheck.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.builder; - -import java.util.concurrent.ExecutionException; - -import com.microsoft.bot.connector.ConnectorClient; -import com.microsoft.bot.connector.authentication.AppCredentials; -import com.microsoft.bot.schema.HealthCheckResponse; -import com.microsoft.bot.schema.HealthResults; - -/** - * A class to process a HealthCheck request. - */ -public final class HealthCheck { - - private HealthCheck() { - // not called - } - - /** - * @param connector the ConnectorClient instance for this request - * @return HealthCheckResponse - */ - public static HealthCheckResponse createHealthCheckResponse(ConnectorClient connector) { - HealthResults healthResults = new HealthResults(); - healthResults.setSuccess(true); - - if (connector != null) { - healthResults.setUserAgent(connector.getUserAgent()); - AppCredentials credentials = (AppCredentials) connector.credentials(); - try { - healthResults.setAuthorization(credentials.getToken().get()); - } catch (InterruptedException | ExecutionException ignored) { - // An exception here may happen when you have a valid appId but invalid or blank secret. - // No callbacks will be possible, although the bot maybe healthy in other respects. - } - } - - if (healthResults.getAuthorization() != null) { - healthResults.setMessages(new String[]{"Health check succeeded."}); - } else { - healthResults.setMessages(new String[]{"Health check succeeded.", "Callbacks are not authorized."}); - } - - HealthCheckResponse response = new HealthCheckResponse(); - response.setHealthResults(healthResults); - return response; - } -} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 4ca4fcfc4..b6a5af8eb 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -10,9 +10,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.atomic.AtomicReference; -import org.mockito.internal.matchers.Not; public class ActivityHandlerTests { @Test @@ -428,98 +425,6 @@ public void TestUnrecognizedActivityType() { Assert.assertEquals("onUnrecognizedActivityType", bot.getRecord().get(0)); } - @Test - public void TestHealthCheckAsyncOverride() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INVOKE); - setName("healthCheck"); - } - }; - - TurnContext turnContext = new TurnContextImpl(new TestInvokeAdapter(), activity); - - TestActivityHandler bot = new TestActivityHandler(); - bot.onTurn(turnContext).join(); - - Assert.assertEquals(2, bot.getRecord().size()); - Assert.assertEquals("onInvokeActivity", bot.getRecord().get(0)); - Assert.assertEquals("onHealthCheck", bot.getRecord().get(1)); - } - - @Test - public void TestHealthCheckAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INVOKE); - setName("healthCheck"); - } - }; - - AtomicReference> activitiesToSend = new AtomicReference<>(); - TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity); - - ActivityHandler bot = new ActivityHandler(); - bot.onTurn(turnContext).join(); - - Assert.assertNotNull(activitiesToSend.get()); - Assert.assertEquals(1, activitiesToSend.get().size()); - Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse); - Assert.assertEquals(200, ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus()); - CompletableFuture future = ((CompletableFuture) ((InvokeResponse) activitiesToSend.get().get(0).getValue()) - .getBody()); - HealthCheckResponse result = new HealthCheckResponse(); - result = (HealthCheckResponse) future.join(); - Assert.assertTrue(result.getHealthResults().getSuccess()); - String[] messages = result.getHealthResults().getMessages(); - Assert.assertEquals(messages[0], "Health check succeeded."); - } - - @Test - public void TestHealthCheckWithConnectorAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INVOKE); - setName("healthCheck"); - } - }; - - AtomicReference> activitiesToSend = new AtomicReference<>(); - TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(activitiesToSend::set), activity); - MockConnectorClient mockConnector = new MockConnectorClient("Windows/3.1", new MockAppCredentials("awesome")); - turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, mockConnector); - ActivityHandler bot = new ActivityHandler(); - bot.onTurn(turnContext).join(); - - Assert.assertNotNull(activitiesToSend.get()); - Assert.assertEquals(1, activitiesToSend.get().size()); - Assert.assertTrue(activitiesToSend.get().get(0).getValue() instanceof InvokeResponse); - Assert.assertEquals( - 200, - ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getStatus() - ); - CompletableFuture future = - ((CompletableFuture) - ((InvokeResponse) activitiesToSend.get().get(0).getValue()).getBody()); - HealthCheckResponse result = new HealthCheckResponse(); - result = (HealthCheckResponse) future.join(); - Assert.assertTrue(result.getHealthResults().getSuccess()); - Assert.assertEquals(result.getHealthResults().getAuthorization(), "awesome"); - Assert.assertEquals(result.getHealthResults().getUserAgent(), "Windows/3.1"); - String[] messages = result.getHealthResults().getMessages(); - Assert.assertEquals(messages[0], "Health check succeeded."); - } - - private static class TestInvokeAdapter extends NotImplementedAdapter { - @Override - public CompletableFuture sendActivities( - TurnContext context, - List activities - ) { - return CompletableFuture.completedFuture(new ResourceResponse[0]); - } - } - private static class NotImplementedAdapter extends BotAdapter { @Override public CompletableFuture sendActivities( @@ -635,12 +540,6 @@ protected CompletableFuture onInvokeActivity(TurnContext turnCon return super.onInvokeActivity(turnContext); } - @Override - protected CompletableFuture onHealthCheck(TurnContext turnContext) { - record.add("onHealthCheck"); - return super.onHealthCheck(turnContext); - } - @Override protected CompletableFuture onInstallationUpdate(TurnContext turnContext) { record.add("onInstallationUpdate"); diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java deleted file mode 100644 index caf9d49af..000000000 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthCheckResponse.java +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.schema; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Defines the structure that is returned as the result of a health check on the bot. - * The health check is sent to the bot as an {@link Activity} of type "invoke" and this class along - * with {@link HealthResults} defines the structure of the body of the response. - * The name of the invoke Activity is "healthCheck". - */ -public class HealthCheckResponse { - /** - * The health check results. - */ - @JsonProperty(value = "healthResults") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private HealthResults healthResults; - - /** - * Gets the healthResults value. - * - * @return The healthResults value. - */ - public HealthResults getHealthResults() { - return this.healthResults; - } - - /** - * Sets the healthResults value. - * - * @param withHealthResults The healthResults value to set. - */ - public void setHealthResults(HealthResults withHealthResults) { - this.healthResults = withHealthResults; - } -} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java deleted file mode 100644 index 2e727cc00..000000000 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HealthResults.java +++ /dev/null @@ -1,139 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.schema; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Defines the structure that is returned as the result of a health check on the bot. - * The health check is sent to the bot as an InvokeActivity and this class along with {@link HealthCheckResponse} - * defines the structure of the body of the response. - */ -public class HealthResults { - /** - * A value indicating whether the health check has succeeded and the bot is healthy. - */ - @JsonProperty(value = "success") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private Boolean success; - - /** - * A value that is exactly the same as the Authorization header that would have been added to an HTTP POST back. - */ - @JsonProperty(value = "authorization") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String authorization; - - /** - * A value that is exactly the same as the User-Agent header that would have been added to an HTTP POST back. - */ - @JsonProperty(value = "user-agent") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String userAgent; - - /** - * Informational messages that can be optionally included in the health check response. - */ - @JsonProperty(value = "messages") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String[] messages; - - /** - * Diagnostic data that can be optionally included in the health check response. - */ - @JsonProperty(value = "diagnostics") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private Object diagnostics; - - /** - * Gets the success value. - * - * @return The success value. - */ - public Boolean getSuccess() { - return this.success; - } - - /** - * Sets the success value. - * - * @param withSuccess The success value to set. - */ - public void setSuccess(Boolean withSuccess) { - this.success = withSuccess; - } - - /** - * Get the authorization value. - * - * @return the authorization value - */ - public String getAuthorization() { - return this.authorization; - } - - /** - * Set the authorization value. - * - * @param withAuthorization the authization value to set - */ - public void setAuthorization(String withAuthorization) { - this.authorization = withAuthorization; - } - - /** - * Get the userAgent value. - * - * @return the userAgent value - */ - public String getUserAgent() { - return this.userAgent; - } - - /** - * Set the userAgent value. - * - * @param withUserAgent the userAgent value to set - */ - public void setUserAgent(String withUserAgent) { - this.userAgent = withUserAgent; - } - - /** - * Get the messages value. - * - * @return the messages value - */ - public String[] getMessages() { - return this.messages; - } - - /** - * Set the messages value. - * - * @param withMessages the messages value to set - */ - public void setMessages(String[] withMessages) { - this.messages = withMessages; - } - - /** - * Get the diagnostics value. - * - * @return the diagnostics value - */ - public Object getDiagnostics() { - return this.diagnostics; - } - - /** - * Set the diagnostics value. - * - * @param withDiagnostics the diagnostics value to set - */ - public void setDiagnostics(Object withDiagnostics) { - this.diagnostics = withDiagnostics; - } -} From e5164bf39a4fb1a1653a068305faf05a792d7829 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 12 Mar 2021 11:29:17 -0600 Subject: [PATCH 099/221] Added missing skills tests, fixed locale issues (#1054) --- .../bot/builder/BotFrameworkAdapter.java | 1 + .../bot/builder/adapters/TestAdapter.java | 93 ++++----- .../com/microsoft/bot/dialogs/Dialog.java | 2 +- .../bot/dialogs/prompts/ConfirmPrompt.java | 2 +- .../bot/dialogs/DialogManagerTests.java | 196 +++++++++++------- .../prompts/ConfirmPromptLocTests.java | 1 + 6 files changed, 167 insertions(+), 128 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index eb8ed6491..e8b309f5f 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -1499,6 +1499,7 @@ public CompletableFuture getOAuthSignInLink(TurnContext context, AppCred setBot(activity.getRecipient()); setChannelId(activity.getChannelId()); setConversation(activity.getConversation()); + setLocale(activity.getLocale()); setServiceUrl(activity.getServiceUrl()); setUser(activity.getFrom()); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 6137eb730..f6f6bb4f8 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -23,7 +23,7 @@ public class TestAdapter extends BotAdapter implements UserTokenProvider { private final Queue botReplies = new LinkedList<>(); private int nextId = 0; private ConversationReference conversationReference; - private String locale; + private String locale = "en-us"; private boolean sendTraceActivity = false; private Map exchangableToken = new HashMap(); @@ -93,64 +93,62 @@ public TestAdapter(String channelId) { public TestAdapter(String channelId, boolean sendTraceActivity) { this.sendTraceActivity = sendTraceActivity; - setConversationReference(new ConversationReference() { + + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setChannelId(channelId); + conversationReference.setServiceUrl("https://test.com"); + conversationReference.setUser(new ChannelAccount() { { - setChannelId(channelId); - setServiceUrl("https://test.com"); - setUser(new ChannelAccount() { - { - setId("user1"); - setName("User1"); - } - }); - setBot(new ChannelAccount() { - { - setId("bot"); - setName("Bot"); - } - }); - setConversation(new ConversationAccount() { - { - setIsGroup(false); - setConversationType("convo1"); - setId("Conversation1"); - } - }); - setLocale(this.getLocale()); + setId("user1"); + setName("User1"); + } + }); + conversationReference.setBot(new ChannelAccount() { + { + setId("bot"); + setName("Bot"); + } + }); + conversationReference.setConversation(new ConversationAccount() { + { + setIsGroup(false); + setConversationType("convo1"); + setId("Conversation1"); } }); + conversationReference.setLocale(this.getLocale()); + + setConversationReference(conversationReference); } public TestAdapter(ConversationReference reference) { if (reference != null) { setConversationReference(reference); } else { - setConversationReference(new ConversationReference() { + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setChannelId(Channels.TEST); + conversationReference.setServiceUrl("https://test.com"); + conversationReference.setUser(new ChannelAccount() { { - setChannelId(Channels.TEST); - setServiceUrl("https://test.com"); - setUser(new ChannelAccount() { - { - setId("user1"); - setName("User1"); - } - }); - setBot(new ChannelAccount() { - { - setId("bot"); - setName("Bot"); - } - }); - setConversation(new ConversationAccount() { - { - setIsGroup(false); - setConversationType("convo1"); - setId("Conversation1"); - } - }); - setLocale(this.getLocale()); + setId("user1"); + setName("User1"); + } + }); + conversationReference.setBot(new ChannelAccount() { + { + setId("bot"); + setName("Bot"); + } + }); + conversationReference.setConversation(new ConversationAccount() { + { + setIsGroup(false); + setConversationType("convo1"); + setId("Conversation1"); } }); + conversationReference.setLocale(this.getLocale()); + setConversationReference(conversationReference); } } @@ -359,6 +357,7 @@ public Activity makeActivity(String withText) { setText(withText); } }; + activity.setLocale(getLocale() != null ? getLocale() : "en-us"); return activity; } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index f20a3ae2e..72a09f7a7 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -359,7 +359,7 @@ && sendEoCToParent(turnContext)) { : EndOfConversationCodes.USER_CANCELLED; Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION); activity.setValue(result.getResult()); - activity.setLocalTimeZone(turnContext.getActivity().getLocale()); + activity.setLocale(turnContext.getActivity().getLocale()); activity.setCode(code); return turnContext.sendActivity(activity).thenApply(finalResult -> null); } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java index 402d0f1a5..94bdf0805 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -32,7 +32,7 @@ public class ConfirmPrompt extends Prompt { /** - * A dictionary of Default Choices based on {@link GetSupportedCultures} . Can + * A map of Default Choices based on {@link GetSupportedCultures} . Can * be replaced by user using the constructor that contains choiceDefaults. */ private Map> choiceDefaults; diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java index 490bf7c9b..7ccc5ae13 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -4,11 +4,14 @@ package com.microsoft.bot.dialogs; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; import java.util.function.Supplier; +import com.microsoft.bot.builder.BotAdapter; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.MemoryStorage; import com.microsoft.bot.builder.SendActivitiesHandler; @@ -16,6 +19,10 @@ import com.microsoft.bot.builder.TraceTranscriptLogger; import com.microsoft.bot.builder.TranscriptLoggerMiddleware; import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; import com.microsoft.bot.builder.UserState; import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; @@ -26,8 +33,8 @@ import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.ResultPair; -import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; import org.junit.Test; public class DialogManagerTests { @@ -157,8 +164,7 @@ public void DialogManager_UserState_PersistedAcrossConversations() { } @Test - public void - DialogManager_UserState_NestedDialogs_PersistedAcrossConversations() { + public void DialogManager_UserState_NestedDialogs_PersistedAcrossConversations() { String firstConversationId = UUID.randomUUID().toString(); String secondConversationId = UUID.randomUUID().toString(); MemoryStorage storage = new MemoryStorage(); @@ -330,81 +336,109 @@ public void DialogManager_UserState_PersistedAcrossConversations() { // Assert.DoesNotContain(dm.Dialogs.GetDialogs(), d -> d.GetType() == typeof(SendActivity)); // } - // public CompletableFuture HandlesBotAndSkillsTestCases(SkillFlowTestCase testCase, boolean shouldSendEoc) { - // var firstConversationId = Guid.NewGuid().toString(); - // var storage = new MemoryStorage(); + @Test + public void HandleBotAndSkillsTestsCases_RootBotOnly() { + HandlesBotAndSkillsTestCases(SkillFlowTestCase.RootBotOnly, false); + } - // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); - // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: testCase, locale: "en-GB").send("Hi") - // .assertReply("Hello, what is your name?") - // .send("SomeName") - // .assertReply("Hello SomeName, nice to meet you!") - // .startTest(); + @Test + public void HandleBotAndSkillsTestsCases_RootBotConsumingSkill() { + HandlesBotAndSkillsTestCases(SkillFlowTestCase.RootBotConsumingSkill, false); + } - // Assert.Equal(DialogTurnStatus.Complete, _dmTurnResult.TurnResult.Status); + @Test + public void HandleBotAndSkillsTestsCases_MiddleSkill() { + HandlesBotAndSkillsTestCases(SkillFlowTestCase.MiddleSkill, true); + } - // if (shouldSendEoc) { - // Assert.NotNull(_eocSent); - // Assert.Equal(ActivityTypes.EndOfConversation, _eocSent.Type); - // Assert.Equal("SomeName", _eocSent.Value); - // Assert.Equal("en-GB", _eocSent.Locale); - // } else { - // Assert.Null(_eocSent); - // } - // } + @Test + public void HandleBotAndSkillsTestsCases_LeafSkill() { + HandlesBotAndSkillsTestCases(SkillFlowTestCase.LeafSkill, true); + } - // @Test - // public CompletableFuture SkillHandlesEoCFromParent() { - // var firstConversationId = Guid.NewGuid().toString(); - // var storage = new MemoryStorage(); - // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + public void HandlesBotAndSkillsTestCases(SkillFlowTestCase testCase, boolean shouldSendEoc) { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); - // var eocActivity = new Activity(ActivityTypes.EndOfConversation); + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + CreateFlow(adaptiveDialog, storage, firstConversationId, null, testCase, "en-GB") + .send("Hi") + .assertReply("Hello, what is your name?") + .send("SomeName") + .assertReply("Hello SomeName, nice to meet you!") + .startTest() + .join(); + + Assert.assertEquals(DialogTurnStatus.COMPLETE, _dmTurnResult.getTurnResult().getStatus()); + + if (shouldSendEoc) { + Assert.assertNotNull(_eocSent); + Assert.assertEquals(ActivityTypes.END_OF_CONVERSATION, _eocSent.getType()); + Assert.assertEquals("SomeName", _eocSent.getValue()); + Assert.assertEquals("en-GB", _eocSent.getLocale()); + } else { + Assert.assertNull(_eocSent); + } + } - // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) - // .send("hi") - // .assertReply("Hello, what is your name?") - // .send(eocActivity) - // .startTest(); + @Test + public void SkillHandlesEoCFromParent() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); - // Assert.Equal(DialogTurnStatus.Cancelled, _dmTurnResult.TurnResult.Status); - // } + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); - // @Test - // public CompletableFuture SkillHandlesRepromptFromParent() { - // var firstConversationId = Guid.NewGuid().toString(); - // var storage = new MemoryStorage(); + Activity eocActivity = new Activity(ActivityTypes.END_OF_CONVERSATION); - // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + CreateFlow(adaptiveDialog, storage, firstConversationId, null, SkillFlowTestCase.LeafSkill, null) + .send("hi") + .assertReply("Hello, what is your name?") + .send(eocActivity) + .startTest() + .join(); - // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + Assert.assertEquals(DialogTurnStatus.CANCELLED, _dmTurnResult.getTurnResult().getStatus()); + } - // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) - // .send("hi") - // .assertReply("Hello, what is your name?") - // .send(repromptEvent) - // .assertReply("Hello, what is your name?") - // .startTest(); + @Test + public void SkillHandlesRepromptFromParent() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); - // Assert.Equal(DialogTurnStatus.Waiting, _dmTurnResult.TurnResult.Status); - // } + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); - // @Test - // public CompletableFuture SkillShouldReturnEmptyOnRepromptWithNoDialog() { - // var firstConversationId = Guid.NewGuid().toString(); - // var storage = new MemoryStorage(); + Activity repromptEvent = new Activity(ActivityTypes.EVENT); + repromptEvent.setName(DialogEvents.REPROMPT_DIALOG); - // var adaptiveDialog = CreateTestDialog(property: "conversation.name"); + CreateFlow(adaptiveDialog, storage, firstConversationId, null, SkillFlowTestCase.LeafSkill, null) + .send("hi") + .assertReply("Hello, what is your name?") + .send(repromptEvent) + .assertReply("Hello, what is your name?") + .startTest() + .join(); - // var repromptEvent = new Activity(ActivityTypes.Event) { Name = DialogEvents.RepromptDialog }; + Assert.assertEquals(DialogTurnStatus.WAITING, _dmTurnResult.getTurnResult().getStatus()); + } - // CreateFlow(adaptiveDialog, storage, firstConversationId, testCase: SkillFlowTestCase.LeafSkill) - // .send(repromptEvent) - // .startTest(); + @Test + public void SkillShouldReturnEmptyOnRepromptWithNoDialog() { + String firstConversationId = UUID.randomUUID().toString(); + MemoryStorage storage = new MemoryStorage(); - // Assert.Equal(DialogTurnStatus.Empty, _dmTurnResult.TurnResult.Status); - // } + Dialog adaptiveDialog = CreateTestDialog("conversation.name"); + + Activity repromptEvent = new Activity(ActivityTypes.EVENT); + repromptEvent.setName(DialogEvents.REPROMPT_DIALOG); + + CreateFlow(adaptiveDialog, storage, firstConversationId, null, SkillFlowTestCase.LeafSkill, null) + .send(repromptEvent) + .startTest() + .join(); + + Assert.assertEquals(DialogTurnStatus.EMPTY, _dmTurnResult.getTurnResult().getStatus()); + } private Dialog CreateTestDialog(String property) { return new AskForNameDialog(property.replace(".", ""), property); @@ -435,31 +469,35 @@ private TestFlow CreateFlow(Dialog dialog, Storage storage, String conversationI DialogManager dm = new DialogManager(dialog, dialogStateProperty); return new TestFlow(adapter, (turnContext) -> { if (finalTestCase != SkillFlowTestCase.RootBotOnly) { - throw new NotImplementedException("CreateFlow is only capable of RootBotOnly test for now."); // Create a skill ClaimsIdentity and put it in TurnState so SkillValidation.IsSkillClaim() returns true. - // ClaimsIdentity claimsIdentity = new ClaimsIdentity(); - // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.VersionClaim, "2.0")); - // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AudienceClaim, _skillBotId)); - // claimsIdentity.AddClaim(new Claim(AuthenticationConstants.AuthorizedParty, _parentBotId)); - // turnContext.TurnState.Add(BotAdapter.BotIdentityKey, claimsIdentity); - - // if (testCase == SkillFlowTestCase.RootBotConsumingSkill) { - // // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. - // // This emulates a response coming to a root bot through SkillHandler. - // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = AuthenticationConstants.ToChannelFromBotOAuthScope }); - // } - - // if (testCase == SkillFlowTestCase.MiddleSkill) { - // // Simulate the SkillConversationReference with a parent Bot D stored in TurnState. - // // This emulates a response coming to a skill from another skill through SkillHandler. - // turnContext.TurnState.Add(SkillHandler.SkillConversationReferenceKey, new SkillConversationReference { OAuthScope = _parentBotId }); - // } + Map claims = new HashMap(); + claims.put(AuthenticationConstants.VERSION_CLAIM, "2.0"); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, _skillBotId); + claims.put(AuthenticationConstants.AUTHORIZED_PARTY, _parentBotId); + ClaimsIdentity claimsIdentity = new ClaimsIdentity("testIssuer", claims); + turnContext.getTurnState().add(BotAdapter.BOT_IDENTITY_KEY, claimsIdentity); + + if (finalTestCase == SkillFlowTestCase.RootBotConsumingSkill) { + // Simulate the SkillConversationReference with a channel OAuthScope stored in TurnState. + // This emulates a response coming to a root bot through SkillHandler. + SkillConversationReference reference = new SkillConversationReference(); + reference.setOAuthScope(AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE); + turnContext.getTurnState().add(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY, reference); + } + + if (finalTestCase == SkillFlowTestCase.MiddleSkill) { + // Simulate the SkillConversationReference with a parent Bot D stored in TurnState. + // This emulates a response coming to a skill from another skill through SkillHandler. + SkillConversationReference reference = new SkillConversationReference(); + reference.setOAuthScope(_parentBotId); + turnContext.getTurnState().add(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY, reference); + } } turnContext.onSendActivities(new TestSendActivities()); // Capture the last DialogManager turn result for assertions. - _dmTurnResult = dm.onTurn(turnContext).join(); + _dmTurnResult = dm.onTurn(turnContext).join(); return CompletableFuture.completedFuture(null); }); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java index c4ca44438..3a42ba28b 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ConfirmPromptLocTests.java @@ -141,6 +141,7 @@ public void ConfirmPrompt_Locale_Override_ChoiceDefaults(String defaultLocale, S // Prompt should default to English if locale is a non-supported value dialogs.add(new ConfirmPrompt("ConfirmPrompt", customDict, null, defaultLocale)); new TestFlow(adapter, (turnContext) -> { + turnContext.getActivity().setLocale(culture.getLocale()); DialogContext dc = dialogs.createContext(turnContext).join(); DialogTurnResult results = dc.continueDialog().join(); From 2574aa61f5b12a489b822496f96fb6bfc3b826c5 Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Fri, 12 Mar 2021 16:48:15 -0300 Subject: [PATCH 100/221] [Samples] Add 25.message-reaction sample (#1055) * Add pom for sample * Add deploymentTemplates * Add webapp * Add teamsAppManifest folder * Add application.properties file * Add README * Migrate main code of message reaction sample * Add empty test * Remove double braces for createMessageActivity method due to problem of mapping * Populate empty test --- .../com/microsoft/bot/schema/Activity.java | 10 +- samples/25.message-reaction/README.md | 77 +++++ .../template-with-new-rg.json | 291 +++++++++++++++++ .../template-with-preexisting-rg.json | 259 ++++++++++++++++ samples/25.message-reaction/pom.xml | 238 ++++++++++++++ .../sample/messagereaction/ActivityLog.java | 64 ++++ .../sample/messagereaction/Application.java | 72 +++++ .../messagereaction/MessageReactionBot.java | 110 +++++++ .../sample/messagereaction/package-info.java | 8 + .../src/main/resources/application.properties | 3 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 293 ++++++++++++++++++ .../messagereaction/ApplicationTest.java | 20 ++ .../teamsAppManifest/icon-color.png | Bin 0 -> 1229 bytes .../teamsAppManifest/icon-outline.png | Bin 0 -> 383 bytes .../teamsAppManifest/manifest.json | 43 +++ 17 files changed, 1497 insertions(+), 6 deletions(-) create mode 100644 samples/25.message-reaction/README.md create mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/25.message-reaction/pom.xml create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java create mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java create mode 100644 samples/25.message-reaction/src/main/resources/application.properties create mode 100644 samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/25.message-reaction/src/main/webapp/index.html create mode 100644 samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java create mode 100644 samples/25.message-reaction/teamsAppManifest/icon-color.png create mode 100644 samples/25.message-reaction/teamsAppManifest/icon-outline.png create mode 100644 samples/25.message-reaction/teamsAppManifest/manifest.json diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index 560c41d99..6acaa8ff7 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -273,12 +273,10 @@ public static Activity createTraceActivity( * @return A message Activity type. */ public static Activity createMessageActivity() { - return new Activity(ActivityTypes.MESSAGE) { - { - setAttachments(new ArrayList<>()); - setEntities(new ArrayList<>()); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setAttachments(new ArrayList<>()); + activity.setEntities(new ArrayList<>()); + return activity; } /** diff --git a/samples/25.message-reaction/README.md b/samples/25.message-reaction/README.md new file mode 100644 index 000000000..b08ad6fa7 --- /dev/null +++ b/samples/25.message-reaction/README.md @@ -0,0 +1,77 @@ +## Message Reactions Bot + +Bot Framework [message reactions](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/subscribe-to-conversation-events?tabs=dotnet#message-reaction-events) bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that responds to Message Reactions. + +## Prerequisites + +This samples **requires** prerequisites in order to run. + +### Overview + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. +- Microsoft Teams is installed and you have an account +- [ngrok](https://ngrok.com/) or equivalent tunnelling solution + +## To try this sample + +> Note these instructions are for running the sample on your local machine, the tunnelling solution is required because +the Teams service needs to call into the bot. + +1) Clone the repository + + ```bash + git clone https://github.com/Microsoft/botbuilder-java.git + ``` + +1) Run ngrok - point to port 3978 + + ```bash + ngrok http -host-header=rewrite 3978 + ``` + +1) Create [Bot Framework registration resource](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-quickstart-registration) in Azure + - Use the current `https` URL you were given by running ngrok. Append with the path `/api/messages` used by this sample + - Ensure that you've [enabled the Teams Channel](https://docs.microsoft.com/en-us/azure/bot-service/channel-connect-teams?view=azure-bot-service-4.0) + - __*If you don't have an Azure account*__ you can use this [Bot Framework registration](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/create-a-bot-for-teams#register-your-web-service-with-the-bot-framework) + +1) Update the `resources/application.properties` configuration for the bot to use the Microsoft App Id and App Password from the Bot Framework registration. (Note the App Password is referred to as the "client secret" in the azure portal and you can always create a new client secret anytime.) + +1) Update `CustomForm.html` to replace your Microsoft App Id *everywhere* you see the place holder string `<>` + +1) __*This step is specific to Teams.*__ + - **Edit** the `manifest.json` contained in the `teamsAppManifest` folder to replace your Microsoft App Id (that was created when you registered your bot earlier) *everywhere* you see the place holder string `<>` (depending on the scenario the Microsoft App Id may occur multiple times in the `manifest.json`). **Note:** the Task Modules containing pages will require the deployed bot's domain in validDomains of the manifest. + - **Zip** up the contents of the `teamsAppManifest` folder to create a `manifest.zip` + - **Upload** the `manifest.zip` to Teams (in the Apps view click "Upload a custom app") + +1) From the root of this project folder: + - Build the sample using `mvn package` + - Unless done previously, install the packages in the local cache by using `mvn install` + - Run it by using `java -jar .\target\bot-messagereaction-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot in Teams + +Message the bot and it will respond with an 'Echo: [your message]'. Add a message reaction to the bots response, and the bot will reply accordingly. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Teams Message Reaction Events](https://docs.microsoft.com/en-us/microsoftteams/platform/bots/how-to/conversations/subscribe-to-conversation-events?tabs=dotnet#message-reaction-events) diff --git a/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json b/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json b/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/25.message-reaction/pom.xml b/samples/25.message-reaction/pom.xml new file mode 100644 index 000000000..e8ac589ed --- /dev/null +++ b/samples/25.message-reaction/pom.xml @@ -0,0 +1,238 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-messagereaction + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Message-reaction Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.messagereaction.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.messagereaction.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java new file mode 100644 index 000000000..f1d1ca5c1 --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.schema.Activity; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * `Activity`'s class with a Storage provider. + */ +public class ActivityLog { + + private Storage storage; + + /** + * Initializes a new instance of the [ActivityLog](xref:reactions-bot.ActivityLog) class. + * + * @param withStorage A storage provider that stores and retrieves plain old JSON objects. + */ + public ActivityLog(Storage withStorage) { + storage = withStorage; + } + + /** + * Saves an {@link Activity} with its associated id into the storage. + * + * @param activityId {@link Activity}'s Id. + * @param activity The {@link Activity} object. + * @return A CompletableFuture + */ + public CompletableFuture append(String activityId, Activity activity) { + if (activityId == null) { + throw new IllegalArgumentException("activityId"); + } + + if (activity == null) { + throw new IllegalArgumentException("activity"); + } + + Map dictionary = new HashMap(); + dictionary.put(activityId, activity); + return storage.write((Map) dictionary); + } + + /** + * Retrieves an {@link Activity} from the storage by a given Id. + * + * @param activityId {@link Activity}'s Id. + * @return The {@link Activity}'s object retrieved from storage. + */ + public CompletableFuture find(String activityId) { + if (activityId == null) { + throw new IllegalArgumentException("activityId"); + } + + return storage.read(new String[]{activityId}) + .thenApply(activitiesResult -> + activitiesResult.size() >= 1 ? ((Activity) activitiesResult.get(activityId)) : null); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java new file mode 100644 index 000000000..878deca5d --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java @@ -0,0 +1,72 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + /** + * The start method. + * + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method with the + * @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(Storage storage) { + ActivityLog log = new ActivityLog(storage); + return new MessageReactionBot(log); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java new file mode 100644 index 000000000..9794d4ab5 --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java @@ -0,0 +1,110 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.MessageReaction; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * From the UI you need to @mention the bot, then add a message reaction to the message the bot sent. + */ +public class MessageReactionBot extends ActivityHandler { + + private final ActivityLog log; + + /** + * Initializes a new instance of the {@link MessageReactionBot} class. + * + * @param withLog The {@link ActivityLog}. + */ + public MessageReactionBot(ActivityLog withLog) { + log = withLog; + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + return sendMessageAndLogActivityId(turnContext, String.format("echo: %s", turnContext.getActivity().getText())); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onReactionsAdded( + List messageReactions, + TurnContext turnContext + ) { + for (MessageReaction reaction : messageReactions) { + // The ReplyToId property of the inbound MessageReaction Activity will correspond to a Message Activity + // which had previously been sent from this bot. + log.find(turnContext.getActivity().getReplyToId()).thenCompose(resultActivity -> { + if (resultActivity == null) { + // If we had sent the message from the error handler + // we wouldn't have recorded the Activity Id and so we shouldn't expect to see it in the log. + return sendMessageAndLogActivityId( + turnContext, + String.format("Activity %s not found in the log.", turnContext.getActivity().getReplyToId())); + } + + return sendMessageAndLogActivityId( + turnContext, + String.format("You added '%s' regarding '%s'", reaction.getType(), resultActivity.getText())); + }); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onReactionsRemoved( + List messageReactions, + TurnContext turnContext + ) { + for (MessageReaction reaction : messageReactions) { + // The ReplyToId property of the inbound MessageReaction Activity will correspond to a Message Activity + // which was previously sent from this bot. + log.find(turnContext.getActivity().getReplyToId()).thenCompose(resultActivity -> { + if (resultActivity == null) { + // If we had sent the message from the error handler + // we wouldn't have recorded the Activity Id and so we shouldn't expect to see it in the log. + return sendMessageAndLogActivityId( + turnContext, + String.format("Activity %s not found in the log.", turnContext.getActivity().getReplyToId())); + } + + return sendMessageAndLogActivityId( + turnContext, + String.format("You removed '%s' regarding '%s'", reaction.getType(), resultActivity.getText())); + }); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Sends an activity reply. + * + * @param context The {@link TurnContext}. + * @param text The {@link Activity}'s text. + */ + private CompletableFuture sendMessageAndLogActivityId(TurnContext turnContext, String text) { + // We need to record the Activity Id from the Activity just sent + // in order to understand what the reaction is a reaction too. + Activity replyActivity = MessageFactory.text(text); + return turnContext.sendActivity(replyActivity) + .thenCompose(resourceResponse -> log.append(resourceResponse.getId(), replyActivity) + .thenApply(result -> null)); + } +} diff --git a/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java new file mode 100644 index 000000000..9fb1b900b --- /dev/null +++ b/samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the message reaction sample. + */ +package com.microsoft.bot.sample.messagereaction; diff --git a/samples/25.message-reaction/src/main/resources/application.properties b/samples/25.message-reaction/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/25.message-reaction/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF b/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml b/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/25.message-reaction/src/main/webapp/index.html b/samples/25.message-reaction/src/main/webapp/index.html new file mode 100644 index 000000000..fe10f9026 --- /dev/null +++ b/samples/25.message-reaction/src/main/webapp/index.html @@ -0,0 +1,293 @@ + + + + + + + Teams Message Reaction Bot + + + + + +
+
+
+
Teams Message Reaction Bot
+
+
+
+
+
Your bot is ready!
+
+ You can now test your bot in Teams.
+
+ Visit + Azure + Bot Service + to register your bot and add it to the
+ Teams channel. The bot's endpoint URL typically looks + like this: +
+
https://your_bots_hostname/api/messages
+
+
+
+ +
+
+ + + diff --git a/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java b/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java new file mode 100644 index 000000000..d2e86a457 --- /dev/null +++ b/samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.messagereaction; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} + diff --git a/samples/25.message-reaction/teamsAppManifest/icon-color.png b/samples/25.message-reaction/teamsAppManifest/icon-color.png new file mode 100644 index 0000000000000000000000000000000000000000..48a2de13303e1e8a25f76391f4a34c7c4700fd3d GIT binary patch literal 1229 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7OGojKx9jP7LeL$-D$|SkfJR9T^xl z_H+M9WCe1|JzX3_D&pSWuFnWfl{x;g|9jrEYf8Vqrkk2Ba|%ol3OT){=#|7ID~|e{ zODQ{kU&ME#@`*-tm%Tukt_gFr+`F?$dx9wg-jad`^gsMn2_%Kh%WH91&SjKq5 zgkdI|!exdOVgw@>>=!Tjnk6q)zV*T8$FdgRFYC{kQ7``NOcl@R(_%_8e5e0E;>v0G zEM9kb)2itgOTSfH7M=b3-S61B?PiazMdwXZwrS)^5UUS#HQjaoua5h_{Gx*_Zz|XK z$tf0mZ&=tpf2!!Q)!A_l&o_$g*|JM$VZa~F^0{x1T{=QFu*x$`=V%~jUW=G`iqqp=lquB-`P{Qjw`=zEu3cMc_x7m2f#9m}uoFBMMQ^+%cOL)F_)N@JZ}Axoxi1y= zeebq`y==e!nl+?cK-PhOec!3%|IupShHrcjW8sSt)F1>NW*{ zW%ljk2)nk%-}+F&?gi=7^$L#VeX3@kp%f{n}fR z`}uZPx$IY~r8R5%gMlrc`jP!L3IloKFoq~sFFH5|cdklX=R08T)}71BhaN8$`AsNf0_ zq>WNhAtCd|-nBlTU=y5zl_vXlXZ~bkuaYENMp>3QSQ_#zuYZ+eQh*OIHRxP~s(}ic zN2J4$u=AQcPt)|>F3zZLsjtP;Tajkugx;NcYED2~JVBlVO>{`uAY?Q4O|AA z=16}CJieK^5P_TKnou!zGR`$!PUC)DqtkO;?!`p!+9v3lP_mu=%Vt3BkoWsq%;FN1sp58w*zfr-z^7tIb*q>!yncCjrzLuOk3N+d&~^Cxd| z>", + "packageName": "com.teams.sample.messageReaction", + "developer": { + "name": "MessageReactionBot", + "websiteUrl": "https://www.microsoft.com", + "privacyUrl": "https://www.teams.com/privacy", + "termsOfUseUrl": "https://www.teams.com/termsofuser" + }, + "icons": { + "color": "icon-color.png", + "outline": "icon-outline.png" + }, + "name": { + "short": "MessageReactionBot", + "full": "MessageReactionBot" + }, + "description": { + "short": "MessageReactionBot", + "full": "MessageReactionBot" + }, + "accentColor": "#FFFFFF", + "bots": [ + { + "botId": "<>", + "scopes": [ + "groupchat", + "team", + "personal" + ], + "supportsFiles": false, + "isNotificationOnly": false + } + ], + "permissions": [ + "identity", + "messageTeamMembers" + ], + "validDomains": [] +} From 06c3fdc0edae14d112ce9f054d6515142fa09951 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 12 Mar 2021 14:35:24 -0600 Subject: [PATCH 101/221] Added sendEmulateOAuthCards (#1053) * Added sendEmulateOAuthCards * Removed unused imports Co-authored-by: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> --- .../bot/builder/BotFrameworkAdapter.java | 43 +++++++++++-------- .../bot/connector/OAuthClientConfig.java | 18 -------- .../microsoft/bot/connector/UserToken.java | 8 ++++ .../bot/connector/rest/RestUserToken.java | 42 ++++++++++++++++++ 4 files changed, 75 insertions(+), 36 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index e8b309f5f..4500b90fb 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -49,6 +49,7 @@ import com.microsoft.bot.schema.TokenStatus; import com.microsoft.bot.restclient.retry.RetryStrategy; import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; import org.apache.commons.lang3.StringUtils; import java.net.HttpURLConnection; @@ -1069,41 +1070,47 @@ public CompletableFuture createConversation(String channelId, String servi * If null, the default credentials will be used. * @return An OAuth client for the bot. */ - protected CompletableFuture createOAuthAPIClient(TurnContext turnContext, - AppCredentials oAuthAppCredentials) { + protected CompletableFuture createOAuthAPIClient( + TurnContext turnContext, + AppCredentials oAuthAppCredentials + ) { if (!OAuthClientConfig.emulateOAuthCards && StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.EMULATOR) - && credentialProvider.isAuthenticationDisabled().join()) { + && credentialProvider.isAuthenticationDisabled().join() + ) { OAuthClientConfig.emulateOAuthCards = true; } + AtomicBoolean sendEmulateOAuthCards = new AtomicBoolean(false); String appId = getBotAppId(turnContext); String cacheKey = appId + (oAuthAppCredentials != null ? oAuthAppCredentials.getAppId() : ""); - String oAuthScope = getBotFrameworkOAuthScope(); - AppCredentials credentials = oAuthAppCredentials != null ? oAuthAppCredentials - : getAppCredentials(appId, oAuthScope).join(); OAuthClient client = oAuthClients.computeIfAbsent(cacheKey, key -> { - OAuthClient oAuthClient = new RestOAuthClient( - OAuthClientConfig.emulateOAuthCards ? turnContext.getActivity().getServiceUrl() - : OAuthClientConfig.OAUTHENDPOINT, - credentials); - - if (OAuthClientConfig.emulateOAuthCards) { - // do not join task - we want this to run in the background. - OAuthClientConfig.sendEmulateOAuthCards(oAuthClient, OAuthClientConfig.emulateOAuthCards); - } + sendEmulateOAuthCards.set(OAuthClientConfig.emulateOAuthCards); + + String oAuthScope = getBotFrameworkOAuthScope(); + AppCredentials credentials = oAuthAppCredentials != null + ? oAuthAppCredentials + : getAppCredentials(appId, oAuthScope).join(); - return oAuthClient; + return new RestOAuthClient( + OAuthClientConfig.emulateOAuthCards + ? turnContext.getActivity().getServiceUrl() + : OAuthClientConfig.OAUTHENDPOINT, + credentials + ); }); // adding the oAuthClient into the TurnState - // TokenResolver.cs will use it get the correct credentials to poll for - // token for streaming scenario if (turnContext.getTurnState().get(BotAdapter.OAUTH_CLIENT_KEY) == null) { turnContext.getTurnState().add(BotAdapter.OAUTH_CLIENT_KEY, client); } + if (sendEmulateOAuthCards.get()) { + return client.getUserToken().sendEmulateOAuthCards(true) + .thenApply(voidresult -> client); + } + return CompletableFuture.completedFuture(client); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java index ae70776f2..e6dfc0d5e 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/OAuthClientConfig.java @@ -4,9 +4,6 @@ package com.microsoft.bot.connector; import com.microsoft.bot.connector.authentication.AuthenticationConstants; -import org.apache.commons.lang3.NotImplementedException; - -import java.util.concurrent.CompletableFuture; /** * OAuthClient config. @@ -27,19 +24,4 @@ private OAuthClientConfig() { */ @SuppressWarnings("checkstyle:VisibilityModifier") public static boolean emulateOAuthCards = false; - - /** - * Send a dummy OAuth card when the bot is being used on the Emulator for - * testing without fetching a real token. - * - * @param client The OAuth client. - * @param emulate Indicates whether the Emulator should emulate the OAuth card. - * @return A task that represents the work queued to execute. - */ - public static CompletableFuture sendEmulateOAuthCards( - OAuthClient client, - boolean emulate - ) { - throw new NotImplementedException("sendEmulateOAuthCards"); - } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java index 6422a030e..4b9686984 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/UserToken.java @@ -129,4 +129,12 @@ CompletableFuture> getTokenStatus( String channelId, String include ); + + /** + * Send a dummy OAuth card when the bot is being used on the Emulator for testing without fetching a real token. + * + * @param emulateOAuthCards Indicates whether the Emulator should emulate the OAuth card. + * @return A task that represents the work queued to execute. + */ + CompletableFuture sendEmulateOAuthCards(boolean emulateOAuthCards); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java index bb7c7c27c..e7a42dbf0 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestUserToken.java @@ -112,6 +112,13 @@ CompletableFuture> getTokenStatus( @Query("channelId") String channelId, @Query("include") String include ); + + @Headers({ "Content-Type: application/json; charset=utf-8", + "x-ms-logging-context: com.microsoft.bot.schema.UserTokens sendEmulateOAuthCards" }) + @POST("api/usertoken/emulateOAuthCards") + CompletableFuture> sendEmulateOAuthCards( + @Query("emulate") boolean emulate + ); } /** @@ -532,4 +539,39 @@ private ServiceResponse> getTokenStatusDelegate( .registerError(ErrorResponseException.class) .build(response); } + + /** + * Send a dummy OAuth card when the bot is being used on the Emulator for testing without fetching a real token. + * + * @param emulateOAuthCards Indicates whether the Emulator should emulate the OAuth card. + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture sendEmulateOAuthCards(boolean emulateOAuthCards) { + return service.sendEmulateOAuthCards(emulateOAuthCards) + .thenApply(responseBodyResponse -> { + try { + return sendEmulateOAuthCardsDelegate(responseBodyResponse).body(); + } catch (ErrorResponseException e) { + throw e; + } catch (Throwable t) { + throw new ErrorResponseException("sendEmulateOAuthCards", responseBodyResponse); + } + }); + } + + private ServiceResponse sendEmulateOAuthCardsDelegate( + Response response + ) throws ErrorResponseException, IOException, IllegalArgumentException { + + return client.restClient() + .responseBuilderFactory() + .newInstance(client.serializerAdapter()) + .register(HttpURLConnection.HTTP_OK, new TypeToken() { + }.getType()) + .register(HttpURLConnection.HTTP_ACCEPTED, new TypeToken() { + }.getType()) + .registerError(ErrorResponseException.class) + .build(response); + } } From 47777086d50f7c362042fe416f32e70987714e8a Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 15 Mar 2021 10:15:26 -0500 Subject: [PATCH 102/221] TranscriptLoggerMiddleware set turnContext.Activity.From.Role (#1057) --- .../builder/TranscriptLoggerMiddleware.java | 19 ++++++++++++++- .../bot/builder/TranscriptMiddlewareTest.java | 2 +- .../bot/schema/ActivityEventNames.java | 24 +++++++++++++++++++ 3 files changed, 43 insertions(+), 2 deletions(-) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityEventNames.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java index e3ea52a12..bf77ddb2c 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java @@ -4,6 +4,7 @@ package com.microsoft.bot.builder; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityEventNames; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.RoleTypes; @@ -13,6 +14,7 @@ import java.util.Queue; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentLinkedQueue; +import org.apache.commons.lang3.StringUtils; /** * When added, this middleware will log incoming and outgoing activities to a @@ -57,7 +59,22 @@ public TranscriptLoggerMiddleware(TranscriptLogger withTranscriptLogger) { public CompletableFuture onTurn(TurnContext context, NextDelegate next) { // log incoming activity at beginning of turn if (context.getActivity() != null) { - logActivity(Activity.clone(context.getActivity()), true); + if (context.getActivity().getFrom() == null) { + context.getActivity().setFrom(new ChannelAccount()); + } + + if (context.getActivity().getFrom().getProperties().get("role") == null + && context.getActivity().getFrom().getRole() == null + ) { + context.getActivity().getFrom().setRole(RoleTypes.USER); + } + + // We should not log ContinueConversation events used by skills to initialize the middleware. + if (!(context.getActivity().isType(ActivityTypes.EVENT) + && StringUtils.equals(context.getActivity().getName(), ActivityEventNames.CONTINUE_CONVERSATION)) + ) { + logActivity(Activity.clone(context.getActivity()), true); + } } // hook up onSend pipeline diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java index c1bfcd788..0945d80dd 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java @@ -282,7 +282,7 @@ public final void Transcript_RolesAreFilled() { // message. As demonstrated by the asserts after this TestFlow block // the role attribute is present on the activity as it is passed to // the transcript, but still missing inside the flow - Assert.assertNull(context.getActivity().getFrom().getRole()); + Assert.assertNotNull(context.getActivity().getFrom().getRole()); conversationId[0] = context.getActivity().getConversation().getId(); context.sendActivity("echo:" + context.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityEventNames.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityEventNames.java new file mode 100644 index 000000000..3c1e81494 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityEventNames.java @@ -0,0 +1,24 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +/** + * Define values for common event names used by activities of type + * ActivityTypes.Event. + */ +public final class ActivityEventNames { + private ActivityEventNames() { + + } + + /** + * The event name for continuing a conversation. + */ + public static final String CONTINUE_CONVERSATION = "ContinueConversation"; + + /** + * The event name for creating a conversation. + */ + public static final String CREATE_CONVERSATION = "CreateConversation"; +} From 44969e6b71e83e60cf8d49d14935a87a2e86320c Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Mon, 15 Mar 2021 10:53:21 -0500 Subject: [PATCH 103/221] Added DeliveryModes.EPHEMERAL (#1058) --- .../src/main/java/com/microsoft/bot/schema/Activity.java | 2 +- .../main/java/com/microsoft/bot/schema/DeliveryModes.java | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index 6acaa8ff7..5cdbe13a5 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -1000,7 +1000,7 @@ public void setImportance(String withImportance) { * A delivery hint to signal to the recipient alternate delivery paths for the * activity. *

- * The default delivery mode is \"default\". + * The default delivery mode is \"default\". See {@link DeliveryModes}. * * @return The delivery mode hint. */ diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java index b69d9768d..caae82ff6 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/DeliveryModes.java @@ -23,7 +23,12 @@ public enum DeliveryModes { /** * The value for expected replies delivery modes. */ - EXPECT_REPLIES("expectReplies"); + EXPECT_REPLIES("expectReplies"), + + /** + * The value for ephemeral delivery modes. + */ + EPHEMERAL("ephemeral"); /** * The actual serialized value for a DeliveryModes instance. From 3736b2656a1d8e5e7eac852e5d63a320714c137c Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Tue, 16 Mar 2021 08:24:17 -0500 Subject: [PATCH 104/221] Dialog.run refactor (#1059) * Refactor for Issue 1046 * Update to push the build again. --- .../bot/builder/BotFrameworkAdapter.java | 863 ++++++++++-------- .../com/microsoft/bot/dialogs/Dialog.java | 321 ++++--- .../microsoft/bot/dialogs/DialogManager.java | 210 +---- .../dialogs/memory/DialogStateManager.java | 13 +- .../bot/dialogs/DialogManagerTests.java | 2 +- 5 files changed, 706 insertions(+), 703 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 4500b90fb..fffef0b33 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -82,10 +82,8 @@ *

* {@link TurnContext} {@link Activity} {@link Bot} {@link Middleware} */ -public class BotFrameworkAdapter extends BotAdapter implements - AdapterIntegration, - UserTokenProvider, - ConnectorClientBuilder { +public class BotFrameworkAdapter extends BotAdapter + implements AdapterIntegration, UserTokenProvider, ConnectorClientBuilder { /** * Key to store InvokeResponse. */ @@ -157,10 +155,19 @@ public BotFrameworkAdapter(CredentialProvider withCredentialProvider) { * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter(CredentialProvider withCredentialProvider, ChannelProvider withChannelProvider, - RetryStrategy withRetryStrategy, Middleware withMiddleware) { - this(withCredentialProvider, new AuthenticationConfiguration(), withChannelProvider, withRetryStrategy, - withMiddleware); + public BotFrameworkAdapter( + CredentialProvider withCredentialProvider, + ChannelProvider withChannelProvider, + RetryStrategy withRetryStrategy, + Middleware withMiddleware + ) { + this( + withCredentialProvider, + new AuthenticationConfiguration(), + withChannelProvider, + withRetryStrategy, + withMiddleware + ); } /** @@ -173,8 +180,13 @@ public BotFrameworkAdapter(CredentialProvider withCredentialProvider, ChannelPro * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter(CredentialProvider withCredentialProvider, AuthenticationConfiguration withAuthConfig, - ChannelProvider withChannelProvider, RetryStrategy withRetryStrategy, Middleware withMiddleware) { + public BotFrameworkAdapter( + CredentialProvider withCredentialProvider, + AuthenticationConfiguration withAuthConfig, + ChannelProvider withChannelProvider, + RetryStrategy withRetryStrategy, + Middleware withMiddleware + ) { if (withCredentialProvider == null) { throw new IllegalArgumentException("CredentialProvider cannot be null"); } @@ -212,8 +224,13 @@ public BotFrameworkAdapter(CredentialProvider withCredentialProvider, Authentica * @param withRetryStrategy Retry policy for retrying HTTP operations. * @param withMiddleware The middleware to initially add to the adapter. */ - public BotFrameworkAdapter(AppCredentials withCredentials, AuthenticationConfiguration withAuthConfig, - ChannelProvider withChannelProvider, RetryStrategy withRetryStrategy, Middleware withMiddleware) { + public BotFrameworkAdapter( + AppCredentials withCredentials, + AuthenticationConfiguration withAuthConfig, + ChannelProvider withChannelProvider, + RetryStrategy withRetryStrategy, + Middleware withMiddleware + ) { if (withCredentials == null) { throw new IllegalArgumentException("credentials"); } @@ -268,8 +285,11 @@ public BotFrameworkAdapter(AppCredentials withCredentials, AuthenticationConfigu * @throws IllegalArgumentException botAppId, reference, or callback is null. */ @Override - public CompletableFuture continueConversation(String botAppId, ConversationReference reference, - BotCallbackHandler callback) { + public CompletableFuture continueConversation( + String botAppId, + ConversationReference reference, + BotCallbackHandler callback + ) { if (reference == null) { return Async.completeExceptionally(new IllegalArgumentException("reference")); } @@ -306,8 +326,11 @@ public CompletableFuture continueConversation(String botAppId, Conversatio * @param callback The method to call for the result bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture continueConversation(ClaimsIdentity claimsIdentity, ConversationReference reference, - BotCallbackHandler callback) { + public CompletableFuture continueConversation( + ClaimsIdentity claimsIdentity, + ConversationReference reference, + BotCallbackHandler callback + ) { return continueConversation(claimsIdentity, reference, getBotFrameworkOAuthScope(), callback); } @@ -327,8 +350,12 @@ public CompletableFuture continueConversation(ClaimsIdentity claimsIdentit * @param callback The method to call for the result bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture continueConversation(ClaimsIdentity claimsIdentity, ConversationReference reference, - String audience, BotCallbackHandler callback) { + public CompletableFuture continueConversation( + ClaimsIdentity claimsIdentity, + ConversationReference reference, + String audience, + BotCallbackHandler callback + ) { if (claimsIdentity == null) { return Async.completeExceptionally(new IllegalArgumentException("claimsIdentity")); } @@ -361,10 +388,10 @@ public CompletableFuture continueConversation(ClaimsIdentity claimsIdentit } return createConnectorClient(reference.getServiceUrl(), claimsIdentity, audience) - .thenCompose(connectorClient -> { - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - return runPipeline(context, callback); - }); + .thenCompose(connectorClient -> { + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + return runPipeline(context, callback); + }); }); } catch (Exception e) { pipelineResult.completeExceptionally(e); @@ -401,15 +428,18 @@ public BotFrameworkAdapter use(Middleware middleware) { * returned. * @throws IllegalArgumentException Activity is null. */ - public CompletableFuture processActivity(String authHeader, Activity activity, - BotCallbackHandler callback) { + public CompletableFuture processActivity( + String authHeader, + Activity activity, + BotCallbackHandler callback + ) { if (activity == null) { return Async.completeExceptionally(new IllegalArgumentException("Activity")); } return JwtTokenValidation - .authenticateRequest(activity, authHeader, credentialProvider, channelProvider, authConfiguration) - .thenCompose(claimsIdentity -> processActivity(claimsIdentity, activity, callback)); + .authenticateRequest(activity, authHeader, credentialProvider, channelProvider, authConfiguration) + .thenCompose(claimsIdentity -> processActivity(claimsIdentity, activity, callback)); } /** @@ -426,8 +456,11 @@ public CompletableFuture processActivity(String authHeader, Acti * returned. * @throws IllegalArgumentException Activity is null. */ - public CompletableFuture processActivity(ClaimsIdentity identity, Activity activity, - BotCallbackHandler callback) { + public CompletableFuture processActivity( + ClaimsIdentity identity, + Activity activity, + BotCallbackHandler callback + ) { if (activity == null) { return Async.completeExceptionally(new IllegalArgumentException("Activity")); } @@ -441,8 +474,8 @@ public CompletableFuture processActivity(ClaimsIdentity identity // The OAuthScope is also stored on the TurnState to get the correct // AppCredentials if fetching a token is required. String scope = SkillValidation.isSkillClaim(identity.claims()) - ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(identity.claims())) - : getBotFrameworkOAuthScope(); + ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(identity.claims())) + : getBotFrameworkOAuthScope(); context.getTurnState().add(OAUTH_SCOPE_KEY, scope); @@ -456,13 +489,16 @@ public CompletableFuture processActivity(ClaimsIdentity identity .thenCompose(result -> { // Handle ExpectedReplies scenarios where the all the activities have been // buffered and sent back at once in an invoke response. - if (DeliveryModes.fromString( - context.getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES + if ( + DeliveryModes + .fromString(context.getActivity().getDeliveryMode()) == DeliveryModes.EXPECT_REPLIES ) { - return CompletableFuture.completedFuture(new InvokeResponse( - HttpURLConnection.HTTP_OK, - new ExpectedReplies(context.getBufferedReplyActivities()) - )); + return CompletableFuture.completedFuture( + new InvokeResponse( + HttpURLConnection.HTTP_OK, + new ExpectedReplies(context.getBufferedReplyActivities()) + ) + ); } // Handle Invoke scenarios, which deviate from the request/response model in @@ -470,12 +506,10 @@ public CompletableFuture processActivity(ClaimsIdentity identity if (activity.isType(ActivityTypes.INVOKE)) { Activity invokeResponse = context.getTurnState().get(INVOKE_RESPONSE_KEY); if (invokeResponse == null) { - return CompletableFuture.completedFuture( - new InvokeResponse(HttpURLConnection.HTTP_NOT_IMPLEMENTED, null) - ); - } else { return CompletableFuture - .completedFuture((InvokeResponse) invokeResponse.getValue()); + .completedFuture(new InvokeResponse(HttpURLConnection.HTTP_NOT_IMPLEMENTED, null)); + } else { + return CompletableFuture.completedFuture((InvokeResponse) invokeResponse.getValue()); } } @@ -500,9 +534,11 @@ private CompletableFuture generateCallerId(ClaimsIdentity claimsIdentity // Is the activity from another bot? if (SkillValidation.isSkillClaim(claimsIdentity.claims())) { - return String.format("%s%s", - CallerIdConstants.BOT_TO_BOT_PREFIX, - JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())); + return String.format( + "%s%s", + CallerIdConstants.BOT_TO_BOT_PREFIX, + JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims()) + ); } // Is the activity from Public Azure? @@ -545,7 +581,8 @@ public CompletableFuture sendActivities(TurnContext context, if (activities.size() == 0) { return Async.completeExceptionally( - new IllegalArgumentException("Expecting one or more activities, but the array was empty.")); + new IllegalArgumentException("Expecting one or more activities, but the array was empty.") + ); } return CompletableFuture.supplyAsync(() -> { @@ -579,8 +616,10 @@ public CompletableFuture sendActivities(TurnContext context, context.getTurnState().add(INVOKE_RESPONSE_KEY, activity); // No need to create a response. One will be created below. response = null; - } else if (activity.isType(ActivityTypes.TRACE) - && !StringUtils.equals(activity.getChannelId(), Channels.EMULATOR)) { + } else if ( + activity.isType(ActivityTypes.TRACE) + && !StringUtils.equals(activity.getChannelId(), Channels.EMULATOR) + ) { // if it is a Trace activity we only send to the channel if it's the emulator. response = null; } else if (!StringUtils.isEmpty(activity.getReplyToId())) { @@ -645,8 +684,8 @@ public CompletableFuture updateActivity(TurnContext context, A @Override public CompletableFuture deleteActivity(TurnContext context, ConversationReference reference) { ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); - return connectorClient.getConversations().deleteActivity(reference.getConversation().getId(), - reference.getActivityId()); + return connectorClient.getConversations() + .deleteActivity(reference.getConversation().getId(), reference.getActivityId()); } /** @@ -658,13 +697,15 @@ public CompletableFuture deleteActivity(TurnContext context, ConversationR */ public CompletableFuture deleteConversationMember(TurnContextImpl context, String memberId) { if (context.getActivity().getConversation() == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.deleteConversationMember(): missing conversation")); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation") + ); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.deleteConversationMember(): missing conversation.id")); + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.deleteConversationMember(): missing conversation.id") + ); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -698,12 +739,14 @@ public CompletableFuture> getActivityMembers(TurnContextImp if (context.getActivity().getConversation() == null) { return Async.completeExceptionally( - new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation")); + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation") + ); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { return Async.completeExceptionally( - new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id")); + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id") + ); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -721,12 +764,14 @@ public CompletableFuture> getActivityMembers(TurnContextImp public CompletableFuture> getConversationMembers(TurnContextImpl context) { if (context.getActivity().getConversation() == null) { return Async.completeExceptionally( - new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation")); + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation") + ); } if (StringUtils.isEmpty(context.getActivity().getConversation().getId())) { return Async.completeExceptionally( - new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id")); + new IllegalArgumentException("BotFrameworkAdapter.GetActivityMembers(): missing conversation.id") + ); } ConnectorClient connectorClient = context.getTurnState().get(CONNECTOR_CLIENT_KEY); @@ -751,8 +796,10 @@ public CompletableFuture> getConversationMembers(TurnContex * conversation, as only the Bot's ServiceUrl and credentials are * required. */ - public CompletableFuture getConversations(String serviceUrl, - MicrosoftAppCredentials credentials) { + public CompletableFuture getConversations( + String serviceUrl, + MicrosoftAppCredentials credentials + ) { return getConversations(serviceUrl, credentials, null); } @@ -773,8 +820,11 @@ public CompletableFuture getConversations(String serviceUrl * results. * @return List of Members of the current conversation */ - public CompletableFuture getConversations(String serviceUrl, - MicrosoftAppCredentials credentials, String continuationToken) { + public CompletableFuture getConversations( + String serviceUrl, + MicrosoftAppCredentials credentials, + String continuationToken + ) { if (StringUtils.isEmpty(serviceUrl)) { return Async.completeExceptionally(new IllegalArgumentException("serviceUrl")); } @@ -784,7 +834,7 @@ public CompletableFuture getConversations(String serviceUrl } return getOrCreateConnectorClient(serviceUrl, credentials) - .thenCompose(connectorClient -> connectorClient.getConversations().getConversations(continuationToken)); + .thenCompose(connectorClient -> connectorClient.getConversations().getConversations(continuationToken)); } /** @@ -841,16 +891,23 @@ public CompletableFuture getUserToken(TurnContext context, String } if (context.getActivity().getFrom() == null || StringUtils.isEmpty(context.getActivity().getFrom().getId())) { return Async.completeExceptionally( - new IllegalArgumentException("BotFrameworkAdapter.getUserToken(): missing from or from.id")); + new IllegalArgumentException("BotFrameworkAdapter.getUserToken(): missing from or from.id") + ); } if (StringUtils.isEmpty(connectionName)) { return Async.completeExceptionally(new IllegalArgumentException("connectionName")); } - return createOAuthAPIClient(context, null) - .thenCompose(oAuthClient -> oAuthClient.getUserToken().getToken(context.getActivity().getFrom().getId(), - connectionName, context.getActivity().getChannelId(), magicCode)); + return createOAuthAPIClient(context, null).thenCompose( + oAuthClient -> oAuthClient.getUserToken() + .getToken( + context.getActivity().getFrom().getId(), + connectionName, + context.getActivity().getChannelId(), + magicCode + ) + ); } /** @@ -879,8 +936,12 @@ public CompletableFuture getOAuthSignInLink(TurnContext context, String * @return A task that represents the work queued to execute. */ @Override - public CompletableFuture getOAuthSignInLink(TurnContext context, String connectionName, String userId, - String finalRedirect) { + public CompletableFuture getOAuthSignInLink( + TurnContext context, + String connectionName, + String userId, + String finalRedirect + ) { return getOAuthSignInLink(context, null, connectionName, userId, finalRedirect); } @@ -909,8 +970,11 @@ public CompletableFuture signOutUser(TurnContext context, String connectio * @return Array of {@link TokenStatus}. */ @Override - public CompletableFuture> getTokenStatus(TurnContext context, String userId, - String includeFilter) { + public CompletableFuture> getTokenStatus( + TurnContext context, + String userId, + String includeFilter + ) { return getTokenStatus(context, null, userId, includeFilter); } @@ -929,8 +993,12 @@ public CompletableFuture> getTokenStatus(TurnContext context, * @return Map of resourceUrl to the corresponding {@link TokenResponse}. */ @Override - public CompletableFuture> getAadTokens(TurnContext context, String connectionName, - String[] resourceUrls, String userId) { + public CompletableFuture> getAadTokens( + TurnContext context, + String connectionName, + String[] resourceUrls, + String userId + ) { return getAadTokens(context, null, connectionName, resourceUrls, userId); } @@ -959,50 +1027,56 @@ public CompletableFuture> getAadTokens(TurnContext co * @param callback The method to call for the resulting bot turn. * @return A task that represents the work queued to execute. */ - public CompletableFuture createConversation(String channelId, String serviceUrl, - MicrosoftAppCredentials credentials, ConversationParameters conversationParameters, - BotCallbackHandler callback) { + public CompletableFuture createConversation( + String channelId, + String serviceUrl, + MicrosoftAppCredentials credentials, + ConversationParameters conversationParameters, + BotCallbackHandler callback + ) { return getOrCreateConnectorClient(serviceUrl, credentials).thenCompose(connectorClient -> { Conversations conversations = connectorClient.getConversations(); return conversations.createConversation(conversationParameters) - .thenCompose(conversationResourceResponse -> { - // Create a event activity to represent the result. - Activity eventActivity = Activity.createEventActivity(); - eventActivity.setName("CreateConversation"); - eventActivity.setChannelId(channelId); - eventActivity.setServiceUrl(serviceUrl); - eventActivity.setId((conversationResourceResponse.getActivityId() != null) - ? conversationResourceResponse.getActivityId() - : UUID.randomUUID().toString()); - eventActivity.setConversation(new ConversationAccount(conversationResourceResponse.getId()) { - { - setTenantId(conversationParameters.getTenantId()); - } - }); - eventActivity.setChannelData(conversationParameters.getChannelData()); - eventActivity.setRecipient(conversationParameters.getBot()); - - // run pipeline - CompletableFuture result = new CompletableFuture<>(); - try (TurnContextImpl context = new TurnContextImpl(this, eventActivity)) { - HashMap claims = new HashMap() { - { - put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - } - }; - ClaimsIdentity claimsIdentity = new ClaimsIdentity("anonymous", claims); - - context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - - result = runPipeline(context, callback); - } catch (Exception e) { - result.completeExceptionally(e); + .thenCompose(conversationResourceResponse -> { + // Create a event activity to represent the result. + Activity eventActivity = Activity.createEventActivity(); + eventActivity.setName("CreateConversation"); + eventActivity.setChannelId(channelId); + eventActivity.setServiceUrl(serviceUrl); + eventActivity.setId( + (conversationResourceResponse.getActivityId() != null) + ? conversationResourceResponse.getActivityId() + : UUID.randomUUID().toString() + ); + eventActivity.setConversation(new ConversationAccount(conversationResourceResponse.getId()) { + { + setTenantId(conversationParameters.getTenantId()); } - return result; }); + eventActivity.setChannelData(conversationParameters.getChannelData()); + eventActivity.setRecipient(conversationParameters.getBot()); + + // run pipeline + CompletableFuture result = new CompletableFuture<>(); + try (TurnContextImpl context = new TurnContextImpl(this, eventActivity)) { + HashMap claims = new HashMap() { + { + put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); + put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); + put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); + } + }; + ClaimsIdentity claimsIdentity = new ClaimsIdentity("anonymous", claims); + + context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + + result = runPipeline(context, callback); + } catch (Exception e) { + result.completeExceptionally(e); + } + return result; + }); }); } @@ -1034,9 +1108,14 @@ public CompletableFuture createConversation(String channelId, String servi * @return A task that represents the work queued to execute. */ @SuppressWarnings("checkstyle:InnerAssignment") - public CompletableFuture createConversation(String channelId, String serviceUrl, - MicrosoftAppCredentials credentials, ConversationParameters conversationParameters, - BotCallbackHandler callback, ConversationReference reference) { + public CompletableFuture createConversation( + String channelId, + String serviceUrl, + MicrosoftAppCredentials credentials, + ConversationParameters conversationParameters, + BotCallbackHandler callback, + ConversationReference reference + ) { if (reference.getConversation() == null) { return CompletableFuture.completedFuture(null); } @@ -1046,8 +1125,10 @@ public CompletableFuture createConversation(String channelId, String servi // Putting tenantId in channelData is a temporary solution while we wait for the // Teams API to be updated ObjectNode channelData = JsonNodeFactory.instance.objectNode(); - channelData.set("tenant", - JsonNodeFactory.instance.objectNode().set("tenantId", JsonNodeFactory.instance.textNode(tenantId))); + channelData.set( + "tenant", + JsonNodeFactory.instance.objectNode().set("tenantId", JsonNodeFactory.instance.textNode(tenantId)) + ); conversationParameters.setChannelData(channelData); @@ -1074,7 +1155,8 @@ protected CompletableFuture createOAuthAPIClient( TurnContext turnContext, AppCredentials oAuthAppCredentials ) { - if (!OAuthClientConfig.emulateOAuthCards + if ( + !OAuthClientConfig.emulateOAuthCards && StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.EMULATOR) && credentialProvider.isAuthenticationDisabled().join() ) { @@ -1089,9 +1171,8 @@ protected CompletableFuture createOAuthAPIClient( sendEmulateOAuthCards.set(OAuthClientConfig.emulateOAuthCards); String oAuthScope = getBotFrameworkOAuthScope(); - AppCredentials credentials = oAuthAppCredentials != null - ? oAuthAppCredentials - : getAppCredentials(appId, oAuthScope).join(); + AppCredentials credentials = + oAuthAppCredentials != null ? oAuthAppCredentials : getAppCredentials(appId, oAuthScope).join(); return new RestOAuthClient( OAuthClientConfig.emulateOAuthCards @@ -1107,8 +1188,7 @@ protected CompletableFuture createOAuthAPIClient( } if (sendEmulateOAuthCards.get()) { - return client.getUserToken().sendEmulateOAuthCards(true) - .thenApply(voidresult -> client); + return client.getUserToken().sendEmulateOAuthCards(true).thenApply(voidresult -> client); } return CompletableFuture.completedFuture(client); @@ -1126,12 +1206,17 @@ protected CompletableFuture createOAuthAPIClient( * authentication is turned off. */ @SuppressWarnings(value = "PMD") - public CompletableFuture createConnectorClient(String serviceUrl, - ClaimsIdentity claimsIdentity, - String audience) { + public CompletableFuture createConnectorClient( + String serviceUrl, + ClaimsIdentity claimsIdentity, + String audience + ) { if (claimsIdentity == null) { - return Async.completeExceptionally(new UnsupportedOperationException( - "ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off.")); + return Async.completeExceptionally( + new UnsupportedOperationException( + "ClaimsIdentity cannot be null. Pass Anonymous ClaimsIdentity if authentication is turned off." + ) + ); } // For requests from channel App Id is in Audience claim of JWT token. For @@ -1155,12 +1240,12 @@ public CompletableFuture createConnectorClient(String serviceUr if (StringUtils.isBlank(audience)) { scope = SkillValidation.isSkillClaim(claimsIdentity.claims()) - ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())) - : getBotFrameworkOAuthScope(); + ? String.format("%s/.default", JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims())) + : getBotFrameworkOAuthScope(); } return getAppCredentials(botAppIdClaim, scope) - .thenCompose(credentials -> getOrCreateConnectorClient(serviceUrl, credentials)); + .thenCompose(credentials -> getOrCreateConnectorClient(serviceUrl, credentials)); } return getOrCreateConnectorClient(serviceUrl); @@ -1182,24 +1267,28 @@ private CompletableFuture getOrCreateConnectorClient(String ser * @param usingAppCredentials (Optional) The AppCredentials to use. * @return A task that will return the ConnectorClient. */ - protected CompletableFuture getOrCreateConnectorClient(String serviceUrl, - AppCredentials usingAppCredentials) { + protected CompletableFuture getOrCreateConnectorClient( + String serviceUrl, + AppCredentials usingAppCredentials + ) { CompletableFuture result = new CompletableFuture<>(); - String clientKey = keyForConnectorClient(serviceUrl, - usingAppCredentials != null ? usingAppCredentials.getAppId() : null, - usingAppCredentials != null ? usingAppCredentials.oAuthScope() : null); + String clientKey = keyForConnectorClient( + serviceUrl, + usingAppCredentials != null ? usingAppCredentials.getAppId() : null, + usingAppCredentials != null ? usingAppCredentials.oAuthScope() : null + ); result.complete(connectorClients.computeIfAbsent(clientKey, key -> { try { RestConnectorClient connectorClient; if (usingAppCredentials != null) { - connectorClient = new RestConnectorClient(new URI(serviceUrl).toURL().toString(), - usingAppCredentials); + connectorClient = + new RestConnectorClient(new URI(serviceUrl).toURL().toString(), usingAppCredentials); } else { AppCredentials emptyCredentials = channelProvider != null && channelProvider.isGovernment() - ? MicrosoftGovernmentAppCredentials.empty() - : MicrosoftAppCredentials.empty(); + ? MicrosoftGovernmentAppCredentials.empty() + : MicrosoftAppCredentials.empty(); connectorClient = new RestConnectorClient(new URI(serviceUrl).toURL().toString(), emptyCredentials); } @@ -1210,7 +1299,8 @@ protected CompletableFuture getOrCreateConnectorClient(String s return connectorClient; } catch (Throwable t) { result.completeExceptionally( - new IllegalArgumentException(String.format("Invalid Service URL: %s", serviceUrl), t)); + new IllegalArgumentException(String.format("Invalid Service URL: %s", serviceUrl), t) + ); return null; } })); @@ -1243,11 +1333,10 @@ private CompletableFuture getAppCredentials(String appId, String } // Create a new AppCredentials and add it to the cache. - return buildAppCredentials(appId, scope) - .thenApply(credentials -> { - appCredentialMap.put(cacheKey, credentials); - return credentials; - }); + return buildAppCredentials(appId, scope).thenApply(credentials -> { + appCredentialMap.put(cacheKey, credentials); + return credentials; + }); } /** @@ -1282,8 +1371,8 @@ private String getBotAppId(TurnContext turnContext) throws IllegalStateException private String getBotFrameworkOAuthScope() { return channelProvider != null && channelProvider.isGovernment() - ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE - : AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + ? GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + : AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; } /** @@ -1322,17 +1411,22 @@ protected static String keyForConnectorClient(String serviceUrl, String appId, S private static class TenantIdWorkaroundForTeamsMiddleware implements Middleware { @Override public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { - if (StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.MSTEAMS) + if ( + StringUtils.equalsIgnoreCase(turnContext.getActivity().getChannelId(), Channels.MSTEAMS) && turnContext.getActivity().getConversation() != null - && StringUtils.isEmpty(turnContext.getActivity().getConversation().getTenantId())) { + && StringUtils.isEmpty(turnContext.getActivity().getConversation().getTenantId()) + ) { ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); JsonNode teamsChannelData = mapper.valueToTree(turnContext.getActivity().getChannelData()); - if (teamsChannelData != null && teamsChannelData.has("tenant") - && teamsChannelData.get("tenant").has("id")) { - - turnContext.getActivity().getConversation() - .setTenantId(teamsChannelData.get("tenant").get("id").asText()); + if ( + teamsChannelData != null && teamsChannelData.has("tenant") + && teamsChannelData.get("tenant").has("id") + ) { + + turnContext.getActivity() + .getConversation() + .setTenantId(teamsChannelData.get("tenant").get("id").asText()); } } @@ -1362,71 +1456,74 @@ protected Map getConnectorClientCache() { * Attempts to retrieve the token for a user that's in a login flow, using * customized AppCredentials. * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName Name of the auth connection to use. - * @param magicCode (Optional) Optional user entered code - * to validate. + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param magicCode (Optional) Optional user entered code to validate. * - * @return Token Response. + * @return Token Response. */ @Override - public CompletableFuture getUserToken(TurnContext context, AppCredentials oAuthAppCredentials, - String connectionName, String magicCode) { + public CompletableFuture getUserToken( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String magicCode + ) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } - if (context.getActivity().getFrom() == null - || StringUtils.isEmpty(context.getActivity().getFrom().getId())) { - return Async.completeExceptionally(new IllegalArgumentException( - "BotFrameworkAdapter.GetUserTokenAsync(): missing from or from.id" - )); + if (context.getActivity().getFrom() == null || StringUtils.isEmpty(context.getActivity().getFrom().getId())) { + return Async.completeExceptionally( + new IllegalArgumentException("BotFrameworkAdapter.GetUserTokenAsync(): missing from or from.id") + ); } if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException( - "connectionName cannot be null." - )); + return Async.completeExceptionally(new IllegalArgumentException("connectionName cannot be null.")); } return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(client -> { - return client.getUserToken().getToken( - context.getActivity().getFrom().getId(), - connectionName, - context.getActivity().getChannelId(), - magicCode); + return client.getUserToken() + .getToken( + context.getActivity().getFrom().getId(), + connectionName, + context.getActivity().getChannelId(), + magicCode + ); }); } /** - * Get the raw signin link to be sent to the user for signin for a - * connection name, using customized AppCredentials. + * Get the raw signin link to be sent to the user for signin for a connection + * name, using customized AppCredentials. * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName Name of the auth connection to use. + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. * - * @return A task that represents the work queued to execute. + * @return A task that represents the work queued to execute. * - * If the task completes successfully, the result contains the raw signin - * link. + * If the task completes successfully, the result contains the raw + * signin link. */ @Override - public CompletableFuture getOAuthSignInLink(TurnContext context, AppCredentials oAuthAppCredentials, - String connectionName) { + public CompletableFuture getOAuthSignInLink( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName + ) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } if (StringUtils.isEmpty(connectionName)) { - Async.completeExceptionally(new IllegalArgumentException( - "connectionName cannot be null." - )); + Async.completeExceptionally(new IllegalArgumentException("connectionName cannot be null.")); } return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { @@ -1463,84 +1560,90 @@ public CompletableFuture getOAuthSignInLink(TurnContext context, AppCred } /** - * Get the raw signin link to be sent to the user for signin for a - * connection name, using the bot's AppCredentials. + * Get the raw signin link to be sent to the user for signin for a connection + * name, using the bot's AppCredentials. * - * @param context Context for the current turn of - * conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param userId The user id that will be associated with - * the token. - * @param finalRedirect The final URL that the OAuth flow will - * redirect to. + * @param context Context for the current turn of conversation with the + * user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with the token. + * @param finalRedirect The final URL that the OAuth flow will redirect to. * - * @return A task that represents the work queued to execute. + * @return A task that represents the work queued to execute. * - * If the task completes successfully, the result contains the raw signin - * link. + * If the task completes successfully, the result contains the raw + * signin link. */ @Override - public CompletableFuture getOAuthSignInLink(TurnContext context, AppCredentials oAuthAppCredentials, - String connectionName, String userId, String finalRedirect) { - if (context == null) { - return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); - } - if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException("connectionName")); - } - if (StringUtils.isEmpty(userId)) { - return Async.completeExceptionally(new IllegalArgumentException("userId")); - } + public CompletableFuture getOAuthSignInLink( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + String finalRedirect + ) { + if (context == null) { + return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); + } + if (StringUtils.isEmpty(connectionName)) { + return Async.completeExceptionally(new IllegalArgumentException("connectionName")); + } + if (StringUtils.isEmpty(userId)) { + return Async.completeExceptionally(new IllegalArgumentException("userId")); + } - return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { - try { - Activity activity = context.getActivity(); - String appId = getBotAppId(context); + return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { + try { + Activity activity = context.getActivity(); + String appId = getBotAppId(context); - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setLocale(activity.getLocale()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setRelatesTo(activity.getRelatesTo()); - setMsAppId(appId); - } - }; + TokenExchangeState tokenExchangeState = new TokenExchangeState() { + { + setConnectionName(connectionName); + setConversation(new ConversationReference() { + { + setActivityId(activity.getId()); + setBot(activity.getRecipient()); + setChannelId(activity.getChannelId()); + setConversation(activity.getConversation()); + setLocale(activity.getLocale()); + setServiceUrl(activity.getServiceUrl()); + setUser(activity.getFrom()); + } + }); + setRelatesTo(activity.getRelatesTo()); + setMsAppId(appId); + } + }; - String serializedState = Serialization.toString(tokenExchangeState); - String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); + String serializedState = Serialization.toString(tokenExchangeState); + String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); - return oAuthClient.getBotSignIn().getSignInUrl(state, null, null, finalRedirect); - } catch (Throwable t) { - throw new CompletionException(t); - } - }); + return oAuthClient.getBotSignIn().getSignInUrl(state, null, null, finalRedirect); + } catch (Throwable t) { + throw new CompletionException(t); + } + }); } /** - * Signs the user out with the token server, using customized - * AppCredentials. + * Signs the user out with the token server, using customized AppCredentials. * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName Name of the auth connection to use. - * @param userId User id of user to sign out. + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId User id of user to sign out. * - * @return A task that represents the work queued to execute. + * @return A task that represents the work queued to execute. */ @Override - public CompletableFuture signOutUser(TurnContext context, AppCredentials oAuthAppCredentials, - String connectionName, String userId) { + public CompletableFuture signOutUser( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId + ) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } @@ -1549,29 +1652,32 @@ public CompletableFuture signOutUser(TurnContext context, AppCredentials o } return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken().signOut(context.getActivity().getFrom().getId(), connectionName, - context.getActivity().getChannelId()); + return oAuthClient.getUserToken() + .signOut(context.getActivity().getFrom().getId(), connectionName, context.getActivity().getChannelId()); }).thenApply(signOutResult -> null); } /** - * Retrieves the token status for each configured connection for the given - * user, using customized AppCredentials. - * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param userId The user Id for which token status is - * retrieved. - * @param includeFilter Optional comma separated list of - * connection's to include. Blank will return token status for all - * configured connections. - * - * @return List of TokenStatus. + * Retrieves the token status for each configured connection for the given user, + * using customized AppCredentials. + * + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param userId The user Id for which token status is retrieved. + * @param includeFilter Optional comma separated list of connection's to + * include. Blank will return token status for all + * configured connections. + * + * @return List of TokenStatus. */ @Override - public CompletableFuture> getTokenStatus(TurnContext context, AppCredentials oAuthAppCredentials, - String userId, String includeFilter) { + public CompletableFuture> getTokenStatus( + TurnContext context, + AppCredentials oAuthAppCredentials, + String userId, + String includeFilter + ) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } @@ -1580,8 +1686,8 @@ public CompletableFuture> getTokenStatus(TurnContext context, } return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken().getTokenStatus(userId, context.getActivity().getChannelId(), - includeFilter); + return oAuthClient.getUserToken() + .getTokenStatus(userId, context.getActivity().getChannelId(), includeFilter); }); } @@ -1589,23 +1695,26 @@ public CompletableFuture> getTokenStatus(TurnContext context, * Retrieves Azure Active Directory tokens for particular resources on a * configured connection, using customized AppCredentials. * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName The name of the Azure Active - * Directory connection configured with this bot. - * @param resourceUrls The list of resource URLs to retrieve - * tokens for. - * @param userId The user Id for which tokens are - * retrieved. If passing in null the userId is taken from the Activity in - * the TurnContext. - * - * @return Dictionary of resourceUrl to the corresponding - * TokenResponse. + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName The name of the Azure Active Directory connection + * configured with this bot. + * @param resourceUrls The list of resource URLs to retrieve tokens for. + * @param userId The user Id for which tokens are retrieved. If + * passing in null the userId is taken from the + * Activity in the TurnContext. + * + * @return Dictionary of resourceUrl to the corresponding TokenResponse. */ @Override - public CompletableFuture> getAadTokens(TurnContext context, - AppCredentials oAuthAppCredentials, String connectionName, String[] resourceUrls, String userId) { + public CompletableFuture> getAadTokens( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String[] resourceUrls, + String userId + ) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } @@ -1618,29 +1727,31 @@ public CompletableFuture> getAadTokens(TurnContext co return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { String effectiveUserId = userId; - if (StringUtils.isEmpty(effectiveUserId) && context.getActivity() != null - && context.getActivity().getFrom() != null) { + if ( + StringUtils.isEmpty(effectiveUserId) && context.getActivity() != null + && context.getActivity().getFrom() != null + ) { effectiveUserId = context.getActivity().getFrom().getId(); } - return oAuthClient.getUserToken().getAadTokens(effectiveUserId, connectionName, - new AadResourceUrls(resourceUrls)); + return oAuthClient.getUserToken() + .getAadTokens(effectiveUserId, connectionName, new AadResourceUrls(resourceUrls)); }); } /** - * Get the raw signin link to be sent to the user for signin for a - * connection name. + * Get the raw signin link to be sent to the user for signin for a connection + * name. * - * @param turnContext Context for the current turn of - * conversation with the user. - * @param connectionName Name of the auth connection to use. + * @param turnContext Context for the current turn of conversation with the + * user. + * @param connectionName Name of the auth connection to use. * - * @return A task that represents the work queued to execute. + * @return A task that represents the work queued to execute. * - * If the task completes successfully, the result contains the raw signin - * link. + * If the task completes successfully, the result contains the raw + * signin link. */ @Override public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName) { @@ -1648,59 +1759,66 @@ public CompletableFuture getSignInResource(TurnContext turnConte } /** - * Get the raw signin link to be sent to the user for signin for a - * connection name. + * Get the raw signin link to be sent to the user for signin for a connection + * name. * - * @param turnContext Context for the current turn of - * conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param userId The user id that will be associated with - * the token. - * @param finalRedirect The final URL that the OAuth flow will - * redirect to. + * @param turnContext Context for the current turn of conversation with the + * user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with the token. + * @param finalRedirect The final URL that the OAuth flow will redirect to. * - * @return A task that represents the work queued to execute. + * @return A task that represents the work queued to execute. * - * If the task completes successfully, the result contains the raw signin - * link. + * If the task completes successfully, the result contains the raw + * signin link. */ @Override - public CompletableFuture getSignInResource(TurnContext turnContext, String connectionName, - String userId, String finalRedirect) { + public CompletableFuture getSignInResource( + TurnContext turnContext, + String connectionName, + String userId, + String finalRedirect + ) { return getSignInResource(turnContext, null, connectionName, userId, finalRedirect); } /** - * Get the raw signin link to be sent to the user for signin for a - * connection name. - * - * @param context Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName Name of the auth connection to use. - * @param userId The user id that will be associated - * with the token. - * @param finalRedirect The final URL that the OAuth flow - * will redirect to. - * - * @return A task that represents the work queued to execute. - * - * If the task completes successfully, the result contains the raw signin - * link. + * Get the raw signin link to be sent to the user for signin for a connection + * name. + * + * @param context Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id that will be associated with the + * token. + * @param finalRedirect The final URL that the OAuth flow will redirect + * to. + * + * @return A task that represents the work queued to execute. + * + * If the task completes successfully, the result contains the raw + * signin link. */ @Override - public CompletableFuture getSignInResource(TurnContext context, - AppCredentials oAuthAppCredentials, String connectionName, String userId, String finalRedirect) { + public CompletableFuture getSignInResource( + TurnContext context, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + String finalRedirect + ) { if (context == null) { return Async.completeExceptionally(new IllegalArgumentException("TurnContext")); } if (StringUtils.isEmpty(connectionName)) { - throw new IllegalArgumentException("connectionName cannot be null."); + throw new IllegalArgumentException("connectionName cannot be null."); } if (StringUtils.isEmpty(userId)) { - throw new IllegalArgumentException("userId cannot be null."); + throw new IllegalArgumentException("userId cannot be null."); } return createOAuthAPIClient(context, oAuthAppCredentials).thenCompose(oAuthClient -> { @@ -1741,74 +1859,75 @@ public CompletableFuture getSignInResource(TurnContext context, /** * Performs a token exchange operation such as for single sign-on. * - * @param turnContext Context for the current turn of - * conversation with the user. - * @param connectionName Name of the auth connection to use. - * @param userId The user id associated with the token.. - * @param exchangeRequest The exchange request details, either a - * token to exchange or a uri to exchange. + * @param turnContext Context for the current turn of conversation with the + * user. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the token.. + * @param exchangeRequest The exchange request details, either a token to + * exchange or a uri to exchange. * - * @return If the task completes, the exchanged token is returned. + * @return If the task completes, the exchanged token is returned. */ @Override - public CompletableFuture exchangeToken(TurnContext turnContext, String connectionName, String userId, - TokenExchangeRequest exchangeRequest) { + public CompletableFuture exchangeToken( + TurnContext turnContext, + String connectionName, + String userId, + TokenExchangeRequest exchangeRequest + ) { return exchangeToken(turnContext, null, connectionName, userId, exchangeRequest); } /** * Performs a token exchange operation such as for single sign-on. * - * @param turnContext Context for the current turn of - * conversation with the user. - * @param oAuthAppCredentials AppCredentials for OAuth. - * @param connectionName Name of the auth connection to use. - * @param userId The user id associated with the - * token.. - * @param exchangeRequest The exchange request details, either - * a token to exchange or a uri to exchange. + * @param turnContext Context for the current turn of conversation with + * the user. + * @param oAuthAppCredentials AppCredentials for OAuth. + * @param connectionName Name of the auth connection to use. + * @param userId The user id associated with the token.. + * @param exchangeRequest The exchange request details, either a token to + * exchange or a uri to exchange. * - * @return If the task completes, the exchanged token is returned. + * @return If the task completes, the exchanged token is returned. */ @Override - public CompletableFuture exchangeToken(TurnContext turnContext, AppCredentials oAuthAppCredentials, - String connectionName, String userId, TokenExchangeRequest exchangeRequest) { + public CompletableFuture exchangeToken( + TurnContext turnContext, + AppCredentials oAuthAppCredentials, + String connectionName, + String userId, + TokenExchangeRequest exchangeRequest + ) { if (StringUtils.isEmpty(connectionName)) { - return Async.completeExceptionally(new IllegalArgumentException( - "connectionName is null or empty" - )); + return Async.completeExceptionally(new IllegalArgumentException("connectionName is null or empty")); } if (StringUtils.isEmpty(userId)) { - return Async.completeExceptionally(new IllegalArgumentException( - "userId is null or empty" - )); + return Async.completeExceptionally(new IllegalArgumentException("userId is null or empty")); } if (exchangeRequest == null) { - return Async.completeExceptionally(new IllegalArgumentException( - "exchangeRequest is null" - )); + return Async.completeExceptionally(new IllegalArgumentException("exchangeRequest is null")); } if (StringUtils.isEmpty(exchangeRequest.getToken()) && StringUtils.isEmpty(exchangeRequest.getUri())) { - return Async.completeExceptionally(new IllegalArgumentException( - "Either a Token or Uri property is required on the TokenExchangeRequest" - )); + return Async.completeExceptionally( + new IllegalArgumentException("Either a Token or Uri property is required on the TokenExchangeRequest") + ); } return createOAuthAPIClient(turnContext, oAuthAppCredentials).thenCompose(oAuthClient -> { - return oAuthClient.getUserToken().exchangeToken(userId, - connectionName, - turnContext.getActivity().getChannelId(), - exchangeRequest); + return oAuthClient.getUserToken() + .exchangeToken(userId, connectionName, turnContext.getActivity().getChannelId(), exchangeRequest); }); } /** - * Inserts a ConnectorClient into the cache. FOR UNIT TESTING ONLY. + * Inserts a ConnectorClient into the cache. FOR UNIT TESTING ONLY. + * * @param serviceUrl The service url * @param appId The app did * @param scope The scope diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 72a09f7a7..cf65a60e2 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -5,6 +5,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.JsonNode; import com.microsoft.bot.builder.BotAdapter; import com.microsoft.bot.builder.BotTelemetryClient; import com.microsoft.bot.builder.NullBotTelemetryClient; @@ -12,14 +13,17 @@ import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.skills.SkillConversationReference; import com.microsoft.bot.builder.skills.SkillHandler; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.authentication.AuthenticationConstants; import com.microsoft.bot.connector.authentication.ClaimsIdentity; import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; import com.microsoft.bot.connector.authentication.SkillValidation; +import com.microsoft.bot.dialogs.memory.DialogStateManager; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.EndOfConversationCodes; +import java.util.Map.Entry; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -29,11 +33,10 @@ public abstract class Dialog { /** - * A {@link DialogTurnResult} that indicates that the current dialog is still active and waiting - * for input from the user next turn. + * A {@link DialogTurnResult} that indicates that the current dialog is still + * active and waiting for input from the user next turn. */ - public static final DialogTurnResult END_OF_TURN = new DialogTurnResult( - DialogTurnStatus.WAITING); + public static final DialogTurnResult END_OF_TURN = new DialogTurnResult(DialogTurnStatus.WAITING); @JsonIgnore private BotTelemetryClient telemetryClient; @@ -94,8 +97,8 @@ public void setTelemetryClient(BotTelemetryClient withTelemetryClient) { * Called when the dialog is started and pushed onto the dialog stack. * * @param dc The {@link DialogContext} for the current turn of conversation. - * @return If the task is successful, the result indicates whether the dialog is still active - * after the turn has been processed by the dialog. + * @return If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. */ public CompletableFuture beginDialog(DialogContext dc) { return beginDialog(dc, null); @@ -104,25 +107,27 @@ public CompletableFuture beginDialog(DialogContext dc) { /** * Called when the dialog is started and pushed onto the dialog stack. * - * @param dc The {@link DialogContext} for the current turn of conversation. + * @param dc The {@link DialogContext} for the current turn of + * conversation. * @param options Initial information to pass to the dialog. - * @return If the task is successful, the result indicates whether the dialog is still active - * after the turn has been processed by the dialog. + * @return If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. */ - public abstract CompletableFuture beginDialog( - DialogContext dc, Object options - ); + public abstract CompletableFuture beginDialog(DialogContext dc, Object options); /** - * Called when the dialog is _continued_, where it is the active dialog and the user replies - * with a new activity. + * Called when the dialog is _continued_, where it is the active dialog and the + * user replies with a new activity. * - *

If this method is *not* overridden, the dialog automatically ends when the user - * replies.

+ *

+ * If this method is *not* overridden, the dialog automatically ends when the + * user replies. + *

* * @param dc The {@link DialogContext} for the current turn of conversation. - * @return If the task is successful, the result indicates whether the dialog is still active - * after the turn has been processed by the dialog. The result may also contain a return value. + * @return If the task is successful, the result indicates whether the dialog is + * still active after the turn has been processed by the dialog. The + * result may also contain a return value. */ public CompletableFuture continueDialog(DialogContext dc) { // By default just end the current dialog. @@ -130,46 +135,54 @@ public CompletableFuture continueDialog(DialogContext dc) { } /** - * Called when a child dialog completed this turn, returning control to this dialog. + * Called when a child dialog completed this turn, returning control to this + * dialog. * - *

Generally, the child dialog was started with a call to - * {@link #beginDialog(DialogContext, Object)} However, if the {@link - * DialogContext#replaceDialog(String)} method is called, the logical child dialog may be - * different than the original.

+ *

+ * Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String)} method is called, the logical + * child dialog may be different than the original. + *

* - *

If this method is *not* overridden, the dialog automatically ends when the user - * replies.

+ *

+ * If this method is *not* overridden, the dialog automatically ends when the + * user replies. + *

* * @param dc The dialog context for the current turn of the conversation. * @param reason Reason why the dialog resumed. - * @return If the task is successful, the result indicates whether this dialog is still active - * after this dialog turn has been processed. + * @return If the task is successful, the result indicates whether this dialog + * is still active after this dialog turn has been processed. */ public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason) { return resumeDialog(dc, reason, null); } /** - * Called when a child dialog completed this turn, returning control to this dialog. + * Called when a child dialog completed this turn, returning control to this + * dialog. * - *

Generally, the child dialog was started with a call to - * {@link #beginDialog(DialogContext, Object)} However, if the {@link - * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may - * be different than the original.

+ *

+ * Generally, the child dialog was started with a call to + * {@link #beginDialog(DialogContext, Object)} However, if the + * {@link DialogContext#replaceDialog(String, Object)} method is called, the + * logical child dialog may be different than the original. + *

* - *

If this method is *not* overridden, the dialog automatically ends when the user - * replies.

+ *

+ * If this method is *not* overridden, the dialog automatically ends when the + * user replies. + *

* * @param dc The dialog context for the current turn of the conversation. * @param reason Reason why the dialog resumed. - * @param result Optional, value returned from the dialog that was called. The type of the value - * returned is dependent on the child dialog. - * @return If the task is successful, the result indicates whether this dialog is still active - * after this dialog turn has been processed. + * @param result Optional, value returned from the dialog that was called. The + * type of the value returned is dependent on the child dialog. + * @return If the task is successful, the result indicates whether this dialog + * is still active after this dialog turn has been processed. */ - public CompletableFuture resumeDialog( - DialogContext dc, DialogReason reason, Object result - ) { + public CompletableFuture resumeDialog(DialogContext dc, DialogReason reason, Object result) { // By default just end the current dialog and return result to parent. return dc.endDialog(result); } @@ -181,9 +194,7 @@ public CompletableFuture resumeDialog( * @param instance State information for this dialog. * @return A CompletableFuture representing the asynchronous operation. */ - public CompletableFuture repromptDialog( - TurnContext turnContext, DialogInstance instance - ) { + public CompletableFuture repromptDialog(TurnContext turnContext, DialogInstance instance) { // No-op by default return CompletableFuture.completedFuture(null); } @@ -192,24 +203,23 @@ public CompletableFuture repromptDialog( * Called when the dialog is ending. * * @param turnContext The context object for this turn. - * @param instance State information associated with the instance of this dialog on the - * dialog stack. + * @param instance State information associated with the instance of this + * dialog on the dialog stack. * @param reason Reason why the dialog ended. * @return A CompletableFuture representing the asynchronous operation. */ - public CompletableFuture endDialog( - TurnContext turnContext, DialogInstance instance, DialogReason reason - ) { + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { // No-op by default return CompletableFuture.completedFuture(null); } /** - * Gets a unique String which represents the version of this dialog. If the version changes - * between turns the dialog system will emit a DialogChanged event. + * Gets a unique String which represents the version of this dialog. If the + * version changes between turns the dialog system will emit a DialogChanged + * event. * - * @return Unique String which should only change when dialog has changed in a way that should - * restart the dialog. + * @return Unique String which should only change when dialog has changed in a + * way that should restart the dialog. */ @JsonIgnore public String getVersion() { @@ -217,46 +227,47 @@ public String getVersion() { } /** - * Called when an event has been raised, using `DialogContext.emitEvent()`, by either the - * current dialog or a dialog that the current dialog started. + * Called when an event has been raised, using `DialogContext.emitEvent()`, by + * either the current dialog or a dialog that the current dialog started. * * @param dc The dialog context for the current turn of conversation. * @param e The event being raised. - * @return True if the event is handled by the current dialog and bubbling should stop. + * @return True if the event is handled by the current dialog and bubbling + * should stop. */ public CompletableFuture onDialogEvent(DialogContext dc, DialogEvent e) { // Before bubble - return onPreBubbleEvent(dc, e) - .thenCompose(handled -> { - // Bubble as needed - if (!handled && e.shouldBubble() && dc.getParent() != null) { - return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false); - } + return onPreBubbleEvent(dc, e).thenCompose(handled -> { + // Bubble as needed + if (!handled && e.shouldBubble() && dc.getParent() != null) { + return dc.getParent().emitEvent(e.getName(), e.getValue(), true, false); + } - // just pass the handled value to the next stage - return CompletableFuture.completedFuture(handled); - }) - .thenCompose(handled -> { - if (!handled) { - // Post bubble - return onPostBubbleEvent(dc, e); - } + // just pass the handled value to the next stage + return CompletableFuture.completedFuture(handled); + }).thenCompose(handled -> { + if (!handled) { + // Post bubble + return onPostBubbleEvent(dc, e); + } - return CompletableFuture.completedFuture(handled); - }); + return CompletableFuture.completedFuture(handled); + }); } /** * Called before an event is bubbled to its parent. * - *

This is a good place to perform interception of an event as returning `true` will prevent - * any further bubbling of the event to the dialogs parents and will also prevent any child - * dialogs from performing their default processing.

+ *

+ * This is a good place to perform interception of an event as returning `true` + * will prevent any further bubbling of the event to the dialogs parents and + * will also prevent any child dialogs from performing their default processing. + *

* * @param dc The dialog context for the current turn of conversation. * @param e The event being raised. - * @return Whether the event is handled by the current dialog and further processing should - * stop. + * @return Whether the event is handled by the current dialog and further + * processing should stop. */ protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEvent e) { return CompletableFuture.completedFuture(false); @@ -265,14 +276,15 @@ protected CompletableFuture onPreBubbleEvent(DialogContext dc, DialogEv /** * Called after an event was bubbled to all parents and wasn't handled. * - *

This is a good place to perform default processing logic for an event. Returning `true` - * will - * prevent any processing of the event by child dialogs.

+ *

+ * This is a good place to perform default processing logic for an event. + * Returning `true` will prevent any processing of the event by child dialogs. + *

* * @param dc The dialog context for the current turn of conversation. * @param e The event being raised. - * @return Whether the event is handled by the current dialog and further processing should - * stop. + * @return Whether the event is handled by the current dialog and further + * processing should stop. */ protected CompletableFuture onPostBubbleEvent(DialogContext dc, DialogEvent e) { return CompletableFuture.completedFuture(false); @@ -292,82 +304,139 @@ protected String onComputeId() { * * @param dialog The dialog to start. * @param turnContext The context for the current turn of the conversation. - * @param accessor The StatePropertyAccessor accessor with which to manage the state of the - * dialog stack. + * @param accessor The StatePropertyAccessor accessor with which to manage + * the state of the dialog stack. * @return A Task representing the asynchronous operation. */ - public static CompletableFuture run( - Dialog dialog, - TurnContext turnContext, - StatePropertyAccessor accessor - ) { + public static CompletableFuture run(Dialog dialog, TurnContext turnContext, + StatePropertyAccessor accessor) { DialogSet dialogSet = new DialogSet(accessor); dialogSet.add(dialog); dialogSet.setTelemetryClient(dialog.getTelemetryClient()); + // return dialogSet.createContext(turnContext) + // .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog, + // turnContext)) + // .thenApply(result -> null); return dialogSet.createContext(turnContext) - .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog, turnContext)) - .thenApply(result -> null); + .thenAccept(dialogContext -> innerRun(turnContext, dialog.getId(), dialogContext)); + } + + /** + * Shared implementation of run with Dialog and DialogManager. + * + * @param turnContext The turnContext. + * @param dialogId The Id of the Dialog. + * @param dialogContext The DialogContext. + * @return A DialogTurnResult. + */ + protected static CompletableFuture innerRun(TurnContext turnContext, String dialogId, + DialogContext dialogContext) { + for (Entry entry : turnContext.getTurnState().getTurnStateServices().entrySet()) { + dialogContext.getServices().replace(entry.getKey(), entry.getValue()); + } + + DialogStateManager dialogStateManager = new DialogStateManager(dialogContext); + return dialogStateManager.loadAllScopes().thenCompose(result -> { + dialogContext.getContext().getTurnState().add(dialogStateManager); + DialogTurnResult dialogTurnResult = null; + boolean endOfTurn = false; + while (!endOfTurn) { + try { + dialogTurnResult = continueOrStart(dialogContext, dialogId, turnContext).join(); + endOfTurn = true; + } catch (Exception err) { + // fire error event, bubbling from the leaf. + boolean handled = dialogContext.emitEvent(DialogEvents.ERROR, err, true, true).join(); + + if (!handled) { + // error was NOT handled, return a result that signifies that the call was unsuccssfull + // (This will trigger the Adapter.OnError handler and end the entire dialog stack) + return Async.completeExceptionally(err); + } + } + } + return CompletableFuture.completedFuture(dialogTurnResult); + }); + } - private static CompletableFuture continueOrStart( - DialogContext dialogContext, Dialog dialog, TurnContext turnContext - ) { + private static CompletableFuture continueOrStart(DialogContext dialogContext, String dialogId, + TurnContext turnContext) { if (DialogCommon.isFromParentToSkill(turnContext)) { // Handle remote cancellation request from parent. if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION)) { if (dialogContext.getStack().size() == 0) { // No dialogs to cancel, just return. - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); } DialogContext activeDialogContext = getActiveDialogContext(dialogContext); - // Send cancellation message to the top dialog in the stack to ensure all the parents + // Send cancellation message to the top dialog in the stack to ensure all the + // parents // are canceled in the right order. - return activeDialogContext.cancelAllDialogs(true, null, null).thenApply(result -> null); + return activeDialogContext.cancelAllDialogs(true, null, null); } // Handle a reprompt event sent from the parent. if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT) - && turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) { + && turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) { if (dialogContext.getStack().size() == 0) { // No dialogs to reprompt, just return. - return CompletableFuture.completedFuture(null); + return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); } - return dialogContext.repromptDialog(); + return dialogContext.repromptDialog() + .thenApply(result -> new DialogTurnResult(DialogTurnStatus.WAITING)); } } - return dialogContext.continueDialog() - .thenCompose(result -> { - if (result.getStatus() == DialogTurnStatus.EMPTY) { - return dialogContext.beginDialog(dialog.getId(), null).thenCompose(finalResult -> { - return processEOC(finalResult, turnContext); - }); - } - return processEOC(result, turnContext); - }); + return dialogContext.continueDialog().thenCompose(result -> { + if (result.getStatus() == DialogTurnStatus.EMPTY) { + return dialogContext.beginDialog(dialogId, null).thenCompose(finalResult -> { + return processEOC(dialogContext, finalResult, turnContext); + }); + } + return processEOC(dialogContext, result, turnContext); + }); } - private static CompletableFuture processEOC(DialogTurnResult result, TurnContext turnContext) { - if (result.getStatus() == DialogTurnStatus.COMPLETE - || result.getStatus() == DialogTurnStatus.CANCELLED - && sendEoCToParent(turnContext)) { + private static CompletableFuture processEOC(DialogContext dialogContext, DialogTurnResult result, + TurnContext turnContext) { + return sendStateSnapshotTrace(dialogContext).thenCompose(snapshotResult -> { + if ((result.getStatus() == DialogTurnStatus.COMPLETE + || result.getStatus() == DialogTurnStatus.CANCELLED) && sendEoCToParent(turnContext)) { EndOfConversationCodes code = result.getStatus() == DialogTurnStatus.COMPLETE - ? EndOfConversationCodes.COMPLETED_SUCCESSFULLY - : EndOfConversationCodes.USER_CANCELLED; + ? EndOfConversationCodes.COMPLETED_SUCCESSFULLY + : EndOfConversationCodes.USER_CANCELLED; Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION); activity.setValue(result.getResult()); activity.setLocale(turnContext.getActivity().getLocale()); activity.setCode(code); - return turnContext.sendActivity(activity).thenApply(finalResult -> null); + turnContext.sendActivity(activity).join(); + } + return CompletableFuture.completedFuture(result); + }); + } + + private static CompletableFuture sendStateSnapshotTrace(DialogContext dialogContext) { + String traceLabel = ""; + Object identity = dialogContext.getContext().getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); + if (identity instanceof ClaimsIdentity) { + traceLabel = SkillValidation.isSkillClaim(((ClaimsIdentity) identity).claims()) ? "Skill State" + : "Bot State"; } - return CompletableFuture.completedFuture(null); + + // send trace of memory + JsonNode snapshot = getActiveDialogContext(dialogContext).getState().getMemorySnapshot(); + Activity traceActivity = Activity.createTraceActivity("BotState", + "https://www.botframework.com/schemas/botState", snapshot, traceLabel); + return dialogContext.getContext().sendActivity(traceActivity).thenApply(result -> null); } /** * Helper to determine if we should send an EoC to the parent or not. + * * @param turnContext * @return */ @@ -376,17 +445,19 @@ private static boolean sendEoCToParent(TurnContext turnContext) { ClaimsIdentity claimsIdentity = turnContext.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) { - // EoC Activities returned by skills are bounced back to the bot by SkillHandler. + // EoC Activities returned by skills are bounced back to the bot by + // SkillHandler. // In those cases we will have a SkillConversationReference instance in state. - SkillConversationReference skillConversationReference = - turnContext.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY); + SkillConversationReference skillConversationReference = turnContext.getTurnState() + .get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY); if (skillConversationReference != null) { - // If the skillConversationReference.OAuthScope is for one of the supported channels, + // If the skillConversationReference.OAuthScope is for one of the supported + // channels, // we are at the root and we should not send an EoC. - return skillConversationReference.getOAuthScope() - != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE - && skillConversationReference.getOAuthScope() - != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; + return skillConversationReference + .getOAuthScope() != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE + && skillConversationReference + .getOAuthScope() != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; } return true; } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java index 519465aeb..d5f566797 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java @@ -9,26 +9,14 @@ import java.util.concurrent.CompletableFuture; import com.fasterxml.jackson.annotation.JsonIgnore; -import com.fasterxml.jackson.databind.JsonNode; -import com.microsoft.bot.builder.BotAdapter; import com.microsoft.bot.builder.BotStateSet; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.StatePropertyAccessor; import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.TurnContextStateCollection; import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.builder.skills.SkillConversationReference; -import com.microsoft.bot.builder.skills.SkillHandler; import com.microsoft.bot.connector.Async; -import com.microsoft.bot.connector.authentication.AuthenticationConstants; -import com.microsoft.bot.connector.authentication.ClaimsIdentity; -import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; -import com.microsoft.bot.connector.authentication.SkillValidation; -import com.microsoft.bot.dialogs.memory.DialogStateManager; import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; -import com.microsoft.bot.schema.EndOfConversationCodes; /** * Class which runs the dialog system. @@ -266,175 +254,13 @@ public CompletableFuture onTurn(TurnContext context) { // Create DialogContext DialogContext dc = new DialogContext(dialogs, context, dialogState); - dc.getServices().getTurnStateServices().forEach((key, value) -> { - dc.getServices().add(key, value); + return Dialog.innerRun(context, rootDialogId, dc).thenCompose(turnResult -> { + return botStateSet.saveAllChanges(dc.getContext(), false).thenCompose(saveResult -> { + DialogManagerResult result = new DialogManagerResult(); + result.setTurnResult(turnResult); + return CompletableFuture.completedFuture(result); + }); }); - - // map TurnState into root dialog context.services - context.getTurnState().getTurnStateServices().forEach((key, value) -> { - dc.getServices().add(key, value); - }); - - // get the DialogStateManager configuration - DialogStateManager dialogStateManager = new DialogStateManager(dc, stateManagerConfiguration); - dialogStateManager.loadAllScopesAsync().join(); - dc.getContext().getTurnState().add(dialogStateManager); - - DialogTurnResult turnResult = null; - - // Loop as long as we are getting valid OnError handled we should continue - // executing the - // actions for the turn. - // NOTE: We loop around this block because each pass through we either complete - // the turn and - // break out of the loop - // or we have had an exception AND there was an OnError action which captured - // the error. - // We need to continue the turn based on the actions the OnError handler - // introduced. - Boolean endOfTurn = false; - while (!endOfTurn) { - try { - ClaimsIdentity claimIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); - if (claimIdentity != null && SkillValidation.isSkillClaim(claimIdentity.claims())) { - // The bot is running as a skill. - turnResult = handleSkillOnTurn(dc).join(); - } else { - // The bot is running as root bot. - turnResult = handleBotOnTurn(dc).join(); - } - - // turn successfully completed, break the loop - endOfTurn = true; - } catch (Exception err) { - // fire error event, bubbling from the leaf. - Boolean handled = dc.emitEvent(DialogEvents.ERROR, err, true, true).join(); - - if (!handled) { - // error was NOT handled, throw the exception and end the turn. (This will - // trigger - // the Adapter.OnError handler and end the entire dialog stack) - return Async.completeExceptionally(new RuntimeException(err)); - } - } - } - - // save all state scopes to their respective botState locations. - dialogStateManager.saveAllChanges(); - - // save BotState changes - botStateSet.saveAllChanges(dc.getContext(), false); - - DialogManagerResult result = new DialogManagerResult(); - result.setTurnResult(turnResult); - return CompletableFuture.completedFuture(result); - } - - /// - /// Helper to send a trace activity with a memory snapshot of the active dialog - /// DC. - /// - private static CompletableFuture sendStateSnapshotTrace(DialogContext dc, String traceLabel) { - // send trace of memory - JsonNode snapshot = getActiveDialogContext(dc).getState().getMemorySnapshot(); - Activity traceActivity = (Activity) Activity.createTraceActivity("Bot State", - "https://www.botframework.com/schemas/botState", snapshot, traceLabel); - dc.getContext().sendActivity(traceActivity).join(); - return CompletableFuture.completedFuture(null); - } - - /** - * Recursively walk up the DC stack to find the active DC. - */ - private static DialogContext getActiveDialogContext(DialogContext dialogContext) { - DialogContext child = dialogContext.getChild(); - if (child == null) { - return dialogContext; - } else { - return getActiveDialogContext(child); - } - } - - private CompletableFuture handleSkillOnTurn(DialogContext dc) { - // the bot instanceof running as a skill. - TurnContext turnContext = dc.getContext(); - - // Process remote cancellation - if (turnContext.getActivity().getType().equals(ActivityTypes.END_OF_CONVERSATION) - && dc.getActiveDialog() != null - && DialogCommon.isFromParentToSkill(turnContext)) { - // Handle remote cancellation request from parent. - DialogContext activeDialogContext = getActiveDialogContext(dc); - - // Send cancellation message to the top dialog in the stack to ensure all the - // parents are canceled in the right order. - return activeDialogContext.cancelAllDialogs(); - } - - // Handle reprompt - // Process a reprompt event sent from the parent. - if (turnContext.getActivity().getType().equals(ActivityTypes.EVENT) - && turnContext.getActivity().getName().equals(DialogEvents.REPROMPT_DIALOG)) { - if (dc.getActiveDialog() == null) { - return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.EMPTY)); - } - - dc.repromptDialog(); - return CompletableFuture.completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING)); - } - - // Continue execution - // - This will apply any queued up interruptions and execute the current/next step(s). - DialogTurnResult turnResult = dc.continueDialog().join(); - if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) { - // restart root dialog - turnResult = dc.beginDialog(rootDialogId).join(); - } - - sendStateSnapshotTrace(dc, "Skill State"); - - if (shouldSendEndOfConversationToParent(turnContext, turnResult)) { - // Send End of conversation at the end. - EndOfConversationCodes code = turnResult.getStatus().equals(DialogTurnStatus.COMPLETE) - ? EndOfConversationCodes.COMPLETED_SUCCESSFULLY - : EndOfConversationCodes.USER_CANCELLED; - Activity activity = new Activity(ActivityTypes.END_OF_CONVERSATION); - activity.setValue(turnResult.getResult()); - activity.setLocale(turnContext.getActivity().getLocale()); - activity.setCode(code); - turnContext.sendActivity(activity).join(); - } - - return CompletableFuture.completedFuture(turnResult); - } - - /** - * Helper to determine if we should send an EndOfConversation to the parent - * or not. - */ - private static boolean shouldSendEndOfConversationToParent(TurnContext context, DialogTurnResult turnResult) { - if (!(turnResult.getStatus().equals(DialogTurnStatus.COMPLETE) - || turnResult.getStatus().equals(DialogTurnStatus.CANCELLED))) { - // The dialog instanceof still going, don't return EoC. - return false; - } - ClaimsIdentity claimsIdentity = context.getTurnState().get(BotAdapter.BOT_IDENTITY_KEY); - if (claimsIdentity != null && SkillValidation.isSkillClaim(claimsIdentity.claims())) { - // EoC Activities returned by skills are bounced back to the bot by SkillHandler. - // In those cases we will have a SkillConversationReference instance in state. - SkillConversationReference skillConversationReference = - context.getTurnState().get(SkillHandler.SKILL_CONVERSATION_REFERENCE_KEY); - if (skillConversationReference != null) { - // If the skillConversationReference.OAuthScope instanceof for one of the supported channels, - // we are at the root and we should not send an EoC. - return skillConversationReference.getOAuthScope() - != AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE - && skillConversationReference.getOAuthScope() - != GovernmentAuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE; - } - return true; - } - return false; } /** @@ -459,28 +285,4 @@ private void registerContainerDialogs(Dialog dialog, Boolean registerRoot) { } } } - - private CompletableFuture handleBotOnTurn(DialogContext dc) { - DialogTurnResult turnResult; - - // the bot is running as a root bot. - if (dc.getActiveDialog() == null) { - // start root dialog - turnResult = dc.beginDialog(rootDialogId).join(); - } else { - // Continue execution - // - This will apply any queued up interruptions and execute the current/next - // step(s). - turnResult = dc.continueDialog().join(); - - if (turnResult.getStatus().equals(DialogTurnStatus.EMPTY)) { - // restart root dialog - turnResult = dc.beginDialog(rootDialogId).join(); - } - } - - sendStateSnapshotTrace(dc, "BotState").join(); - - return CompletableFuture.completedFuture(turnResult); - } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java index 5bf261f20..817a5dfb6 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/DialogStateManager.java @@ -54,6 +54,17 @@ public class DialogStateManager implements Map { private ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + /** + * Initializes a new instance of the + * {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class. + * + * @param dc The dialog context for the current turn of the + * conversation. + */ + public DialogStateManager(DialogContext dc) { + this(dc, null); + } + /** * Initializes a new instance of the * {@link com.microsoft.bot.dialogs.memory.DialogStateManager} class. @@ -455,7 +466,7 @@ public JsonNode getMemorySnapshot() { * * @return A Completed Future. */ - public CompletableFuture loadAllScopesAsync() { + public CompletableFuture loadAllScopes() { configuration.getMemoryScopes().forEach((scope) -> { scope.load(dialogContext, false).join(); }); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java index 7ccc5ae13..8f376be3b 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -104,7 +104,7 @@ public void DialogManager_AlternateProperty() { Dialog adaptiveDialog = CreateTestDialog("conversation.name"); - CreateFlow(adaptiveDialog, storage, firstConversationId, "dialogState", null, null) + CreateFlow(adaptiveDialog, storage, firstConversationId, "DialogState", null, null) .send("hi") .assertReply("Hello, what is your name?") .send("Carlos") From 466fd90b8078db71bb6bb873fb763b1f83818bf3 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 17 Mar 2021 10:41:03 -0500 Subject: [PATCH 105/221] Fixes for Issue 809 (#1060) --- .../bot/builder/ChannelServiceHandler.java | 6 +- .../builder/ChannelServiceHandlerTests.java | 73 ++++++++++++++ .../authentication/AppCredentials.java | 3 + .../authentication/JwtTokenValidation.java | 31 ++++-- .../bot/connector/AppCredentialsTests.java | 95 +++++++++++++++++++ .../connector/JwtTokenValidationTests.java | 27 ++++++ 6 files changed, 224 insertions(+), 11 deletions(-) create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ChannelServiceHandlerTests.java create mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AppCredentialsTests.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java index cb3e74ddd..14a3cde5d 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ChannelServiceHandler.java @@ -48,11 +48,11 @@ public ChannelServiceHandler( ChannelProvider channelProvider) { if (credentialProvider == null) { - throw new IllegalArgumentException("credentialprovider cannot be nul"); + throw new IllegalArgumentException("credentialprovider cannot be null"); } if (authConfiguration == null) { - throw new IllegalArgumentException("authConfiguration cannot be nul"); + throw new IllegalArgumentException("authConfiguration cannot be null"); } this.credentialProvider = credentialProvider; @@ -603,7 +603,7 @@ private CompletableFuture authenticate(String authHeader) { return credentialProvider.isAuthenticationDisabled().thenCompose(isAuthDisabled -> { if (!isAuthDisabled) { return Async.completeExceptionally( - // No auth header. Auth is required. Request is not authorized. + // No auth header. Auth is required. Request is not authorized. new AuthenticationException("No auth header, Auth is required. Request is not authorized") ); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ChannelServiceHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ChannelServiceHandlerTests.java new file mode 100644 index 000000000..77f201fec --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ChannelServiceHandlerTests.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.ClaimsIdentity; +import com.microsoft.bot.connector.authentication.JwtTokenValidation; +import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ResourceResponse; + +import org.junit.Assert; +import org.junit.Test; + +public class ChannelServiceHandlerTests { + + @Test + public void AuthenticateSetsAnonymousSkillClaim() { + TestChannelServiceHandler sut = new TestChannelServiceHandler(); + sut.handleReplyToActivity(null, "123", "456", new Activity(ActivityTypes.MESSAGE)); + + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, + sut.getClaimsIdentity().getType()); + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID, + JwtTokenValidation.getAppIdFromClaims(sut.getClaimsIdentity().claims())); + } + + /** + * A {@link ChannelServiceHandler} with overrides for testings. + */ + private class TestChannelServiceHandler extends ChannelServiceHandler { + TestChannelServiceHandler() { + super(new SimpleCredentialProvider(), new AuthenticationConfiguration(), null); + } + + private ClaimsIdentity claimsIdentity; + + @Override + protected CompletableFuture onReplyToActivity( + ClaimsIdentity claimsIdentity, + String conversationId, + String activityId, + Activity activity + ) { + this.claimsIdentity = claimsIdentity; + return CompletableFuture.completedFuture(new ResourceResponse()); + } + /** + * Gets the {@link ClaimsIdentity} sent to the different methods after + * auth is done. + * @return the ClaimsIdentity value as a getClaimsIdentity(). + */ + public ClaimsIdentity getClaimsIdentity() { + return this.claimsIdentity; + } + + /** + * Gets the {@link ClaimsIdentity} sent to the different methods after + * auth is done. + * @param withClaimsIdentity The ClaimsIdentity value. + */ + private void setClaimsIdentity(ClaimsIdentity withClaimsIdentity) { + this.claimsIdentity = withClaimsIdentity; + } + + } +} + diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java index 000f3db41..497d94125 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java @@ -242,6 +242,9 @@ public CompletableFuture getToken() { * @return true if the auth token should be added to the request. */ boolean shouldSetToken(String url) { + if (StringUtils.isBlank(getAppId()) || getAppId().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID)) { + return false; + } return isTrustedServiceUrl(url); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java index f81a70ca6..286eab799 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java @@ -4,7 +4,10 @@ package com.microsoft.bot.connector.authentication; import com.microsoft.bot.connector.Async; +import com.microsoft.bot.connector.Channels; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.RoleTypes; + import java.util.Map; import org.apache.commons.lang3.StringUtils; @@ -61,19 +64,31 @@ public static CompletableFuture authenticateRequest( ChannelProvider channelProvider, AuthenticationConfiguration authConfig ) { + if (authConfig == null) { + return Async.completeExceptionally( + new IllegalArgumentException("authConfig cannot be null") + ); + } - if (StringUtils.isEmpty(authHeader)) { + if (StringUtils.isBlank(authHeader)) { // No auth header was sent. We might be on the anonymous code path. return credentials.isAuthenticationDisabled().thenApply(isAuthDisable -> { - if (isAuthDisable) { - // In the scenario where Auth is disabled, we still want to have the - // IsAuthenticated flag set in the ClaimsIdentity. To do this requires - // adding in an empty claim. - return new ClaimsIdentity("anonymous"); + if (!isAuthDisable) { + // No Auth Header. Auth is required. Request is not authorized. + throw new AuthenticationException("No Auth Header. Auth is required."); + } + + if (activity.getChannelId() != null + && activity.getChannelId().equals(Channels.EMULATOR) + && activity.getRecipient() != null + && activity.getRecipient().getRole().equals(RoleTypes.SKILL)) { + return SkillValidation.createAnonymousSkillClaim(); } - // No Auth Header. Auth is required. Request is not authorized. - throw new AuthenticationException("No Auth Header. Auth is required."); + // In the scenario where Auth is disabled, we still want to have the + // IsAuthenticated flag set in the ClaimsIdentity. To do this requires + // adding in an empty claim. + return new ClaimsIdentity(AuthenticationConstants.ANONYMOUS_AUTH_TYPE); }); } diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AppCredentialsTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AppCredentialsTests.java new file mode 100644 index 000000000..6310b6597 --- /dev/null +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AppCredentialsTests.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.connector; + +import java.io.IOException; +import java.net.MalformedURLException; + +import com.microsoft.bot.connector.authentication.AppCredentials; +import com.microsoft.bot.connector.authentication.AppCredentialsInterceptor; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; +import com.microsoft.bot.connector.authentication.Authenticator; +import com.microsoft.bot.restclient.ServiceClient; + +import org.junit.Assert; +import org.junit.Test; + +import okhttp3.Interceptor; +import okhttp3.MediaType; +import okhttp3.OkHttpClient; +import okhttp3.Protocol; +import okhttp3.Request; +import okhttp3.Response; +import okhttp3.ResponseBody; +import retrofit2.Retrofit; + +public class AppCredentialsTests { + + @Test + public void ConstructorTests() { + TestAppCredentials shouldDefaultToChannelScope = new TestAppCredentials("irrelevant"); + Assert.assertEquals(AuthenticationConstants.TO_CHANNEL_FROM_BOT_OAUTH_SCOPE, + shouldDefaultToChannelScope.oAuthScope()); + + TestAppCredentials shouldDefaultToCustomScope = new TestAppCredentials("irrelevant", "customScope"); + Assert.assertEquals("customScope", shouldDefaultToCustomScope.oAuthScope()); + } + + @Test + public void basicCredentialsTest() throws Exception { + TestAppCredentials credentials = new TestAppCredentials("irrelevant", "pass"); + OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder(); + credentials.applyCredentialsFilter(clientBuilder); + clientBuilder.addInterceptor( + new Interceptor() { + @Override + public Response intercept(Chain chain) throws IOException { + String header = chain.request().header("Authorization"); + Assert.assertNull(header); + return new Response.Builder() + .request(chain.request()) + .code(200) + .message("OK") + .protocol(Protocol.HTTP_1_1) + .body(ResponseBody.create(MediaType.parse("text/plain"), "azure rocks")) + .build(); + } + }); + ServiceClient serviceClient = new ServiceClient("http://localhost", clientBuilder, new Retrofit.Builder()) { }; + Response response = serviceClient.httpClient().newCall( + new Request.Builder().url("http://localhost").build()).execute(); + Assert.assertEquals(200, response.code()); + } + + private class TestAppCredentials extends AppCredentials { + TestAppCredentials(String channelAuthTenant) { + super(channelAuthTenant); + } + + TestAppCredentials(String channelAuthTenant, String oAuthScope) { + super(channelAuthTenant, oAuthScope); + } + + @Override + protected Authenticator buildAuthenticator() throws MalformedURLException { + return null; + } + + /** + * Apply the credentials to the HTTP request. + * + *

+ * Note: Provides the same functionality as dotnet ProcessHttpRequestAsync + *

+ * + * @param clientBuilder the builder for building up an {@link OkHttpClient} + */ + @Override + public void applyCredentialsFilter(OkHttpClient.Builder clientBuilder) { + clientBuilder.interceptors().add(new AppCredentialsInterceptor(this)); + } + + } +} + diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java index ce755c5a4..90b5485d8 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java @@ -5,6 +5,10 @@ import com.microsoft.bot.connector.authentication.*; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.RoleTypes; + import org.junit.Assert; import org.junit.Test; @@ -209,6 +213,29 @@ public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionExc Assert.assertEquals("anonymous", identity.getIssuer()); } + /** + * Tests with no authentication header and makes sure the service URL is not added to the trusted list. + */ + @Test + public void ChannelAuthenticationDisabledAndSkillShouldBeAnonymous() throws ExecutionException, InterruptedException { + String header = ""; + CredentialProvider credentials = new SimpleCredentialProvider("", ""); + + ClaimsIdentity identity = JwtTokenValidation.authenticateRequest( + new Activity() {{ + setServiceUrl("https://webchat.botframework.com/"); + setChannelId(Channels.EMULATOR); + setRelatesTo(new ConversationReference()); + setRecipient(new ChannelAccount() { { setRole(RoleTypes.SKILL); } }); + }}, + header, + credentials, + new SimpleChannelProvider()).join(); + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_AUTH_TYPE, identity.getType()); + Assert.assertEquals(AuthenticationConstants.ANONYMOUS_SKILL_APPID, JwtTokenValidation.getAppIdFromClaims(identity.claims())); + } + + @Test public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException, ExecutionException, InterruptedException { try { From 837dca217f264aeba2e52d48b2b960be658074f5 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 17 Mar 2021 11:24:44 -0500 Subject: [PATCH 106/221] Move allowed callers and skill conversation factory to SDK (#1062) * Move allowed callers and skill convo factory * Update for Application file. Co-authored-by: tracyboehrer --- .../skills}/SkillConversationIdFactory.java | 54 +++++++-- .../SkillConversationIdFactoryTests.java | 104 ++++++++++++++++++ .../AllowedCallersClaimsValidator.java | 63 +++++++++++ .../AllowedCallersClaimsValidationTests.java | 96 ++++++++++++++++ .../DialogRootBot/pom.xml | 6 + .../bot/sample/simplerootbot/Application.java | 3 +- .../SkillConversationIdFactory.java | 51 --------- .../bot/sample/echoskillbot/Application.java | 10 +- .../AllowedCallersClaimsValidator.java | 67 ----------- .../bot/sample/dialogrootbot/Application.java | 3 +- .../sample/dialogrootbot/Bots/RootBot.java | 2 +- .../sample/dialogskillbot/Application.java | 10 +- .../AllowedCallersClaimsValidator.java | 67 ----------- 13 files changed, 332 insertions(+), 204 deletions(-) rename {samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot => libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills}/SkillConversationIdFactory.java (55%) create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AllowedCallersClaimsValidator.java create mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java similarity index 55% rename from samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java rename to libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java index f9e8cb23c..bee7c0fe1 100644 --- a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillConversationIdFactory.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java @@ -1,16 +1,13 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MT License. -package com.microsoft.bot.sample.dialogrootbot; +package com.microsoft.bot.builder.skills; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; import com.microsoft.bot.builder.Storage; -import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; -import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; -import com.microsoft.bot.builder.skills.SkillConversationReference; import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.ConversationReference; @@ -25,6 +22,11 @@ public class SkillConversationIdFactory extends SkillConversationIdFactoryBase { private Storage storage; + /** + * Creates an instance of a SkillConversationIdFactory. + * + * @param storage A storage instance for the factory. + */ public SkillConversationIdFactory(Storage storage) { if (storage == null) { throw new IllegalArgumentException("Storage cannot be null."); @@ -32,18 +34,26 @@ public SkillConversationIdFactory(Storage storage) { this.storage = storage; } + /** + * Creates a conversation id for a skill conversation. + * + * @param options A {@link SkillConversationIdFactoryOptions} instance + * containing parameters for creating the conversation ID. + * + * @return A unique conversation ID used to communicate with the skill. + * + * It should be possible to use the returned String on a request URL and + * it should not contain special characters. + */ @Override public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { if (options == null) { Async.completeExceptionally(new IllegalArgumentException("options cannot be null.")); } ConversationReference conversationReference = options.getActivity().getConversationReference(); - String skillConversationId = String.format( - "%s-%s-%s-skillconvo", - conversationReference.getConversation().getId(), - options.getBotFrameworkSkill().getId(), - conversationReference.getChannelId() - ); + String skillConversationId = String.format("%s-%s-%s-skillconvo", + conversationReference.getConversation().getId(), options.getBotFrameworkSkill().getId(), + conversationReference.getChannelId()); SkillConversationReference skillConversationReference = new SkillConversationReference(); skillConversationReference.setConversationReference(conversationReference); @@ -51,9 +61,20 @@ public CompletableFuture createSkillConversationId(SkillConversationIdFa Map skillConversationInfo = new HashMap(); skillConversationInfo.put(skillConversationId, skillConversationReference); return storage.write(skillConversationInfo) - .thenCompose(result -> CompletableFuture.completedFuture(skillConversationId)); + .thenCompose(result -> CompletableFuture.completedFuture(skillConversationId)); } + /** + * Gets the {@link SkillConversationReference} created using + * {@link SkillConversationIdFactory#createSkillConversationId} for a + * skillConversationId. + * + * @param skillConversationId A skill conversationId created using + * {@link SkillConversationIdFactory#createSkillConversationId}. + * + * @return The caller's {@link ConversationReference} for a skillConversationId. + * null if not found. + */ @Override public CompletableFuture getSkillConversationReference(String skillConversationId) { if (StringUtils.isAllBlank(skillConversationId)) { @@ -63,13 +84,22 @@ public CompletableFuture getSkillConversationReferen return storage.read(new String[] {skillConversationId}).thenCompose(skillConversationInfo -> { if (skillConversationInfo.size() > 0) { return CompletableFuture - .completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId)); + .completedFuture((SkillConversationReference) skillConversationInfo.get(skillConversationId)); } else { return CompletableFuture.completedFuture(null); } }); } + /** + * Deletes a {@link ConversationReference} . + * + * @param skillConversationId A skill conversationId created using {@link + * CreateSkillConversationId(SkillConversationIdFactoryOptions,System#getT + * reading()#getCancellationToken())} . + * + * @return A {@link CompletableFuture} representing the asynchronous operation. + */ @Override public CompletableFuture deleteConversationReference(String skillConversationId) { return storage.delete(new String[] {skillConversationId}); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java new file mode 100644 index 000000000..2ca4d4625 --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java @@ -0,0 +1,104 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.UUID; + +import com.microsoft.bot.builder.skills.BotFrameworkSkill; +import com.microsoft.bot.builder.skills.SkillConversationIdFactory; +import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; +import com.microsoft.bot.builder.skills.SkillConversationReference; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.ConversationReference; + +import org.junit.Test; +import org.apache.commons.lang3.StringUtils; +import org.junit.Assert; + + +public class SkillConversationIdFactoryTests { + + private static final String SERVICE_URL = "http://testbot.com/api/messages"; + private final String skillId = "skill"; + + private final SkillConversationIdFactory skillConversationIdFactory = + new SkillConversationIdFactory(new MemoryStorage()); + private final String applicationId = UUID.randomUUID().toString(); + private final String botId = UUID.randomUUID().toString(); + + @Test + public void SkillConversationIdFactoryHappyPath() { + ConversationReference conversationReference = buildConversationReference(); + + // Create skill conversation + SkillConversationIdFactoryOptions options = new SkillConversationIdFactoryOptions(); + options.setActivity(buildMessageActivity(conversationReference)); + options.setBotFrameworkSkill(this.buildBotFrameworkSkill()); + options.setFromBotId(botId); + options.setFromBotOAuthScope(botId); + + + String skillConversationId = skillConversationIdFactory.createSkillConversationId(options).join(); + + Assert.assertFalse(StringUtils.isBlank(skillConversationId)); + + // Retrieve skill conversation + SkillConversationReference retrievedConversationReference = + skillConversationIdFactory.getSkillConversationReference(skillConversationId).join(); + + // Delete + skillConversationIdFactory.deleteConversationReference(skillConversationId); + + // Retrieve again + SkillConversationReference deletedConversationReference = + skillConversationIdFactory.getSkillConversationReference(skillConversationId).join(); + + Assert.assertNotNull(retrievedConversationReference); + Assert.assertNotNull(retrievedConversationReference.getConversationReference()); + Assert.assertTrue(compareConversationReferences(conversationReference, + retrievedConversationReference.getConversationReference())); + Assert.assertNull(deletedConversationReference); + } + + private static ConversationReference buildConversationReference() { + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setConversation(new ConversationAccount(UUID.randomUUID().toString())); + conversationReference.setServiceUrl(SERVICE_URL); + return conversationReference; + } + + private static Activity buildMessageActivity(ConversationReference conversationReference) { + if (conversationReference == null) { + throw new IllegalArgumentException("conversationReference cannot be null."); + } + + Activity activity = Activity.createMessageActivity(); + activity.applyConversationReference(conversationReference); + + return activity; + } + + private BotFrameworkSkill buildBotFrameworkSkill() { + BotFrameworkSkill skill = new BotFrameworkSkill(); + skill.setAppId(applicationId); + skill.setId(skillId); + try { + skill.setSkillEndpoint(new URI(SERVICE_URL)); + } catch (URISyntaxException e) { + e.printStackTrace(); + } + return skill; + } + + private static boolean compareConversationReferences( + ConversationReference reference1, + ConversationReference reference2 + ) { + return reference1.getConversation().getId() == reference2.getConversation().getId() + && reference1.getServiceUrl() == reference2.getServiceUrl(); + } +} diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AllowedCallersClaimsValidator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AllowedCallersClaimsValidator.java new file mode 100644 index 000000000..da3eff071 --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AllowedCallersClaimsValidator.java @@ -0,0 +1,63 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.connector.authentication; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.connector.Async; + +/** + * Sample claims validator that loads an allowed list from configuration if + * presentand checks that requests are coming from allowed parent bots. + */ +public class AllowedCallersClaimsValidator extends ClaimsValidator { + + private List allowedCallers; + + /** + * Creates an instance of an {@link AllowedCallersClaimsValidator}. + * @param withAllowedCallers A List that contains the list of allowed callers. + */ + public AllowedCallersClaimsValidator(List withAllowedCallers) { + this.allowedCallers = withAllowedCallers != null ? withAllowedCallers : new ArrayList(); + } + + /** + * Validates a Map of claims and should throw an exception if the + * validation fails. + * + * @param claims The Map of claims to validate. + * + * @return true if the validation is successful, false if not. + */ + @Override + public CompletableFuture validateClaims(Map claims) { + if (claims == null) { + return Async.completeExceptionally(new IllegalArgumentException("Claims cannot be null")); + } + + // If _allowedCallers contains an "*", we allow all callers. + if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) { + // Check that the appId claim in the skill request instanceof in the list of + // callers configured for this bot. + String appId = JwtTokenValidation.getAppIdFromClaims(claims); + if (!allowedCallers.contains(appId)) { + return Async.completeExceptionally( + new RuntimeException( + String.format( + "Received a request from a bot with an app ID of \"%s\". To enable requests from this " + + "caller, add the app ID to the configured set of allowedCallers.", + appId + ) + ) + ); + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java new file mode 100644 index 000000000..db50423ed --- /dev/null +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java @@ -0,0 +1,96 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.connector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator; +import com.microsoft.bot.connector.authentication.AuthenticationConstants; + +import org.apache.commons.lang3.tuple.Pair; +import org.junit.Assert; +import org.junit.Test; + +public class AllowedCallersClaimsValidationTests { + + private final String version = "1.0"; + + private final String audienceClaim = UUID.randomUUID().toString(); + + public static List>> getConfigureServicesSucceedsData() { + String primaryAppId = UUID.randomUUID().toString(); + String secondaryAppId = UUID.randomUUID().toString(); + + List>> resultList = new ArrayList>>(); + // Null allowed callers + resultList.add(Pair.of(null, null)); + // Null configuration with attempted caller + resultList.add(Pair.of(primaryAppId, null)); + // Empty allowed callers array + resultList.add(Pair.of(null, new ArrayList())); + // Allow any caller + resultList.add(Pair.of(primaryAppId, new ArrayList() { { add("*"); } })); + // Specify allowed caller + resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(primaryAppId); } }))); + // Specify multiple callers + resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(primaryAppId); + add(secondaryAppId); } }))); + // Blocked caller throws exception + resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(secondaryAppId); } }))); + return resultList; + } + + @Test + public void TestAcceptAllowedCallersArray() { + List>> configuredServices = getConfigureServicesSucceedsData(); + for (Pair> item : configuredServices) { + acceptAllowedCallersArray(item.getLeft(), item.getRight()); + } + } + + + public void acceptAllowedCallersArray(String allowedCallerClaimId, List allowList) { + AllowedCallersClaimsValidator validator = new AllowedCallersClaimsValidator(allowList); + + if (allowedCallerClaimId != null) { + Map claims = createCallerClaims(allowedCallerClaimId); + + if (allowList != null) { + if (allowList.contains(allowedCallerClaimId) || allowList.contains("*")) { + validator.validateClaims(claims); + } else { + validateUnauthorizedAccessException(allowedCallerClaimId, validator, claims); + } + } else { + validateUnauthorizedAccessException(allowedCallerClaimId, validator, claims); + } + } + } + + private static void validateUnauthorizedAccessException( + String allowedCallerClaimId, + AllowedCallersClaimsValidator validator, + Map claims) { + try { + validator.validateClaims(claims); + } catch (RuntimeException exception) { + Assert.assertTrue(exception.getMessage().contains(allowedCallerClaimId)); + } + } + + private Map createCallerClaims(String appId) { + Map callerClaimMap = new HashMap(); + + callerClaimMap.put(AuthenticationConstants.APPID_CLAIM, appId); + callerClaimMap.put(AuthenticationConstants.VERSION_CLAIM, version); + callerClaimMap.put(AuthenticationConstants.AUDIENCE_CLAIM, audienceClaim); + return callerClaimMap; + } +} + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml index 5bc83ef58..f0ca5da0e 100644 --- a/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml @@ -84,6 +84,12 @@ 4.6.0-preview9 compile + + com.microsoft.bot + bot-builder + 4.6.0-preview9 + compile + diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java index 55cb47636..b7d885fac 100644 --- a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java @@ -8,6 +8,7 @@ import com.microsoft.bot.builder.ChannelServiceHandler; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.skills.SkillConversationIdFactory; import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; import com.microsoft.bot.builder.skills.SkillHandler; import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; @@ -110,7 +111,7 @@ public SkillHttpClient getSkillHttpClient( @Bean public SkillConversationIdFactoryBase getSkillConversationIdFactoryBase() { - return new SkillConversationIdFactory(); + return new SkillConversationIdFactory(getStorage()); } @Bean public ChannelServiceHandler getChannelServiceHandler( diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java b/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java deleted file mode 100644 index 1aa0fd42a..000000000 --- a/samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillConversationIdFactory.java +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.simplerootbot; - -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; -import com.microsoft.bot.builder.skills.SkillConversationIdFactoryOptions; -import com.microsoft.bot.builder.skills.SkillConversationReference; - -/** - * A {@link SkillConversationIdFactory} that uses an in memory - * {@link Map{TKey,TValue}} to store and retrieve {@link ConversationReference} - * instances. - */ -public class SkillConversationIdFactory extends SkillConversationIdFactoryBase { - - private final Map _conversationRefs = - new HashMap(); - - @Override - public CompletableFuture createSkillConversationId(SkillConversationIdFactoryOptions options) { - SkillConversationReference skillConversationReference = new SkillConversationReference(); - skillConversationReference.setConversationReference(options.getActivity().getConversationReference()); - skillConversationReference.setOAuthScope(options.getFromBotOAuthScope()); - String key = String.format( - "%s-%s-%s-%s-skillconvo", - options.getFromBotId(), - options.getBotFrameworkSkill().getAppId(), - skillConversationReference.getConversationReference().getConversation().getId(), - skillConversationReference.getConversationReference().getChannelId() - ); - _conversationRefs.put(key, skillConversationReference); - return CompletableFuture.completedFuture(key); - } - - @Override - public CompletableFuture getSkillConversationReference(String skillConversationId) { - SkillConversationReference conversationReference = _conversationRefs.get(skillConversationId); - return CompletableFuture.completedFuture(conversationReference); - } - - @Override - public CompletableFuture deleteConversationReference(String skillConversationId) { - _conversationRefs.remove(skillConversationId); - return CompletableFuture.completedFuture(null); - } -} diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java index 52995f61c..3f539d264 100644 --- a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java @@ -3,13 +3,15 @@ package com.microsoft.bot.sample.echoskillbot; +import java.util.Arrays; + import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator; import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; import com.microsoft.bot.integration.spring.BotController; import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.echoskillbot.authentication.AllowedCallersClaimsValidator; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @@ -35,6 +37,8 @@ */ public class Application extends BotDependencyConfiguration { + private final String configKey = "AllowedCallers"; + public static void main(String[] args) { SpringApplication.run(Application.class, args); } @@ -57,7 +61,9 @@ public Bot getBot() { @Override public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); - authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration)); + authenticationConfiguration.setClaimsValidator( + new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey))) + ); return authenticationConfiguration; } diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java deleted file mode 100644 index e85d547c7..000000000 --- a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/authentication/AllowedCallersClaimsValidator.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.echoskillbot.authentication; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.connector.Async; -import com.microsoft.bot.connector.authentication.ClaimsValidator; -import com.microsoft.bot.connector.authentication.JwtTokenValidation; -import com.microsoft.bot.connector.authentication.SkillValidation; -import com.microsoft.bot.integration.Configuration; - -/** - * Sample claims validator that loads an allowed list from configuration if - * presentand checks that requests are coming from allowed parent bots. - */ -public class AllowedCallersClaimsValidator extends ClaimsValidator { - - private final String configKey = "AllowedCallers"; - private final List allowedCallers; - - public AllowedCallersClaimsValidator(Configuration config) { - if (config == null) { - throw new IllegalArgumentException("config cannot be null."); - } - - // AllowedCallers instanceof the setting in the application.properties file - // that consists of the list of parent bot Ds that are allowed to access the - // skill. - // To add a new parent bot, simply edit the AllowedCallers and add - // the parent bot's Microsoft app ID to the list. - // In this sample, we allow all callers if AllowedCallers contains an "*". - String[] appsList = config.getProperties(configKey); - if (appsList == null) { - throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey)); - } - - allowedCallers = Arrays.asList(appsList); - } - - @Override - public CompletableFuture validateClaims(Map claims) { - // If _allowedCallers contains an "*", we allow all callers. - if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) { - // Check that the appId claim in the skill request instanceof in the list of - // callers configured for this bot. - String appId = JwtTokenValidation.getAppIdFromClaims(claims); - if (!allowedCallers.contains(appId)) { - return Async.completeExceptionally( - new RuntimeException( - String.format( - "Received a request from a bot with an app ID of \"%s\". " - + "To enable requests from this caller, add the app ID to your configuration file.", - appId - ) - ) - ); - } - } - - return CompletableFuture.completedFuture(null); - } -} diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java index bb2331ac1..dc37fe66a 100644 --- a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java @@ -8,6 +8,7 @@ import com.microsoft.bot.builder.ChannelServiceHandler; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.skills.SkillConversationIdFactory; import com.microsoft.bot.builder.skills.SkillConversationIdFactoryBase; import com.microsoft.bot.builder.skills.SkillHandler; import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; @@ -18,8 +19,8 @@ import com.microsoft.bot.integration.SkillHttpClient; import com.microsoft.bot.integration.spring.BotController; import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.dialogrootbot.Bots.RootBot; import com.microsoft.bot.sample.dialogrootbot.authentication.AllowedSkillsClaimsValidator; +import com.microsoft.bot.sample.dialogrootbot.bots.RootBot; import com.microsoft.bot.sample.dialogrootbot.dialogs.MainDialog; import org.springframework.boot.SpringApplication; diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java index 227afb8c4..96b692f0a 100644 --- a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java @@ -1,7 +1,7 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MT License. -package com.microsoft.bot.sample.dialogrootbot.Bots; +package com.microsoft.bot.sample.dialogrootbot.bots; import java.io.IOException; import java.io.InputStream; diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java index 61f160d22..818516bf6 100644 --- a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java +++ b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java @@ -3,14 +3,16 @@ package com.microsoft.bot.sample.dialogskillbot; +import java.util.Arrays; + import com.microsoft.bot.builder.Bot; import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.connector.authentication.AllowedCallersClaimsValidator; import com.microsoft.bot.connector.authentication.AuthenticationConfiguration; import com.microsoft.bot.integration.BotFrameworkHttpAdapter; import com.microsoft.bot.integration.Configuration; import com.microsoft.bot.integration.spring.BotController; import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.dialogskillbot.authentication.AllowedCallersClaimsValidator; import com.microsoft.bot.sample.dialogskillbot.bots.SkillBot; import com.microsoft.bot.sample.dialogskillbot.dialogs.ActivityRouterDialog; import com.microsoft.bot.sample.dialogskillbot.dialogs.DialogSkillBotRecognizer; @@ -39,6 +41,8 @@ */ public class Application extends BotDependencyConfiguration { + private final String configKey = "AllowedCallers"; + public static void main(String[] args) { SpringApplication.run(Application.class, args); } @@ -65,7 +69,9 @@ public Bot getBot(Configuration configuration, ConversationState converationStat @Override public AuthenticationConfiguration getAuthenticationConfiguration(Configuration configuration) { AuthenticationConfiguration authenticationConfiguration = new AuthenticationConfiguration(); - authenticationConfiguration.setClaimsValidator(new AllowedCallersClaimsValidator(configuration)); + authenticationConfiguration.setClaimsValidator( + new AllowedCallersClaimsValidator(Arrays.asList(configuration.getProperties(configKey))) + ); return authenticationConfiguration; } diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java b/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java deleted file mode 100644 index c15c81cff..000000000 --- a/samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/authentication/AllowedCallersClaimsValidator.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.dialogskillbot.authentication; - -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.connector.Async; -import com.microsoft.bot.connector.authentication.ClaimsValidator; -import com.microsoft.bot.connector.authentication.JwtTokenValidation; -import com.microsoft.bot.connector.authentication.SkillValidation; -import com.microsoft.bot.integration.Configuration; - -/** - * Sample claims validator that loads an allowed list from configuration if - * presentand checks that requests are coming from allowed parent bots. - */ -public class AllowedCallersClaimsValidator extends ClaimsValidator { - - private final String configKey = "AllowedCallers"; - private final List allowedCallers; - - public AllowedCallersClaimsValidator(Configuration config) { - if (config == null) { - throw new IllegalArgumentException("config cannot be null."); - } - - // AllowedCallers instanceof the setting in the application.properties file - // that consists of the list of parent bot Ds that are allowed to access the - // skill. - // To add a new parent bot, simply edit the AllowedCallers and add - // the parent bot's Microsoft app ID to the list. - // In this sample, we allow all callers if AllowedCallers contains an "*". - String[] appsList = config.getProperties(configKey); - if (appsList == null) { - throw new IllegalStateException(String.format("\"%s\" not found in configuration.", configKey)); - } - - allowedCallers = Arrays.asList(appsList); - } - - @Override - public CompletableFuture validateClaims(Map claims) { - // If _allowedCallers contains an "*", we allow all callers. - if (SkillValidation.isSkillClaim(claims) && !allowedCallers.contains("*")) { - // Check that the appId claim in the skill request instanceof in the list of - // callers configured for this bot. - String appId = JwtTokenValidation.getAppIdFromClaims(claims); - if (!allowedCallers.contains(appId)) { - return Async.completeExceptionally( - new RuntimeException( - String.format( - "Received a request from a bot with an app ID of \"%s\". " - + "To enable requests from this caller, add the app ID to your configuration file.", - appId - ) - ) - ); - } - } - - return CompletableFuture.completedFuture(null); - } -} From c24b0e4046d258f421c2c3aa96ec4c90fed9f1ce Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 18 Mar 2021 09:46:23 -0500 Subject: [PATCH 107/221] Completed adaptive card sample 07.using-adaptive-cards (#1064) * Completed adaptive card sample * Remove commented code line. --- samples/07.using-adaptive-cards/LICENSE | 21 + samples/07.using-adaptive-cards/README.md | 59 +++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/07.using-adaptive-cards/pom.xml | 249 +++++++++++ .../usingadaptivecards/Application.java | 67 +++ .../bots/AdaptiveCardsBot.java | 97 ++++ .../main/resources/FlightItineraryCard.json | 204 +++++++++ .../src/main/resources/ImageGalleryCard.json | 60 +++ .../src/main/resources/LargeWeatherCard.json | 205 +++++++++ .../src/main/resources/RestaurantCard.json | 60 +++ .../src/main/resources/SolitaireCard.json | 39 ++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../usingadaptivecards/ApplicationTest.java | 19 + 18 files changed, 2084 insertions(+) create mode 100644 samples/07.using-adaptive-cards/LICENSE create mode 100644 samples/07.using-adaptive-cards/README.md create mode 100644 samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/07.using-adaptive-cards/pom.xml create mode 100644 samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java create mode 100644 samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java create mode 100644 samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json create mode 100644 samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json create mode 100644 samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json create mode 100644 samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json create mode 100644 samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json create mode 100644 samples/07.using-adaptive-cards/src/main/resources/application.properties create mode 100644 samples/07.using-adaptive-cards/src/main/resources/log4j2.json create mode 100644 samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/07.using-adaptive-cards/src/main/webapp/index.html create mode 100644 samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java diff --git a/samples/07.using-adaptive-cards/LICENSE b/samples/07.using-adaptive-cards/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/07.using-adaptive-cards/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/07.using-adaptive-cards/README.md b/samples/07.using-adaptive-cards/README.md new file mode 100644 index 000000000..3212a04ad --- /dev/null +++ b/samples/07.using-adaptive-cards/README.md @@ -0,0 +1,59 @@ +# Using Adaptive Cards + +Bot Framework v4 using adaptive cards bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to send an Adaptive Card from the bot to the user. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\adaptive-cards-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Interacting with the bot + +Card authors describe their content as a simple JSON object. That content can then be rendered natively inside a host application, automatically adapting to the look and feel of the host. For example, Contoso Bot can author an Adaptive Card through the Bot Framework, and when delivered to Cortana, it will look and feel like a Cortana card. When that same payload is sent to Microsoft Teams, it will look and feel like Microsoft Teams. As more host apps start to support Adaptive Cards, that same payload will automatically light up inside these applications, yet still feel entirely native to the app. Users win because everything feels familiar. Host apps win because they control the user experience. Card authors win because their content gets broader reach without any additional work. + +The Bot Framework provides support for Adaptive Cards. See the following to learn more about Adaptive Cards. + +- [Adaptive card](http://adaptivecards.io) +- [Send an Adaptive card](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-rich-cards?view=azure-bot-service-3.0&viewFallbackFrom=azure-bot-service-4.0#send-an-adaptive-card) + +### Adding media to messages + +A message exchange between user and bot can contain media attachments, such as cards, images, video, audio, and files. + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Adaptive Cards](https://adaptivecards.io/) +- [Send an Adaptive card](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-rich-cards?view=azure-bot-service-3.0&viewFallbackFrom=azure-bot-service-4.0#send-an-adaptive-card) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/07.using-adaptive-cards/pom.xml b/samples/07.using-adaptive-cards/pom.xml new file mode 100644 index 000000000..ece6a3907 --- /dev/null +++ b/samples/07.using-adaptive-cards/pom.xml @@ -0,0 +1,249 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + adaptive-cards + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Adaptive Cards bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.usingadaptivecards.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-builder + 4.6.0-preview9 + compile + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.usingadaptivecards.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java new file mode 100644 index 000000000..93bb1388b --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingadaptivecards; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.usingadaptivecards.bots.AdaptiveCardsBot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new AdaptiveCardsBot(); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java new file mode 100644 index 000000000..a64e090ec --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.usingadaptivecards.bots; + +import java.io.InputStream; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Random; +import java.util.concurrent.CompletableFuture; + + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Serialization; + +import org.apache.commons.io.IOUtils; + +// This bot will respond to the user's input with an Adaptive Card. +// Adaptive Cards are a way for developers to exchange card content +// in a common and consistent way. A simple open card format enables +// an ecosystem of shared tooling, seamless integration between apps, +// and native cross-platform performance on any device. +// For each user interaction, an instance of this class instanceof created and the OnTurnAsync method instanceof called. +// This instanceof a Transient lifetime service. Transient lifetime services are created +// each time they're requested. For each Activity received, a new instance of this +// class instanceof created. Objects that are expensive to construct, or have a lifetime +// beyond the single turn, should be carefully managed. + +public class AdaptiveCardsBot extends ActivityHandler { + + private static final String welcomeText = "This bot will introduce you to AdaptiveCards. " + + "Type anything to see an AdaptiveCard."; + + // This array contains the file names of our adaptive cards + private final String[] cards = { + "FlightItineraryCard.json", + "ImageGalleryCard.json", + "LargeWeatherCard.json", + "RestaurantCard.json", + "SolitaireCard.json" + }; + + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, + TurnContext turnContext + ) { + return sendWelcomeMessage(turnContext); + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + Random r = new Random(); + Attachment cardAttachment = createAdaptiveCardAttachment(cards[r.nextInt(cards.length)]); + + return turnContext.sendActivity(MessageFactory.attachment(cardAttachment)).thenCompose(result ->{ + return turnContext.sendActivity(MessageFactory.text("Please enter any text to see another card.")) + .thenApply(sendResult -> null); + }); + } + + private static CompletableFuture sendWelcomeMessage(TurnContext turnContext) { + for (ChannelAccount member : turnContext.getActivity().getMembersAdded()) { + if (!member.getId().equals(turnContext.getActivity().getRecipient().getId())) { + turnContext.sendActivity( + String.format("Welcome to Adaptive Cards Bot %s. %s", member.getName(), welcomeText) + ).join(); + } + } + return CompletableFuture.completedFuture(null); + } + + private static Attachment createAdaptiveCardAttachment(String filePath) { + try ( + InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream(filePath) + ) { + String adaptiveCardJson = IOUtils + .toString(inputStream, StandardCharsets.UTF_8.toString()); + + return new Attachment() {{ + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(Serialization.jsonToTree(adaptiveCardJson)); + }}; + + } catch (IOException e) { + e.printStackTrace(); + return new Attachment(); + } + } +} + diff --git a/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json b/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json new file mode 100644 index 000000000..1c97e8a72 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json @@ -0,0 +1,204 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.0", + "type": "AdaptiveCard", + "speak": "Your flight is confirmed for you and 3 other passengers from San Francisco to Amsterdam on Friday, October 10 8:30 AM", + "body": [ + { + "type": "TextBlock", + "text": "Passengers", + "weight": "bolder", + "isSubtle": false + }, + { + "type": "TextBlock", + "text": "Sarah Hum", + "separator": true + }, + { + "type": "TextBlock", + "text": "Jeremy Goldberg", + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "Evan Litvak", + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "2 Stops", + "weight": "bolder", + "spacing": "medium" + }, + { + "type": "TextBlock", + "text": "Fri, October 10 8:30 AM", + "weight": "bolder", + "spacing": "none" + }, + { + "type": "ColumnSet", + "separator": true, + "columns": [ + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "text": "San Francisco", + "isSubtle": true + }, + { + "type": "TextBlock", + "size": "extraLarge", + "color": "accent", + "text": "SFO", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": " " + }, + { + "type": "Image", + "url": "https://adaptivecards.io/content/airplane.png", + "size": "small", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "Amsterdam", + "isSubtle": true + }, + { + "type": "TextBlock", + "horizontalAlignment": "right", + "size": "extraLarge", + "color": "accent", + "text": "AMS", + "spacing": "none" + } + ] + } + ] + }, + { + "type": "TextBlock", + "text": "Non-Stop", + "weight": "bolder", + "spacing": "medium" + }, + { + "type": "TextBlock", + "text": "Fri, October 18 9:50 PM", + "weight": "bolder", + "spacing": "none" + }, + { + "type": "ColumnSet", + "separator": true, + "columns": [ + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "text": "Amsterdam", + "isSubtle": true + }, + { + "type": "TextBlock", + "size": "extraLarge", + "color": "accent", + "text": "AMS", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": "auto", + "items": [ + { + "type": "TextBlock", + "text": " " + }, + { + "type": "Image", + "url": "https://adaptivecards.io/content/airplane.png", + "size": "small", + "spacing": "none" + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "San Francisco", + "isSubtle": true + }, + { + "type": "TextBlock", + "horizontalAlignment": "right", + "size": "extraLarge", + "color": "accent", + "text": "SFO", + "spacing": "none" + } + ] + } + ] + }, + { + "type": "ColumnSet", + "spacing": "medium", + "columns": [ + { + "type": "Column", + "width": "1", + "items": [ + { + "type": "TextBlock", + "text": "Total", + "size": "medium", + "isSubtle": true + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "right", + "text": "$4,032.54", + "size": "medium", + "weight": "bolder" + } + ] + } + ] + } + ] +} \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json b/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json new file mode 100644 index 000000000..b2558d5f2 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "TextBlock", + "text": "Here are some cool photos", + "size": "large" + }, + { + "type": "TextBlock", + "text": "Sorry some of them are repeats", + "size": "medium", + "weight": "lighter" + }, + { + "type": "ImageSet", + "imageSize": "medium", + "images": [ + { + "type": "Image", + "url": "https://picsum.photos/200/200?image=100" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=200" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=301" + }, + { + "type": "Image", + "url": "https://picsum.photos/200/200?image=400" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=500" + }, + { + "type": "Image", + "url": "https://picsum.photos/200/200?image=600" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=700" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=800" + }, + { + "type": "Image", + "url": "https://picsum.photos/300/200?image=900" + } + ] + } + ] + } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json b/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json new file mode 100644 index 000000000..938fc5db0 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json @@ -0,0 +1,205 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "speak": "Weather forecast for Monday is high of 62 and low of 42 degrees with a 20% chance of rain. Winds will be 5 mph from the northeast.", + "backgroundImage": "https://adaptivecards.io/content/Mostly%20Cloudy-Background-Dark.jpg", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "35", + "items": [ + { + "type": "Image", + "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png", + "size": "stretch" + } + ] + }, + { + "type": "Column", + "width": "65", + "items": [ + { + "type": "TextBlock", + "text": "Monday April 1", + "weight": "bolder", + "size": "large", + "color": "light" + }, + { + "type": "TextBlock", + "text": "63 / 42", + "size": "medium", + "spacing": "none" + }, + { + "type": "TextBlock", + "isSubtle": true, + "text": "20% chance of rain", + "spacing": "none" + }, + { + "type": "TextBlock", + "isSubtle": true, + "text": "Winds 5 mph NE", + "spacing": "none" + } + ] + } + ] + }, + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": "20", + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "Fri" + }, + { + "type": "Image", + "size": "auto", + "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "62" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "isSubtle": true, + "wrap": false, + "text": "52", + "spacing": "none" + } + ], + "selectAction": { + "type": "Action.OpenUrl", + "title": "View Friday", + "url": "https://www.microsoft.com" + } + }, + { + "type": "Column", + "width": "20", + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "Sat" + }, + { + "type": "Image", + "size": "auto", + "url": "https://adaptivecards.io/content/Drizzle-Square.png" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "60" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "isSubtle": true, + "wrap": false, + "text": "48", + "spacing": "none" + } + ], + "selectAction": { + "type": "Action.OpenUrl", + "title": "View Saturday", + "url": "https://www.microsoft.com" + } + }, + { + "type": "Column", + "width": "20", + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "Sun" + }, + { + "type": "Image", + "size": "auto", + "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "59" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "isSubtle": true, + "wrap": false, + "text": "49", + "spacing": "none" + } + ], + "selectAction": { + "type": "Action.OpenUrl", + "title": "View Sunday", + "url": "https://www.microsoft.com" + } + }, + { + "type": "Column", + "width": "20", + "items": [ + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "Mon" + }, + { + "type": "Image", + "size": "auto", + "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "wrap": false, + "text": "64" + }, + { + "type": "TextBlock", + "horizontalAlignment": "center", + "isSubtle": true, + "wrap": false, + "text": "51", + "spacing": "none" + } + ], + "selectAction": { + "type": "Action.OpenUrl", + "title": "View Monday", + "url": "https://www.microsoft.com" + } + } + ] + } + ] + } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json b/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json new file mode 100644 index 000000000..20acdc3e6 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json @@ -0,0 +1,60 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "speak": "Tom's Pie is a pizza restaurant which is rated 9.3 by customers.", + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": 2, + "items": [ + { + "type": "TextBlock", + "text": "PIZZA" + }, + { + "type": "TextBlock", + "text": "Tom's Pie", + "weight": "bolder", + "size": "extraLarge", + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "4.2 ★★★☆ (93) · $$", + "isSubtle": true, + "spacing": "none" + }, + { + "type": "TextBlock", + "text": "**Matt H. said** \"I'm compelled to give this place 5 stars due to the number of times I've chosen to eat here this past year!\"", + "size": "small", + "wrap": true + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "Image", + "url": "https://picsum.photos/300?image=882", + "size": "auto" + } + ] + } + ] + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "More Info", + "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" + } + ] + } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json b/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json new file mode 100644 index 000000000..5d68664b3 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json @@ -0,0 +1,39 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png", + "body": [ + { + "type": "ColumnSet", + "columns": [ + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "Image", + "url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png", + "size": "stretch" + } + ] + }, + { + "type": "Column", + "width": 1, + "items": [ + { + "type": "TextBlock", + "text": "Click here to play another game of Spider in Microsoft Solitaire Collection!", + "color": "light", + "weight": "bolder", + "wrap": true, + "size": "default", + "horizontalAlignment": "center" + } + ] + } + ] + } + ] + } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/application.properties b/samples/07.using-adaptive-cards/src/main/resources/application.properties new file mode 100644 index 000000000..d7d0ee864 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 diff --git a/samples/07.using-adaptive-cards/src/main/resources/log4j2.json b/samples/07.using-adaptive-cards/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF b/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml b/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/webapp/index.html b/samples/07.using-adaptive-cards/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/07.using-adaptive-cards/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java b/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java new file mode 100644 index 000000000..c2cc1d1e6 --- /dev/null +++ b/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.usingadaptivecards; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 8399a7f3aaf69875e1e0f9c3ff4de295848e569a Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Thu, 18 Mar 2021 13:03:29 -0300 Subject: [PATCH 108/221] [Samples] Add 49.qnamaker-all-features sample (#1063) * Add main README and pom * Add deploymentTemplates * Add resources folder * Add webapp folder * Add empty test * Add botservices * Add Startup * Add dialogs * Add bot * Add package-info file * Fix issues of QnAMakerDialog, add default constructor and remove double braces * Fix issues in sample, update document and set initialDialog name for WaterfallDialog * Fix disparities between samples * Fix issues in migrated README --- .../bot/ai/qna/dialogs/QnAMakerDialog.java | 109 ++--- samples/49.qnamaker-all-features/LICENSE | 21 + samples/49.qnamaker-all-features/README.md | 126 ++++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/49.qnamaker-all-features/pom.xml | 244 ++++++++++ .../qnamaker/all/features/Application.java | 78 ++++ .../qnamaker/all/features/BotServices.java | 19 + .../all/features/BotServicesImpl.java | 81 ++++ .../sample/qnamaker/all/features/QnABot.java | 79 ++++ .../all/features/QnAMakerBaseDialog.java | 85 ++++ .../qnamaker/all/features/RootDialog.java | 57 +++ .../qnamaker/all/features/package-info.java | 8 + .../src/main/resources/application.properties | 7 + .../src/main/resources/log4j2.json | 18 + .../src/main/resources/smartLightFAQ.tsv | 23 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 420 ++++++++++++++++++ .../all/features/ApplicationTest.java | 19 + 20 files changed, 1905 insertions(+), 54 deletions(-) create mode 100644 samples/49.qnamaker-all-features/LICENSE create mode 100644 samples/49.qnamaker-all-features/README.md create mode 100644 samples/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/49.qnamaker-all-features/pom.xml create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/Application.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServices.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServicesImpl.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnABot.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnAMakerBaseDialog.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/RootDialog.java create mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/package-info.java create mode 100644 samples/49.qnamaker-all-features/src/main/resources/application.properties create mode 100644 samples/49.qnamaker-all-features/src/main/resources/log4j2.json create mode 100644 samples/49.qnamaker-all-features/src/main/resources/smartLightFAQ.tsv create mode 100644 samples/49.qnamaker-all-features/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/49.qnamaker-all-features/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/49.qnamaker-all-features/src/main/webapp/index.html create mode 100644 samples/49.qnamaker-all-features/src/test/java/com/microsoft/bot/sample/qnamaker/all/features/ApplicationTest.java diff --git a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/dialogs/QnAMakerDialog.java b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/dialogs/QnAMakerDialog.java index 6459b2d70..959a39efa 100644 --- a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/dialogs/QnAMakerDialog.java +++ b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/dialogs/QnAMakerDialog.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.List; import java.util.Map; +import java.util.HashMap; import java.util.concurrent.CompletableFuture; import javax.annotation.Nullable; @@ -438,6 +439,19 @@ public void setRankerType(String withRankerType) { this.rankerType = withRankerType; } + /** + * Initializes a new instance of the @{link QnAMakerDialog} class. + */ + public QnAMakerDialog() { + super("QnAMakerDialog", null); + + // add waterfall steps + this.addStep(this::callGenerateAnswer); + this.addStep(this::callTrain); + this.addStep(this::checkForMultiTurnPrompt); + this.addStep(this::displayQnAResult); + } + /** * Initializes a new instance of the @{link QnAMakerDialog} class. * @@ -570,7 +584,7 @@ public QnAMakerDialog( @Nullable OkHttpClient withHttpClient ) { this( - QnAMakerDialog.class.getName(), + "QnAMakerDialog", withKnowledgeBaseId, withEndpointKey, withHostName, @@ -613,15 +627,12 @@ public CompletableFuture beginDialog(DialogContext dc, @Nullab QnAMakerOptions qnAMakerOptions = this.getQnAMakerOptions(dc).join(); QnADialogResponseOptions qnADialogResponseOptions = this.getQnAResponseOptions(dc).join(); - QnAMakerDialogOptions dialogOptions = new QnAMakerDialogOptions() { - { - setQnAMakerOptions(qnAMakerOptions); - setResponseOptions(qnADialogResponseOptions); - } - }; + QnAMakerDialogOptions dialogOptions = new QnAMakerDialogOptions(); + dialogOptions.setQnAMakerOptions(qnAMakerOptions); + dialogOptions.setResponseOptions(qnADialogResponseOptions); if (options != null) { - dialogOptions = (QnAMakerDialogOptions) ObjectPath.assign(dialogOptions, options); + dialogOptions = ObjectPath.assign(dialogOptions, options); } ObjectPath.setPathValue(dc.getActiveDialog().getState(), OPTIONS, dialogOptions); @@ -706,13 +717,10 @@ protected CompletableFuture getQnAMakerClient(DialogContext dc) return CompletableFuture.completedFuture(qnaClient); } - QnAMakerEndpoint endpoint = new QnAMakerEndpoint() { - { - setEndpointKey(endpointKey); - setHost(hostName); - setKnowledgeBaseId(knowledgeBaseId); - } - }; + QnAMakerEndpoint endpoint = new QnAMakerEndpoint(); + endpoint.setEndpointKey(endpointKey); + endpoint.setHost(hostName); + endpoint.setKnowledgeBaseId(knowledgeBaseId); return this.getQnAMakerOptions( dc @@ -729,17 +737,15 @@ protected CompletableFuture getQnAMakerClient(DialogContext dc) * task is successful, the result contains the QnA Maker options to use. */ protected CompletableFuture getQnAMakerOptions(DialogContext dc) { - return CompletableFuture.completedFuture(new QnAMakerOptions() { - { - setScoreThreshold(threshold); - setStrictFilters(strictFilters); - setTop(top); - setContext(new QnARequestContext()); - setQnAId(0); - setRankerType(rankerType); - setIsTest(isTest); - } - }); + QnAMakerOptions options = new QnAMakerOptions(); + options.setScoreThreshold(threshold); + options.setStrictFilters(strictFilters); + options.setTop(top); + options.setContext(new QnARequestContext()); + options.setQnAId(0); + options.setRankerType(rankerType); + options.setIsTest(isTest); + return CompletableFuture.completedFuture(options); } /** @@ -750,16 +756,15 @@ protected CompletableFuture getQnAMakerOptions(DialogContext dc * successful, the result contains the response options to use. */ protected CompletableFuture getQnAResponseOptions(DialogContext dc) { - return CompletableFuture.completedFuture(new QnADialogResponseOptions() { - { - setNoAnswer(noAnswer.bind(dc, dc.getState()).join()); - setActiveLearningCardTitle( - activeLearningCardTitle != null ? activeLearningCardTitle : DEFAULT_CARD_TITLE - ); - setCardNoMatchText(cardNoMatchText != null ? cardNoMatchText : DEFAULT_CARD_NO_MATCH_TEXT); - setCardNoMatchResponse(cardNoMatchResponse.bind(dc, null).join()); - } - }); + QnADialogResponseOptions options = new QnADialogResponseOptions(); + options.setNoAnswer(noAnswer.bind(dc, dc.getState()).join()); + options.setActiveLearningCardTitle( + activeLearningCardTitle != null ? activeLearningCardTitle : DEFAULT_CARD_TITLE + ); + options.setCardNoMatchText(cardNoMatchText != null ? cardNoMatchText : DEFAULT_CARD_NO_MATCH_TEXT); + options.setCardNoMatchResponse(cardNoMatchResponse.bind(dc, null).join()); + + return CompletableFuture.completedFuture(options); } /** @@ -816,16 +821,18 @@ private static void resetOptions(DialogContext dc, QnAMakerDialogOptions dialogO // -Check if previous context is present, if yes then put it with the query // -Check for id if query is present in reverse index. Map previousContextData = - ObjectPath.getPathValue(dc.getActiveDialog().getState(), QNA_CONTEXT_DATA, Map.class); + ObjectPath.getPathValue( + dc.getActiveDialog().getState(), + QNA_CONTEXT_DATA, + Map.class, + new HashMap()); Integer previousQnAId = ObjectPath.getPathValue(dc.getActiveDialog().getState(), PREVIOUS_QNA_ID, Integer.class, 0); if (previousQnAId > 0) { - dialogOptions.getQnAMakerOptions().setContext(new QnARequestContext() { - { - setPreviousQnAId(previousQnAId); - } - }); + QnARequestContext context = new QnARequestContext(); + context.setPreviousQnAId(previousQnAId); + dialogOptions.getQnAMakerOptions().setContext(context); Integer currentQnAId = previousContextData.get(dc.getContext().getActivity().getText()); if (currentQnAId != null) { @@ -921,19 +928,13 @@ private CompletableFuture callTrain(WaterfallStepContext stepC if (qnaResult != null) { List queryResultArr = new ArrayList(); stepContext.getValues().put(ValueProperty.QNA_DATA, queryResultArr.add(qnaResult)); - FeedbackRecord record = new FeedbackRecord() { - { - setUserId(stepContext.getContext().getActivity().getId()); - setUserQuestion(currentQuery); - setQnaId(qnaResult.getId()); - } - }; + FeedbackRecord record = new FeedbackRecord(); + record.setUserId(stepContext.getContext().getActivity().getId()); + record.setUserQuestion(currentQuery); + record.setQnaId(qnaResult.getId()); FeedbackRecord[] records = {record}; - FeedbackRecords feedbackRecords = new FeedbackRecords() { - { - setRecords(records); - } - }; + FeedbackRecords feedbackRecords = new FeedbackRecords(); + feedbackRecords.setRecords(records); // Call Active Learning Train API return this.getQnAMakerClient(stepContext).thenCompose(qnaClient -> { try { diff --git a/samples/49.qnamaker-all-features/LICENSE b/samples/49.qnamaker-all-features/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/49.qnamaker-all-features/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/49.qnamaker-all-features/README.md b/samples/49.qnamaker-all-features/README.md new file mode 100644 index 000000000..46331b358 --- /dev/null +++ b/samples/49.qnamaker-all-features/README.md @@ -0,0 +1,126 @@ +# QnA Maker + +Bot Framework v4 QnA Maker bot sample. This sample shows how to integrate Multiturn and Active learning in a QnA Maker bot with Java. Click [here][72] to know more about using follow-up prompts to create multiturn conversation. To know more about how to enable and use active learning, click [here][71]. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. + +The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. + +## Concepts introduced in this sample +The [QnA Maker Service][7] enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. +In this sample, we demonstrate +- how to use the Active Learning to generate suggestions for knowledge base. +- how to use the Multiturn experience for the knowledge base. + +# Prerequisites +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/set-up-qnamaker-service-azure) to create a QnA Maker service. +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation) to create multiturn experience. +- Follow instructions [here](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/quickstarts/create-publish-knowledge-base) to import and publish your newly created QnA Maker service. +- Update [application.properties](src/main/resources/application.properties) with your kbid (KnowledgeBase Id), endpointKey and endpointHost. You may also change the default answer by updating `DefaultAnswer` (optional) field. QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). +- (Optional) Follow instructions [here](https://github.com/microsoft/botframework-cli/tree/main/packages/qnamaker) to set up the + QnA Maker CLI to deploy the model. + + +### Create a QnAMaker Application to enable QnA Knowledge Bases + +QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). + +# Configure Cognitive Service Model +- Create a Knowledge Base in QnAMaker Portal. +- Import "smartLightFAQ.tsv" file, in QnAMaker Portal. +- Save and Train the model. +- Create Bot from Publish page. +- Test bot with Web Chat. +- Capture values of settings like "QnAAuthKey" from "Configuration" page of created bot, in Azure Portal. +- Updated application.properties with values as needed. +- Use value of "QnAAuthKey" for setting "QnAEndpointKey". +- Capture KnowledgeBase Id, HostName and EndpointKey current published app + +# Try Active Learning +- Once your QnA Maker service is up and you have published the sample KB, try the following queries to trigger the Train API on the bot. +- Sample query: "light" +- You can observe that, Multiple answers are returned with high score. + +# Try Multi-turn prompt +- Once your QnA Maker service is up and you have published the sample KB, try the following queries to trigger the Train API on the bot. +- Sample query: "won't turn on" +- You can notice a prompt, included as part of answer to query. + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-qnamaker-all-features-sample.jar` + +##### Microsoft Teams channel group chat fix +- Goto `QnABot.java` +- Add References +- Modify `onTurn` function as: + ```java + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + // Teams group chat + if (turnContext.getActivity().getChannelId().equals(Channels.MSTEAMS)) { + turnContext.getActivity().setText(turnContext.getActivity().removeRecipientMention()); + } + + return super.onTurn(turnContext) + // Save any state changes that might have occurred during the turn. + .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) + .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); + } + ``` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +# Deploy the bot to Azure +See [Deploy your Java bot to Azure][50] for instructions. + +The deployment process assumes you have an account on Microsoft Azure and are able to log into the [Microsoft Azure Portal][60]. + +If you are new to Microsoft Azure, please refer to [Getting started with Azure][70] for guidance on how to get started on Azure. + +# Further reading +- [Active learning Documentation][40] +- [Bot Framework Documentation][80] +- [Bot Basics][90] +- [Azure Bot Service Introduction][100] +- [Azure Bot Service Documentation][110] +- [QnA Maker CLI][170] +- [BF-CLI][130] +- [Azure Portal][140] +- [Spring Boot][160] + +[1]: https://dev.botframework.com +[4]: https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[5]: https://github.com/microsoft/botframework-emulator +[6]: https://aka.ms/botframeworkemulator +[7]: https://www.qnamaker.ai + +[40]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/improve-knowledge-base +[50]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0 +[60]: https://portal.azure.com +[70]: https://azure.microsoft.com/get-started/ +[80]: https://docs.botframework.com +[90]: https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0 +[100]: https://docs.microsoft.com/en-us/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0 +[110]: https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0 +[120]: https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest +[130]: https://github.com/microsoft/botframework-cli +[140]: https://portal.azure.com +[150]: https://www.luis.ai +[160]: https://spring.io/projects/spring-boot +[170]: https://github.com/microsoft/botframework-cli/tree/main/packages/qnamaker + +[71]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/improve-knowledge-base +[72]: https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/how-to/multiturn-conversation diff --git a/samples/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json b/samples/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json b/samples/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/49.qnamaker-all-features/pom.xml b/samples/49.qnamaker-all-features/pom.xml new file mode 100644 index 000000000..d0045273f --- /dev/null +++ b/samples/49.qnamaker-all-features/pom.xml @@ -0,0 +1,244 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-qnamaker-all-features + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java QnAMaker Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.qnamaker.all.features.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + com.microsoft.bot + bot-ai-qna + 4.6.0-preview9 + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.qnamaker.all.features.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/Application.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/Application.java new file mode 100644 index 000000000..10e036abc --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/Application.java @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + /** + * The start method. + * + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + UserState userState, + ConversationState conversationState + ) { + BotServices services = new BotServicesImpl(configuration); + RootDialog dialog = new RootDialog(services, configuration); + return new QnABot<>(conversationState, userState, dialog); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServices.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServices.java new file mode 100644 index 000000000..0a907362e --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServices.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.ai.qna.QnAMaker; + +/** + * Interface which contains the QnAMaker application. + */ +public interface BotServices { + + /** + * Get QnAMaker application. + * + * @return QnAMaker application + */ + QnAMaker getQnAMakerService(); +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServicesImpl.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServicesImpl.java new file mode 100644 index 000000000..6d2d0e334 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServicesImpl.java @@ -0,0 +1,81 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.ai.qna.QnAMaker; +import com.microsoft.bot.ai.qna.QnAMakerEndpoint; +import com.microsoft.bot.integration.Configuration; +import org.apache.commons.lang3.StringUtils; + +/** + * Class which implements the BotService interface to handle + * the services attached to the bot. + */ +public class BotServicesImpl implements BotServices { + + private QnAMaker qnAMakerService; + + /** + * Initializes a new instance of the {@link BotServicesImpl} class. + * + * @param configuration A {@link Configuration} which contains the properties of the application.properties + */ + public BotServicesImpl(Configuration configuration) { + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); + qnAMakerEndpoint.setHost(getHostname(configuration.getProperty("QnAEndpointHostName"))); + qnAMakerEndpoint.setEndpointKey(getEndpointKey(configuration)); + QnAMaker qnAMaker = new QnAMaker(qnAMakerEndpoint, null); + this.qnAMakerService = qnAMaker; + } + + /** + * {@inheritDoc} + */ + @Override + public QnAMaker getQnAMakerService() { + return qnAMakerService; + } + + /** + * Get the URL with the hostname value. + * + * @param hostname The hostname value + * @return The URL with the hostname value + */ + private static String getHostname(String hostname) { + if (!hostname.startsWith("https://")) { + hostname = "https://".concat(hostname); + } + + if (!hostname.contains("/v5.0") && !hostname.endsWith("/qnamaker")) { + hostname = hostname.concat("/qnamaker"); + } + + return hostname; + } + + /** + * Get the endpointKey. + * + * @param configuration A {@link Configuration} populated with the application.properties file + * @return The endpointKey + */ + private static String getEndpointKey(Configuration configuration) { + String endpointKey = configuration.getProperty("QnAEndpointKey"); + + if (StringUtils.isBlank(endpointKey)) { + // This features sample is copied as is for "azure bot service" default "createbot" template. + // Post this sample change merged into "azure bot service" template repo, "Azure Bot Service" + // will make the web app config change to use "QnAEndpointKey".But, the the old "QnAAuthkey" + // required for backward compact. This is a requirement from docs to keep app setting name + // consistent with "QnAEndpointKey". This is tracked in Github issue: + // https://github.com/microsoft/BotBuilder-Samples/issues/2532 + + endpointKey = configuration.getProperty("QnAAuthKey"); + } + + return endpointKey; + } +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnABot.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnABot.java new file mode 100644 index 000000000..3bfed648a --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnABot.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.ChannelAccount; + +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * A simple bot that responds to utterances with answers from QnA Maker. + * If an answer is not found for an utterance, the bot responds with help. + * + * @param A {@link Dialog} + */ +public class QnABot extends ActivityHandler { + + private final BotState conversationState; + private final Dialog dialog; + private final BotState userState; + + /** + * Initializes a new instance of the {@link QnABot} class. + * + * @param withConversationState A {@link ConversationState} + * @param withUserState A {@link UserState} + * @param withDialog A {@link Dialog} + */ + public QnABot(ConversationState withConversationState, UserState withUserState, T withDialog) { + this.conversationState = withConversationState; + this.userState = withUserState; + this.dialog = withDialog; + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return super.onTurn(turnContext) + // Save any state changes that might have occurred during the turn. + .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) + .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + // Run the Dialog with the new message Activity. + return Dialog.run( + this.dialog, + turnContext, + this.conversationState.createProperty("DialogState")); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + for (ChannelAccount member: membersAdded) { + if (!member.getId().equals(turnContext.getActivity().getRecipient().getId())) { + turnContext.sendActivity(MessageFactory.text("Hello and welcome!")).thenApply(result -> null); + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnAMakerBaseDialog.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnAMakerBaseDialog.java new file mode 100644 index 000000000..8f03cab7b --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnAMakerBaseDialog.java @@ -0,0 +1,85 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.ai.qna.QnADialogResponseOptions; +import com.microsoft.bot.ai.qna.QnAMakerClient; +import com.microsoft.bot.ai.qna.QnAMakerOptions; +import com.microsoft.bot.ai.qna.dialogs.QnAMakerDialog; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + +import java.util.concurrent.CompletableFuture; + +/** + * QnAMaker action builder class. + */ +public class QnAMakerBaseDialog extends QnAMakerDialog { + + // Dialog Options parameters + private final String defaultCardTitle = "Did you mean:"; + private final String defaultCardNoMatchText = "None of the above."; + private final String defaultCardNoMatchResponse = "Thanks for the feedback."; + + private final BotServices services; + private String defaultAnswer = "No QnAMaker answer found."; + + /** + * Initializes a new instance of the {@link QnAMakerBaseDialog} class. + * Dialog helper to generate dialogs. + * + * @param withServices Bot Services + * @param configuration A {@link Configuration} which contains the properties of the application.properties + */ + public QnAMakerBaseDialog(BotServices withServices, Configuration configuration) { + super(); + this.services = withServices; + if (StringUtils.isNotBlank(configuration.getProperty("DefaultAnswer"))) { + this.defaultAnswer = configuration.getProperty("DefaultAnswer"); + } + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture getQnAMakerClient(DialogContext dc) { + return CompletableFuture.completedFuture(this.services.getQnAMakerService()); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture getQnAMakerOptions(DialogContext dc) { + QnAMakerOptions options = new QnAMakerOptions(); + options.setScoreThreshold(DEFAULT_THRESHOLD); + options.setTop(DEFAULT_TOP_N); + options.setQnAId(0); + options.setRankerType("Default"); + options.setIsTest(false); + return CompletableFuture.completedFuture(options); + } + + /** + * {@inheritDoc} + */ + @Override + protected CompletableFuture getQnAResponseOptions(DialogContext dc) { + Activity defaultAnswerActivity = MessageFactory.text(this.defaultAnswer); + + Activity cardNoMatchResponse = MessageFactory.text(this.defaultCardNoMatchResponse); + + QnADialogResponseOptions responseOptions = new QnADialogResponseOptions(); + responseOptions.setActiveLearningCardTitle(this.defaultCardTitle); + responseOptions.setCardNoMatchText(this.defaultCardNoMatchText); + responseOptions.setNoAnswer(defaultAnswerActivity); + responseOptions.setCardNoMatchResponse(cardNoMatchResponse); + + return CompletableFuture.completedFuture(responseOptions); + } +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/RootDialog.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/RootDialog.java new file mode 100644 index 000000000..d2f7dc85c --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/RootDialog.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.integration.Configuration; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * This is an example root dialog. Replace this with your applications. + */ +public class RootDialog extends ComponentDialog { + + /** + * QnA Maker initial dialog. + */ + private final String initialDialog = "initial-dialog"; + + /** + * Root dialog for this bot. Creates a QnAMakerDialog. + * + * @param services A {@link BotServices} which contains the applications + * @param configuration A {@link Configuration} populated with the application.properties file + */ + public RootDialog(BotServices services, Configuration configuration) { + super("root"); + + this.addDialog(new QnAMakerBaseDialog(services, configuration)); + + WaterfallStep[] waterfallSteps = { + this::initialStep + }; + WaterfallDialog waterfallDialog = new WaterfallDialog(initialDialog, Arrays.asList(waterfallSteps)); + this.addDialog(waterfallDialog); + + // The initial child Dialog to run. + this.setInitialDialogId(initialDialog); + } + + /** + * This is the first step of the WaterfallDialog. + * It kicks off the dialog with the QnA Maker with provided options. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture initialStep(WaterfallStepContext stepContext) { + return stepContext.beginDialog("QnAMakerDialog", null); + } +} diff --git a/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/package-info.java b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/package-info.java new file mode 100644 index 000000000..b4c448591 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the qnamaker-all-features sample. + */ +package com.microsoft.bot.sample.qnamaker.all.features; diff --git a/samples/49.qnamaker-all-features/src/main/resources/application.properties b/samples/49.qnamaker-all-features/src/main/resources/application.properties new file mode 100644 index 000000000..9598d9ac3 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/resources/application.properties @@ -0,0 +1,7 @@ +MicrosoftAppId= +MicrosoftAppPassword= +QnAKnowledgebaseId= +QnAEndpointKey= +QnAEndpointHostName= +DefaultAnswer= +server.port=3978 diff --git a/samples/49.qnamaker-all-features/src/main/resources/log4j2.json b/samples/49.qnamaker-all-features/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/49.qnamaker-all-features/src/main/resources/smartLightFAQ.tsv b/samples/49.qnamaker-all-features/src/main/resources/smartLightFAQ.tsv new file mode 100644 index 000000000..ae1312590 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/resources/smartLightFAQ.tsv @@ -0,0 +1,23 @@ +Question Answer Source Metadata SuggestedQuestions IsContextOnly Prompts QnaId +My Contoso smart light won't turn on. 1. The simplest way to troubleshoot your smart light is to turn it off and on.\n2. Check the connection to the wall outlet to make sure it's plugged in properly. dina-kb-9.tsv [] false [{"displayOrder":0,"qnaId":91,"displayText":"Stopped"}] 90 +Light won't turn on. 1. The simplest way to troubleshoot your smart light is to turn it off and on.\n2. Check the connection to the wall outlet to make sure it's plugged in properly. dina-kb-9.tsv [] false [{"displayOrder":0,"qnaId":91,"displayText":"Stopped"}] 90 +Doesn't work 1. The simplest way to troubleshoot your smart light is to turn it off and on.\n2. Check the connection to the wall outlet to make sure it's plugged in properly. dina-kb-9.tsv [] false [{"displayOrder":0,"qnaId":91,"displayText":"Stopped"}] 90 +My smart light app stopped responding. 1. Restart the app. \n2. If the problem persists, contact support. dina-kb-9.tsv [] false [{"displayOrder":0,"qnaId":95,"displayText":"support"}] 91 +I upgraded the app and it doesn't work anymore. When you upgrade, you need to:\n\n1. Disable Bluetooth, then re-enable it\n2. After reenable, repair your light with the app. dina-kb-9.tsv [] false [] 92 +Light doesn't work after upgrade. When you upgrade, you need to:\n\n1. Disable Bluetooth, then re-enable it\n2. After reenable, repair your light with the app. dina-kb-9.tsv [] false [] 92 +light When you upgrade, you need to:\n\n1. Disable Bluetooth, then re-enable it\n2. After reenable, repair your light with the app. dina-kb-9.tsv [] false [] 92 +How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. dina-kb-9.tsv [] false [] 93 +What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. dina-kb-9.tsv [] false [] 94 +Contact customer support Email us at service@contoso.com or call us at (202) 555-0164 dina-kb-9.tsv [] false [] 95 +I need help Email us at service@contoso.com or call us at (202) 555-0164 dina-kb-9.tsv [] false [] 95 +Help! Email us at service@contoso.com or call us at (202) 555-0164 dina-kb-9.tsv [] false [] 95 +How do I contact support? Email us at service@contoso.com or call us at (202) 555-0164 dina-kb-9.tsv [] false [] 95 +Who should I contact for customer service? Email us at service@contoso.com or call us at (202) 555-0164 dina-kb-9.tsv [] false [] 95 +Troubleshooting Enter a question to search the knowledge base. dina-kb-9.tsv category:troubleshooting [] false [] 96 +Hi Welcome to the **Smart lightbulb** bot. 😎 dina-kb-9.tsv category:chitchat [] false [] 97 +Hello Welcome to the **Smart lightbulb** bot. 😎 dina-kb-9.tsv category:chitchat [] false [] 97 +Start Welcome to the **Smart lightbulb** bot. 😎 dina-kb-9.tsv category:chitchat [] false [] 97 +Begin Welcome to the **Smart lightbulb** bot. 😎 dina-kb-9.tsv category:chitchat [] false [] 97 +Bye 😎 dina-kb-9.tsv category:chitchat [] false [] 98 +End 😎 dina-kb-9.tsv category:chitchat [] false [] 98 +Good bye 😎 dina-kb-9.tsv category:chitchat [] false [] 98 \ No newline at end of file diff --git a/samples/49.qnamaker-all-features/src/main/webapp/META-INF/MANIFEST.MF b/samples/49.qnamaker-all-features/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/49.qnamaker-all-features/src/main/webapp/WEB-INF/web.xml b/samples/49.qnamaker-all-features/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/49.qnamaker-all-features/src/main/webapp/index.html b/samples/49.qnamaker-all-features/src/main/webapp/index.html new file mode 100644 index 000000000..e570d5488 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/main/webapp/index.html @@ -0,0 +1,420 @@ + + + + + + + QnAMakerBot + + + + + +
+
+
+
QnAMakerBot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/49.qnamaker-all-features/src/test/java/com/microsoft/bot/sample/qnamaker/all/features/ApplicationTest.java b/samples/49.qnamaker-all-features/src/test/java/com/microsoft/bot/sample/qnamaker/all/features/ApplicationTest.java new file mode 100644 index 000000000..4933b87c5 --- /dev/null +++ b/samples/49.qnamaker-all-features/src/test/java/com/microsoft/bot/sample/qnamaker/all/features/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.qnamaker.all.features; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 3ca313aea319719c50f742a657f5fdb9677920be Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 22 Mar 2021 10:15:30 -0500 Subject: [PATCH 109/221] Code coverage with Jacoco (#1068) * JaCoCo test coverage * JaCoCo build target setup * Updates to support Jacoco code coverage Co-authored-by: tracyboehrer --- libraries/bot-ai-luis-v3/pom.xml | 19 ---- libraries/bot-ai-qna/pom.xml | 19 ---- libraries/bot-applicationinsights/pom.xml | 19 ---- libraries/bot-azure/pom.xml | 19 ---- libraries/bot-builder/pom.xml | 19 ---- libraries/bot-connector/pom.xml | 20 ---- .../AdditionalPropertiesDeserializer.java | 8 +- libraries/bot-dialogs/pom.xml | 19 ---- .../dialogs/prompts/ChoicePromptTests.java | 2 +- libraries/bot-integration-core/pom.xml | 21 ---- libraries/bot-integration-spring/pom.xml | 21 ---- libraries/bot-schema/pom.xml | 21 ---- pom.xml | 97 +++++++++++-------- 13 files changed, 62 insertions(+), 242 deletions(-) diff --git a/libraries/bot-ai-luis-v3/pom.xml b/libraries/bot-ai-luis-v3/pom.xml index 6ecfe0ba0..5ce30ee1c 100644 --- a/libraries/bot-ai-luis-v3/pom.xml +++ b/libraries/bot-ai-luis-v3/pom.xml @@ -96,25 +96,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-ai-luis-v3 - xml - 256m - - true - - - diff --git a/libraries/bot-ai-qna/pom.xml b/libraries/bot-ai-qna/pom.xml index 189aa41c2..4b7ceaba6 100644 --- a/libraries/bot-ai-qna/pom.xml +++ b/libraries/bot-ai-qna/pom.xml @@ -95,25 +95,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-ai-qna - xml - 256m - - true - - - diff --git a/libraries/bot-applicationinsights/pom.xml b/libraries/bot-applicationinsights/pom.xml index 1d58b99ee..c7bfde216 100644 --- a/libraries/bot-applicationinsights/pom.xml +++ b/libraries/bot-applicationinsights/pom.xml @@ -74,25 +74,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-applicationinsights - xml - 256m - - true - - - diff --git a/libraries/bot-azure/pom.xml b/libraries/bot-azure/pom.xml index b08be1899..12c503b9d 100644 --- a/libraries/bot-azure/pom.xml +++ b/libraries/bot-azure/pom.xml @@ -90,25 +90,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-azure - xml - 256m - - true - - - diff --git a/libraries/bot-builder/pom.xml b/libraries/bot-builder/pom.xml index 392d58389..c69a98262 100644 --- a/libraries/bot-builder/pom.xml +++ b/libraries/bot-builder/pom.xml @@ -114,25 +114,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-builder - xml - 256m - - true - - - diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index 38999fc5a..b0999bc41 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -144,26 +144,6 @@ - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-connector - xml - 256m - - true - - - - org.apache.maven.plugins maven-pmd-plugin diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java index ce3021a55..5e8319cef 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/restclient/serializer/AdditionalPropertiesDeserializer.java @@ -95,9 +95,11 @@ public Object deserialize(JsonParser jp, DeserializationContext ctxt) throws IOE Field[] fields = c.getDeclaredFields(); for (Field field : fields) { JsonProperty property = field.getAnnotation(JsonProperty.class); - String key = property.value().split("((? - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-dialogs - xml - 256m - - true - - - org.apache.maven.plugins maven-pmd-plugin diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java index c6df50191..42ea35187 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -489,7 +489,7 @@ public void ShouldRecognizeAChoice() { // This is being left out for now due to it failing due to an issue in the Text Recognizers library. // It should be worked out in the recognizers and then this test should be enabled again. - //@Test + @Test public void ShouldNotRecognizeOtherText() { ConversationState convoState = new ConversationState(new MemoryStorage()); StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); diff --git a/libraries/bot-integration-core/pom.xml b/libraries/bot-integration-core/pom.xml index e008b0988..0f7251890 100644 --- a/libraries/bot-integration-core/pom.xml +++ b/libraries/bot-integration-core/pom.xml @@ -74,27 +74,6 @@ true - - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-integration-core - xml - 256m - - true - - - -
diff --git a/libraries/bot-integration-spring/pom.xml b/libraries/bot-integration-spring/pom.xml index 8c0ca1e8d..8b3a76590 100644 --- a/libraries/bot-integration-spring/pom.xml +++ b/libraries/bot-integration-spring/pom.xml @@ -94,27 +94,6 @@ true - - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-integration-spring - xml - 256m - - true - - - - diff --git a/libraries/bot-schema/pom.xml b/libraries/bot-schema/pom.xml index 9c51d929c..a1df159c8 100644 --- a/libraries/bot-schema/pom.xml +++ b/libraries/bot-schema/pom.xml @@ -82,27 +82,6 @@ true - - - org.eluder.coveralls - coveralls-maven-plugin - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - - ../../cobertura-report/bot-schema - xml - 256m - - true - - - - diff --git a/pom.xml b/pom.xml index 392c43b48..6832defc3 100644 --- a/pom.xml +++ b/pom.xml @@ -84,6 +84,44 @@ org.apache.maven.plugins maven-checkstyle-plugin + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + + default-report + + report + + + + + + + + + + + + + + + + + + + + + + + + @@ -141,12 +179,8 @@ - org.eluder.coveralls - coveralls-maven-plugin - - - org.codehaus.mojo - cobertura-maven-plugin + org.jacoco + jacoco-maven-plugin org.apache.maven.plugins @@ -504,42 +538,11 @@ - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - - - ./cobertura-report/bot-builder/coverage.xml - - - ./cobertura-report/bot-schema/coverage.xml - - - ./cobertura-report/bot-connector/coverage.xml - - - - UTF-8 - ${env.COVERALLS_GIT_BRANCH} - ${env.COVERALLS_PULL_REQUEST} - ${env.COVERALLS_SERVICE_BUILD_NUMBER} - ${env.COVERALLS_SERVICE_BUILD_URL} - ${env.COVERALLS_SERVICE_JOB_ID} - ${env.COVERALLS_SERVICE_NAME} - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - - xml - - true - + org.jacoco + jacoco-maven-plugin + 0.8.6 + org.sonatype.plugins nexus-staging-maven-plugin @@ -625,6 +628,18 @@ + + org.jacoco + jacoco-maven-plugin + + + + + report + + + + From 5d3d1c3cd74fb45fbb0effefb09ca69be202124a Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 22 Mar 2021 15:37:31 -0500 Subject: [PATCH 110/221] Updates for Issue 1065 (#1071) * Updates for Issue 1065 * Tweak to kick off new build. --- .../com/microsoft/bot/dialogs/Dialog.java | 13 +-- .../bot/dialogs/DialogManagerTests.java | 79 +++++++++++++++++++ 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index cf65a60e2..339c86972 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -311,13 +311,16 @@ protected String onComputeId() { public static CompletableFuture run(Dialog dialog, TurnContext turnContext, StatePropertyAccessor accessor) { DialogSet dialogSet = new DialogSet(accessor); + if (turnContext.getTurnState().get(BotTelemetryClient.class) != null) { + dialogSet.setTelemetryClient(turnContext.getTurnState().get(BotTelemetryClient.class)); + } else if (dialog.getTelemetryClient() != null) { + dialogSet.setTelemetryClient(dialog.getTelemetryClient()); + } else { + dialogSet.setTelemetryClient(new NullBotTelemetryClient()); + } + dialogSet.add(dialog); - dialogSet.setTelemetryClient(dialog.getTelemetryClient()); - // return dialogSet.createContext(turnContext) - // .thenCompose(dialogContext -> continueOrStart(dialogContext, dialog, - // turnContext)) - // .thenApply(result -> null); return dialogSet.createContext(turnContext) .thenAccept(dialogContext -> innerRun(turnContext, dialog.getId(), dialogContext)); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java index 8f376be3b..431631e98 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -3,6 +3,8 @@ package com.microsoft.bot.dialogs; +import java.time.Duration; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -12,13 +14,16 @@ import java.util.function.Supplier; import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.BotTelemetryClient; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.MemoryStorage; import com.microsoft.bot.builder.SendActivitiesHandler; +import com.microsoft.bot.builder.Severity; import com.microsoft.bot.builder.Storage; import com.microsoft.bot.builder.TraceTranscriptLogger; import com.microsoft.bot.builder.TranscriptLoggerMiddleware; import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextImpl; import com.microsoft.bot.builder.skills.SkillConversationReference; import com.microsoft.bot.builder.skills.SkillHandler; import com.microsoft.bot.connector.authentication.AuthenticationConstants; @@ -30,6 +35,7 @@ import com.microsoft.bot.dialogs.prompts.TextPrompt; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ConversationAccount; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.ResultPair; @@ -189,6 +195,22 @@ public void DialogManager_UserState_NestedDialogs_PersistedAcrossConversations() .join(); } + @Test + public void RunShouldSetTelemetryClient() { + TestAdapter adapter = new TestAdapter(); + Dialog dialog = CreateTestDialog("conversation.name"); + ConversationState conversationState = new ConversationState(new MemoryStorage()); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("test-channel"); + ConversationAccount conversation = new ConversationAccount("test-conversation-id"); + activity.setConversation(conversation); + MockBotTelemetryClient telemetryClient = new MockBotTelemetryClient(); + TurnContext turnContext = new TurnContextImpl(adapter, activity); + turnContext.getTurnState().add(BotTelemetryClient.class.getName(), telemetryClient); + Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + Assert.assertEquals(telemetryClient, dialog.getTelemetryClient()); + } + // @Test // public CompletableFuture DialogManager_OnErrorEvent_Leaf() { // TestUtilities.RunTestScript(); @@ -559,4 +581,61 @@ public CompletableFuture resumeDialog(DialogContext outerDc, return outerDc.endDialog(result); } } + + private class MockBotTelemetryClient implements BotTelemetryClient { + + @Override + public void trackAvailability( + String name, + OffsetDateTime timeStamp, + Duration duration, + String runLocation, + boolean success, + String message, + Map properties, + Map metrics + ) { + + } + + @Override + public void trackDependency( + String dependencyTypeName, + String target, + String dependencyName, + String data, + OffsetDateTime startTime, + Duration duration, + String resultCode, + boolean success + ) { + + } + + @Override + public void trackEvent(String eventName, Map properties, Map metrics) { + + } + + @Override + public void trackException(Exception exception, Map properties, Map metrics) { + + } + + @Override + public void trackTrace(String message, Severity severityLevel, Map properties) { + + } + + @Override + public void trackDialogView(String dialogName, Map properties, Map metrics) { + + } + + @Override + public void flush() { + + } + + } } From 2efc03072813f851277a24a6c1b0384ef45ad3b8 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Tue, 23 Mar 2021 13:42:32 -0500 Subject: [PATCH 111/221] Fix bugs around inconsistent locale (#1074) * DialogContext.getLocale port from C# * BotAdapter updates for locale. Co-authored-by: tracyboehrer --- .../com/microsoft/bot/builder/BotAdapter.java | 4 ++++ .../microsoft/bot/builder/TurnContext.java | 2 ++ .../bot/builder/TurnContextImpl.java | 2 -- .../bot/builder/BotAdapterTests.java | 21 +++++++++++++++++++ .../microsoft/bot/dialogs/DialogContext.java | 19 +++++++++++++---- 5 files changed, 42 insertions(+), 6 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java index ecd5690a2..94cdf5662 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotAdapter.java @@ -10,6 +10,7 @@ import com.microsoft.bot.schema.ResourceResponse; import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import org.apache.commons.lang3.NotImplementedException; @@ -196,6 +197,9 @@ protected CompletableFuture runPipeline( // Call any registered Middleware Components looking for ReceiveActivity() if (context.getActivity() != null) { if (!StringUtils.isEmpty(context.getActivity().getLocale())) { + + Locale.setDefault(Locale.forLanguageTag(context.getActivity().getLocale())); + context.setLocale(context.getActivity().getLocale()); } diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java index cea9ebbcf..7db509d7c 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContext.java @@ -24,6 +24,8 @@ * {@link Bot} {@link Middleware} */ public interface TurnContext { + String STATE_TURN_LOCALE = "turn.locale"; + /** * Sends a trace activity to the {@link BotAdapter} for logging purposes. * diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index e64022c1c..3f3b3024c 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -67,8 +67,6 @@ public class TurnContextImpl implements TurnContext, AutoCloseable { */ private Boolean responded = false; - private static final String STATE_TURN_LOCALE = "turn.locale"; - /** * Creates a context object. * diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java index c5293cb15..faf7084d4 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java @@ -47,6 +47,27 @@ public void PassResourceResponsesThrough() { ); } + @Test + public void GetLocaleFromActivity() { + Consumer> validateResponse = (activities) -> { + // no need to do anything. + }; + SimpleAdapter a = new SimpleAdapter(validateResponse); + TurnContextImpl c = new TurnContextImpl(a, new Activity(ActivityTypes.MESSAGE)); + + String activityId = UUID.randomUUID().toString(); + Activity activity = TestMessage.Message(); + activity.setId(activityId); + activity.setLocale("de-DE"); + BotCallbackHandler callback = (turnContext) -> { + Assert.assertEquals("de-DE", turnContext.getActivity().getLocale()); + return CompletableFuture.completedFuture(null); + }; + + a.processRequest(activity, callback).join(); + } + + @Test public void ContinueConversation_DirectMsgAsync() { boolean[] callbackInvoked = new boolean[] { false }; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 7416e814b..0c282e9c8 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -10,6 +10,7 @@ import com.microsoft.bot.connector.Async; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.concurrent.CompletableFuture; import org.apache.commons.lang3.StringUtils; @@ -529,13 +530,24 @@ public CompletableFuture emitEvent(String name, Object value, boolean b } /** - * Obtain the CultureInfo in DialogContext. + * Obtain the locale in DialogContext. * @return A String representing the current locale. */ public String getLocale() { - return getContext() != null ? getContext().getLocale() : null; - } + // turn.locale is the highest precedence. + String locale = (String) state.get(TurnContext.STATE_TURN_LOCALE); + if (!StringUtils.isEmpty(locale)) { + return locale; + } + + // If turn.locale was not populated, fall back to activity locale + locale = getContext().getActivity().getLocale(); + if (!StringUtils.isEmpty(locale)) { + return locale; + } + return Locale.getDefault().toString(); + } /** * @param reason @@ -545,7 +557,6 @@ private CompletableFuture endActiveDialog(DialogReason reason) { return endActiveDialog(reason, null); } - /** * @param reason * @param result From b7c65cac3883012d05ff88d5f2458fc68a55e5f3 Mon Sep 17 00:00:00 2001 From: Victor Grycuk Date: Tue, 23 Mar 2021 16:28:25 -0300 Subject: [PATCH 112/221] [SDK][Bot-Azure] Add AzureQueueStorage component (#1033) * Implement AzureQueueStorage * Add and fix unit tests * Improve createQueueIfNotExists handling * Replace assertEmulator with runIfEmulator * Apply Tracy's feedback * Update libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java Co-authored-by: Martin Battaglino Co-authored-by: Martin Battaglino * Remove double brace to standard initialization Co-authored-by: Martin Battaglino --- libraries/bot-azure/pom.xml | 6 + .../bot/azure/queues/AzureQueueStorage.java | 90 +++++++ .../bot/azure/queues/package-info.java | 8 + .../microsoft/bot/azure/AzureQueueTests.java | 246 ++++++++++++++++++ .../microsoft/bot/builder/QueueStorage.java | 28 ++ .../bot/builder/adapters/TestAdapter.java | 6 +- .../bot/schema/ConversationReference.java | 1 + 7 files changed, 383 insertions(+), 2 deletions(-) create mode 100644 libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java create mode 100644 libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/package-info.java create mode 100644 libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/QueueStorage.java diff --git a/libraries/bot-azure/pom.xml b/libraries/bot-azure/pom.xml index 12c503b9d..54954bae1 100644 --- a/libraries/bot-azure/pom.xml +++ b/libraries/bot-azure/pom.xml @@ -73,6 +73,12 @@ bot-dialogs + + com.azure + azure-storage-queue + 12.8.0 + + com.microsoft.bot bot-builder diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java new file mode 100644 index 000000000..b351cc640 --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure.queues; + +import com.azure.storage.queue.QueueClient; +import com.azure.storage.queue.QueueClientBuilder; +import com.azure.storage.queue.models.SendMessageResult; +import com.microsoft.bot.builder.QueueStorage; +import com.microsoft.bot.restclient.serializer.JacksonAdapter; +import com.microsoft.bot.schema.Activity; +import org.apache.commons.lang3.StringUtils; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.time.Duration; +import java.util.Base64; +import java.util.concurrent.CompletableFuture; + +/** + * Service used to add messages to an Azure.Storage.Queues. + */ +public class AzureQueueStorage extends QueueStorage { + private Boolean createQueueIfNotExists = true; + private final QueueClient queueClient; + + /** + * Initializes a new instance of the {@link AzureQueueStorage} class. + * @param queuesStorageConnectionString Azure Storage connection string. + * @param queueName Name of the storage queue where entities will be queued. + */ + public AzureQueueStorage(String queuesStorageConnectionString, String queueName) { + if (StringUtils.isBlank(queuesStorageConnectionString)) { + throw new IllegalArgumentException("queuesStorageConnectionString is required."); + } + + if (StringUtils.isBlank(queueName)) { + throw new IllegalArgumentException("queueName is required."); + } + + queueClient = new QueueClientBuilder() + .connectionString(queuesStorageConnectionString) + .queueName(queueName) + .buildClient(); + } + + /** + * Queue an Activity to an Azure.Storage.Queues.QueueClient. + * The visibility timeout specifies how long the message should be invisible + * to Dequeue and Peek operations. The message content must be a UTF-8 encoded string that is up to 64KB in size. + * @param activity This is expected to be an {@link Activity} retrieved from a call to + * activity.GetConversationReference().GetContinuationActivity(). + * This enables restarting the conversation using BotAdapter.ContinueConversationAsync. + * @param visibilityTimeout Default value of 0. Cannot be larger than 7 days. + * @param timeToLive Specifies the time-to-live interval for the message. + * @return {@link SendMessageResult} as a Json string, from the QueueClient SendMessageAsync operation. + */ + @Override + public CompletableFuture queueActivity(Activity activity, + @Nullable Duration visibilityTimeout, + @Nullable Duration timeToLive) { + return CompletableFuture.supplyAsync(() -> { + if (createQueueIfNotExists) { + try { + queueClient.create(); + } catch (Exception e) { + throw new RuntimeException(e); + } + + // This is an optimization flag to check if the container creation call has been made. + // It is okay if this is called more than once. + createQueueIfNotExists = false; + } + + try { + JacksonAdapter jacksonAdapter = new JacksonAdapter(); + String serializedActivity = jacksonAdapter.serialize(activity); + byte[] encodedBytes = serializedActivity.getBytes(StandardCharsets.UTF_8); + String encodedString = Base64.getEncoder().encodeToString(encodedBytes); + + SendMessageResult receipt = queueClient.sendMessage(encodedString); + return jacksonAdapter.serialize(receipt); + } catch (IOException e) { + e.printStackTrace(); + } + return null; + }); + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/package-info.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/package-info.java new file mode 100644 index 000000000..07134724a --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for bot-integration-core. + */ +package com.microsoft.bot.azure.queues; diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java new file mode 100644 index 000000000..fa03fa5e3 --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java @@ -0,0 +1,246 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import com.azure.storage.queue.QueueClient; +import com.azure.storage.queue.QueueClientBuilder; +import com.azure.storage.queue.models.QueueMessageItem; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.microsoft.bot.azure.queues.AzureQueueStorage; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.QueueStorage; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogManager; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityEventNames; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ConversationReference; +import org.apache.commons.codec.binary.Base64; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.time.Duration; +import java.time.LocalDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeParseException; +import java.util.Calendar; +import java.util.concurrent.CompletableFuture; + +import com.microsoft.bot.restclient.serializer.JacksonAdapter; + +public class AzureQueueTests { + private static final Integer DEFAULT_DELAY = 2000; + private static boolean emulatorIsRunning = false; + private final String connectionString = "UseDevelopmentStorage=true"; + private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; + + @BeforeClass + public static void allTestsInit() throws IOException, InterruptedException { + Process p = Runtime.getRuntime().exec + ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); + int result = p.waitFor(); + // status = 0: the service was started. + // status = -5: the service is already started. Only one instance of the application + // can be run at the same time. + emulatorIsRunning = result == 0 || result == -5; + } + + // These tests require Azure Storage Emulator v5.7 + public QueueClient containerInit(String name) { + QueueClient queue = new QueueClientBuilder() + .connectionString(connectionString) + .queueName(name) + .buildClient(); + queue.create(); + queue.clearMessages(); + return queue; + } + + @Test + public void continueConversationLaterTests() { + if (runIfEmulator()) { + String queueName = "continueconversationlatertests"; + QueueClient queue = containerInit(queueName); + + ConversationReference cr = TestAdapter.createConversationReference("ContinueConversationLaterTests", "User1", "Bot"); + TestAdapter adapter = new TestAdapter(cr) + .useStorage(new MemoryStorage()) + .useBotState(new ConversationState(new MemoryStorage()), new UserState(new MemoryStorage())); + + AzureQueueStorage queueStorage = new AzureQueueStorage(connectionString, queueName); + + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.SECOND, 2); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); + + ContinueConversationLater ccl = new ContinueConversationLater(); + ccl.setDate(sdf.format(cal.getTime())); + ccl.setValue("foo"); + DialogManager dm = new DialogManager(ccl, "DialogStateProperty"); + dm.getInitialTurnState().replace("QueueStorage", queueStorage); + + new TestFlow(adapter, turnContext -> CompletableFuture.runAsync(() -> dm.onTurn(turnContext))) + .send("hi") + .startTest().join(); + + try { + Thread.sleep(DEFAULT_DELAY); + } catch (InterruptedException e) { + e.printStackTrace(); + Assert.fail(); + } + + QueueMessageItem messages = queue.receiveMessage(); + JacksonAdapter jacksonAdapter = new JacksonAdapter(); + String messageJson = new String(Base64.decodeBase64(messages.getMessageText())); + Activity activity = null; + + try { + activity = jacksonAdapter.deserialize(messageJson, Activity.class); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } + + Assert.assertTrue(activity.isType(ActivityTypes.EVENT)); + Assert.assertEquals(ActivityEventNames.CONTINUE_CONVERSATION, activity.getName()); + Assert.assertEquals("foo", activity.getValue()); + Assert.assertNotNull(activity.getRelatesTo()); + ConversationReference cr2 = activity.getConversationReference(); + cr.setActivityId(null); + cr2.setActivityId(null); + + try { + Assert.assertEquals(jacksonAdapter.serialize(cr), jacksonAdapter.serialize(cr2)); + } catch (IOException e) { + e.printStackTrace(); + Assert.fail(); + } + } + } + + private boolean runIfEmulator() { + if (!emulatorIsRunning) { + System.out.println(NO_EMULATOR_MESSAGE); + return false; + } + + return true; + } + + private class ContinueConversationLater extends Dialog { + @JsonProperty("disabled") + private Boolean disabled = false; + + @JsonProperty("date") + private String date; + + @JsonProperty("value") + private String value; + + /** + * Initializes a new instance of the Dialog class. + */ + public ContinueConversationLater() { + super(ContinueConversationLater.class.getName()); + } + + @Override + public CompletableFuture beginDialog(DialogContext dc, Object options) { + if (this.disabled) { + return dc.endDialog(); + } + + String dateString = this.date; + LocalDateTime date = null; + try { + date = LocalDateTime.parse(dateString); + } catch (DateTimeParseException ex) { + throw new IllegalArgumentException("Date is invalid"); + } + + ZonedDateTime zonedDate = date.atZone(ZoneOffset.UTC); + ZonedDateTime now = LocalDateTime.now().atZone(ZoneOffset.UTC); + if (zonedDate.isBefore(now)) { + throw new IllegalArgumentException("Date must be in the future"); + } + + // create ContinuationActivity from the conversation reference. + Activity activity = dc.getContext().getActivity().getConversationReference().getContinuationActivity(); + activity.setValue(this.value); + + Duration visibility = Duration.between(zonedDate, now); + Duration ttl = visibility.plusMinutes(2); + + QueueStorage queueStorage = dc.getContext().getTurnState().get("QueueStorage"); + if (queueStorage == null) { + throw new NullPointerException("Unable to locate QueueStorage in HostContext"); + } + return queueStorage.queueActivity(activity, visibility, ttl).thenCompose(receipt -> { + // return the receipt as the result + return dc.endDialog(receipt); + }); + } + + /** + * Gets an optional expression which if is true will disable this action. + * "user.age > 18". + * @return A boolean expression. + */ + public Boolean getDisabled() { + return disabled; + } + + /** + * Sets an optional expression which if is true will disable this action. + * "user.age > 18". + * @param withDisabled A boolean expression. + */ + public void setDisabled(Boolean withDisabled) { + this.disabled = withDisabled; + } + + /** + * Gets the expression which resolves to the date/time to continue the conversation. + * @return Date/time string in ISO 8601 format to continue conversation. + */ + public String getDate() { + return date; + } + + /** + * Sets the expression which resolves to the date/time to continue the conversation. + * @param withDate Date/time string in ISO 8601 format to continue conversation. + */ + public void setDate(String withDate) { + this.date = withDate; + } + + /** + * Gets an optional value to use for EventActivity.Value. + * @return The value to use for the EventActivity.Value payload. + */ + public String getValue() { + return value; + } + + /** + * Sets an optional value to use for EventActivity.Value. + * @param withValue The value to use for the EventActivity.Value payload. + */ + public void setValue(String withValue) { + this.value = withValue; + } + } +} diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/QueueStorage.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/QueueStorage.java new file mode 100644 index 000000000..0665e8629 --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/QueueStorage.java @@ -0,0 +1,28 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +import com.microsoft.bot.schema.Activity; + +import javax.annotation.Nullable; +import java.time.Duration; +import java.util.concurrent.CompletableFuture; + +/** + * A base class for enqueueing an Activity for later processing. + */ +public abstract class QueueStorage { + + /** + * Enqueues an Activity for later processing. The visibility timeout specifies how long the message + * should be invisible to Dequeue and Peek operations. + * @param activity The {@link Activity} to be queued for later processing. + * @param visibilityTimeout Visibility timeout. Optional with a default value of 0. Cannot be larger than 7 days. + * @param timeToLive Specifies the time-to-live interval for the message. + * @return A result string. + */ + public abstract CompletableFuture queueActivity(Activity activity, + @Nullable Duration visibilityTimeout, + @Nullable Duration timeToLive); +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index f6f6bb4f8..94674825f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -347,8 +347,10 @@ public Activity makeActivity() { public Activity makeActivity(String withText) { Integer next = nextId++; + String locale = !getLocale().isEmpty() ? getLocale() : "en-us"; Activity activity = new Activity(ActivityTypes.MESSAGE) { { + setLocale(locale); setFrom(conversationReference().getUser()); setRecipient(conversationReference().getBot()); setConversation(conversationReference().getConversation()); @@ -416,8 +418,8 @@ public static ConversationReference createConversationReference(String name, Str reference.setChannelId("test"); reference.setServiceUrl("https://test.com"); reference.setConversation(new ConversationAccount(false, name, name, null, null, null, null)); - reference.setUser(new ChannelAccount(user.toLowerCase(), user.toLowerCase())); - reference.setBot(new ChannelAccount(bot.toLowerCase(), bot.toLowerCase())); + reference.setUser(new ChannelAccount(user.toLowerCase(), user)); + reference.setBot(new ChannelAccount(bot.toLowerCase(), bot)); reference.setLocale("en-us"); return reference; } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java index 4a9f1bd65..bd6d59e3f 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java @@ -79,6 +79,7 @@ public Activity getContinuationActivity() { activity.setConversation(getConversation()); activity.setRecipient(getBot()); activity.setLocale(getLocale()); + activity.setServiceUrl(getServiceUrl()); activity.setFrom(getUser()); activity.setRelatesTo(this); return activity; From 5c00a74e28a61a363a6cec078551d1f785485930 Mon Sep 17 00:00:00 2001 From: Franco Alvarez <51216149+fran893@users.noreply.github.com> Date: Tue, 23 Mar 2021 16:56:56 -0300 Subject: [PATCH 113/221] [SDK][Bot-Azure] Add BlobsStorage component (#1066) * Add azure-storage-blob in pom * Migrate BlobsStorage and BlobsTranscriptStore * Add tests for BlobsStorage * Add statePersistsThroughMultiTurn in StorageBaseTests for parity * Add TestPocoState model * Improve precision for timeStamp adding NanoClockHelper * Add delay of 500ms to separate activities in tests * Remove double braces in GenerateAnswerUtils * Migrate ensureActivityHasId method to improve parity * Apply format changes * Replace assertEmulator with runIfEmulator * Apply Tracy feedback * Rename afterTest to testCleanup Co-authored-by: Martin Battaglino Co-authored-by: Victor Grycuk Co-authored-by: Victor --- .../bot/ai/qna/utils/GenerateAnswerUtils.java | 26 +- .../microsoft/bot/ai/qna/QnAMakerTests.java | 37 +- libraries/bot-azure/pom.xml | 6 + .../bot/azure/blobs/BlobsStorage.java | 255 ++++++++ .../bot/azure/blobs/BlobsTranscriptStore.java | 502 +++++++++++++++ .../bot/azure/blobs/package-info.java | 8 + .../com/microsoft/bot/azure/package-info.java | 2 +- .../bot/azure/TranscriptStoreTests.java | 572 ++++++++++++++++++ .../bot/azure/blobs/BlobsStorageTests.java | 291 +++++++++ .../bot/builder/StorageBaseTests.java | 29 + .../microsoft/bot/builder/TestPocoState.java | 16 + .../bot/builder/TranscriptMiddlewareTest.java | 24 +- .../com/microsoft/bot/schema/Activity.java | 23 +- .../microsoft/bot/schema/NanoClockHelper.java | 65 ++ 14 files changed, 1818 insertions(+), 38 deletions(-) create mode 100644 libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java create mode 100644 libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java create mode 100644 libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/package-info.java create mode 100644 libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java create mode 100644 libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestPocoState.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/NanoClockHelper.java diff --git a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java index 9436b7a7d..52c09b3c0 100644 --- a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java +++ b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java @@ -282,20 +282,18 @@ private CompletableFuture emitTraceInfo( QnAMakerOptions withOptions ) { String knowledgeBaseId = this.endpoint.getKnowledgeBaseId(); - QnAMakerTraceInfo traceInfo = new QnAMakerTraceInfo() { - { - setMessage(messageActivity); - setQueryResults(result); - setKnowledgeBaseId(knowledgeBaseId); - setScoreThreshold(withOptions.getScoreThreshold()); - setTop(withOptions.getTop()); - setStrictFilters(withOptions.getStrictFilters()); - setContext(withOptions.getContext()); - setQnAId(withOptions.getQnAId()); - setIsTest(withOptions.getIsTest()); - setRankerType(withOptions.getRankerType()); - } - }; + QnAMakerTraceInfo traceInfo = new QnAMakerTraceInfo(); + traceInfo.setMessage(messageActivity); + traceInfo.setQueryResults(result); + traceInfo.setKnowledgeBaseId(knowledgeBaseId); + traceInfo.setScoreThreshold(withOptions.getScoreThreshold()); + traceInfo.setTop(withOptions.getTop()); + traceInfo.setStrictFilters(withOptions.getStrictFilters()); + traceInfo.setContext(withOptions.getContext()); + traceInfo.setQnAId(withOptions.getQnAId()); + traceInfo.setIsTest(withOptions.getIsTest()); + traceInfo.setRankerType(withOptions.getRankerType()); + Activity traceActivity = Activity.createTraceActivity( QnAMaker.QNA_MAKER_NAME, QnAMaker.QNA_MAKER_TRACE_TYPE, diff --git a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java index 133c4db45..0dc66d4cb 100644 --- a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java +++ b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java @@ -11,7 +11,6 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; -import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; @@ -126,21 +125,17 @@ public void qnaMakerTraceActivity() { Assert.assertTrue(results.length == 1); Assert.assertEquals("BaseCamp: You can use a damp rag to clean around the Power Pack", results[0].getAnswer()); } - - conversationId[0] = turnContext.getActivity().getConversation().getId(); - Activity typingActivity = new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }; - turnContext.sendActivity(typingActivity).join(); - try { - TimeUnit.SECONDS.sleep(5); - } catch (InterruptedException e) { - // Empty error + delay(500); + conversationId[0] = turnContext.getActivity().getConversation().getId(); + Activity typingActivity = new Activity() { + { + setType(ActivityTypes.TYPING); + setRelatesTo(turnContext.getActivity().getRelatesTo()); } - turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); + }; + turnContext.sendActivity(typingActivity).join(); + delay(500); + turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); return CompletableFuture.completedFuture(null); }) .send("how do I clean the stove?") @@ -2087,6 +2082,18 @@ private void enqueueResponse(MockWebServer mockWebServer, JsonNode response) thr .setBody(mockResponse)); } + /** + * Time period delay. + * @param milliseconds Time to delay. + */ + private void delay(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + public class OverrideTelemetry extends QnAMaker { public OverrideTelemetry(QnAMakerEndpoint endpoint, QnAMakerOptions options, diff --git a/libraries/bot-azure/pom.xml b/libraries/bot-azure/pom.xml index 54954bae1..9ef60875d 100644 --- a/libraries/bot-azure/pom.xml +++ b/libraries/bot-azure/pom.xml @@ -86,6 +86,12 @@ test-jar test + + + com.azure + azure-storage-blob + 12.10.0 + diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java new file mode 100644 index 000000000..19bda96ba --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java @@ -0,0 +1,255 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure.blobs; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.util.Context; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobContainerClientBuilder; +import com.azure.storage.blob.models.BlobErrorCode; +import com.azure.storage.blob.models.BlobRequestConditions; +import com.azure.storage.blob.models.BlobStorageException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.exc.MismatchedInputException; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.StoreItem; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +/** + * Implements {@link Storage} using Azure Storage Blobs. + * This class uses a single Azure Storage Blob Container. + * Each entity or {@link StoreItem} is serialized into a JSON string and stored in an individual text blob. + * Each blob is named after the store item key, which is encoded so that it conforms a valid blob name. + * an entity is an {@link StoreItem}, the storage object will set the entity's {@link StoreItem} + * property value to the blob's ETag upon read. Afterward, an {@link BlobRequestConditions} with the ETag value + * will be generated during Write. New entities start with a null ETag. + */ +public class BlobsStorage implements Storage { + + private ObjectMapper objectMapper; + private final BlobContainerClient containerClient; + + private final Integer millisecondsTimeout = 2000; + private final Integer retryTimes = 8; + + /** + * Initializes a new instance of the {@link BlobsStorage} class. + * @param dataConnectionString Azure Storage connection string. + * @param containerName Name of the Blob container where entities will be stored. + */ + public BlobsStorage(String dataConnectionString, String containerName) { + if (StringUtils.isBlank(dataConnectionString)) { + throw new IllegalArgumentException("dataConnectionString is required."); + } + + if (StringUtils.isBlank(containerName)) { + throw new IllegalArgumentException("containerName is required."); + } + + objectMapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + .findAndRegisterModules() + .enableDefaultTyping(); + + containerClient = new BlobContainerClientBuilder() + .connectionString(dataConnectionString) + .containerName(containerName) + .buildClient(); + } + + /** + * Deletes entity blobs from the configured container. + * @param keys An array of entity keys. + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture delete(String[] keys) { + if (keys == null) { + throw new IllegalArgumentException("The 'keys' parameter is required."); + } + + for (String key: keys) { + String blobName = getBlobName(key); + BlobClient blobClient = containerClient.getBlobClient(blobName); + if (blobClient.exists()) { + try { + blobClient.delete(); + } catch (BlobStorageException e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + } + + return CompletableFuture.completedFuture(null); + } + + /** + * Retrieve entities from the configured blob container. + * @param keys An array of entity keys. + * @return A task that represents the work queued to execute. + */ + @Override + public CompletableFuture> read(String[] keys) { + if (keys == null) { + throw new IllegalArgumentException("The 'keys' parameter is required."); + } + + if (!containerClient.exists()) { + try { + containerClient.create(); + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException(e); + } + } + + Map items = new HashMap<>(); + + for (String key : keys) { + String blobName = getBlobName(key); + BlobClient blobClient = containerClient.getBlobClient(blobName); + innerReadBlob(blobClient).thenAccept(value -> { + if (value != null) { + items.put(key, value); + } + }); + } + return CompletableFuture.completedFuture(items); + } + + /** + * Stores a new entity in the configured blob container. + * @param changes The changes to write to storage. + * @return A task that represents the work queued to execute. + */ + public CompletableFuture write(Map changes) { + if (changes == null) { + throw new IllegalArgumentException("The 'changes' parameter is required."); + } + + if (!containerClient.exists()) { + try { + containerClient.create(); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + for (Map.Entry keyValuePair : changes.entrySet()) { + Object newValue = keyValuePair.getValue(); + StoreItem storeItem = newValue instanceof StoreItem ? (StoreItem) newValue : null; + + // "*" eTag in StoreItem converts to null condition for AccessCondition + boolean isNullOrEmpty = storeItem == null || StringUtils.isBlank(storeItem.getETag()) + || storeItem.getETag().equals("*"); + BlobRequestConditions accessCondition = !isNullOrEmpty + ? new BlobRequestConditions().setIfMatch(storeItem.getETag()) + : null; + + String blobName = getBlobName(keyValuePair.getKey()); + BlobClient blobReference = containerClient.getBlobClient(blobName); + try { + String json = objectMapper.writeValueAsString(newValue); + InputStream stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); + //verify the corresponding length + blobReference.uploadWithResponse(stream, stream.available(), + null, null, + null, null, accessCondition, null, Context.NONE); + } catch (HttpResponseException e) { + if (e.getResponse().getStatusCode() == HttpStatus.SC_BAD_REQUEST) { + StringBuilder sb = + new StringBuilder("An error occurred while trying to write an object. The underlying "); + sb.append(BlobErrorCode.INVALID_BLOCK_LIST); + sb.append(" error is commonly caused due to " + + "concurrently uploading an object larger than 128MB in size."); + + throw new HttpResponseException(sb.toString(), e.getResponse()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + return CompletableFuture.completedFuture(null); + } + + private static String getBlobName(String key) { + if (StringUtils.isBlank(key)) { + throw new IllegalArgumentException("The 'key' parameter is required."); + } + + String blobName; + try { + blobName = URLEncoder.encode(key, StandardCharsets.UTF_8.toString()); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException("The key could not be encoded"); + } + + return blobName; + } + + private CompletableFuture innerReadBlob(BlobClient blobReference) { + Integer i = 0; + while (true) { + try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { + blobReference.download(outputStream); + String contentString = outputStream.toString(); + + Object obj; + // We are doing this try/catch because we are receiving String or HashMap + try { + // We need to deserialize to an Object class since there are contentString which has an Object type + obj = objectMapper.readValue(contentString, Object.class); + } catch (MismatchedInputException ex) { + // In case of the contentString has the structure of a HashMap, + // we need to deserialize it to a HashMap object + obj = objectMapper.readValue(contentString, HashMap.class); + } + + if (obj instanceof StoreItem) { + String eTag = blobReference.getProperties().getETag(); + ((StoreItem) obj).setETag(eTag); + } + + return CompletableFuture.completedFuture(obj); + } catch (HttpResponseException e) { + if (e.getResponse().getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { + // additional retry logic, + // even though this is a read operation blob storage can return 412 if there is contention + if (i++ < retryTimes) { + try { + TimeUnit.MILLISECONDS.sleep(millisecondsTimeout); + continue; + } catch (InterruptedException ex) { + break; + } + } + throw e; + } else { + break; + } + } catch (IOException e) { + e.printStackTrace(); + break; + } + } + return CompletableFuture.completedFuture(null); + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java new file mode 100644 index 000000000..ada2b7c32 --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java @@ -0,0 +1,502 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure.blobs; + +import com.azure.core.exception.HttpResponseException; +import com.azure.core.http.rest.PagedResponse; +import com.azure.storage.blob.BlobClient; +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobContainerClientBuilder; +import com.azure.storage.blob.models.BlobItem; +import com.azure.storage.blob.models.BlobListDetails; +import com.azure.storage.blob.models.ListBlobsOptions; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.microsoft.bot.builder.BotAssert; +import com.microsoft.bot.builder.PagedResult; +import com.microsoft.bot.builder.TranscriptInfo; +import com.microsoft.bot.builder.TranscriptStore; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Pair; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpStatus; + +import javax.annotation.Nullable; +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.UnsupportedEncodingException; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.time.OffsetDateTime; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * The blobs transcript store stores transcripts in an Azure Blob container. + * Each activity is stored as json blob in structure of + * container/{channelId]/{conversationId}/{Timestamp.ticks}-{activity.id}.json. + */ +public class BlobsTranscriptStore implements TranscriptStore { + + // Containers checked for creation. + private static final HashSet CHECKED_CONTAINERS = new HashSet(); + + private final Integer milisecondsTimeout = 2000; + private final Integer retryTimes = 3; + private final Integer longRadix = 16; + private final Integer multipleProductValue = 10_000_000; + + private final ObjectMapper jsonSerializer; + private BlobContainerClient containerClient; + + /** + * Initializes a new instance of the {@link BlobsTranscriptStore} class. + * @param dataConnectionString Azure Storage connection string. + * @param containerName Name of the Blob container where entities will be stored. + */ + public BlobsTranscriptStore(String dataConnectionString, String containerName) { + if (StringUtils.isBlank(dataConnectionString)) { + throw new IllegalArgumentException("dataConnectionString"); + } + + if (StringUtils.isBlank(containerName)) { + throw new IllegalArgumentException("containerName"); + } + + jsonSerializer = new ObjectMapper() + .setSerializationInclusion(JsonInclude.Include.NON_NULL) + .enable(SerializationFeature.INDENT_OUTPUT) + .findAndRegisterModules(); + + // Triggers a check for the existence of the container + containerClient = this.getContainerClient(dataConnectionString, containerName); + } + + /** + * Log an activity to the transcript. + * @param activity Activity being logged. + * @return A CompletableFuture that represents the work queued to execute. + */ + public CompletableFuture logActivity(Activity activity) { + BotAssert.activityNotNull(activity); + + switch (activity.getType()) { + case ActivityTypes.MESSAGE_UPDATE: + Activity updatedActivity = null; + try { + updatedActivity = jsonSerializer + .readValue(jsonSerializer.writeValueAsString(activity), Activity.class); + } catch (IOException ex) { + ex.printStackTrace(); + } + updatedActivity.setType(ActivityTypes.MESSAGE); // fixup original type (should be Message) + Activity finalUpdatedActivity = updatedActivity; + innerReadBlob(activity).thenAccept(activityAndBlob -> { + if (activityAndBlob != null && activityAndBlob.getLeft() != null) { + finalUpdatedActivity.setLocalTimestamp(activityAndBlob.getLeft().getLocalTimestamp()); + finalUpdatedActivity.setTimestamp(activityAndBlob.getLeft().getTimestamp()); + logActivityToBlobClient(finalUpdatedActivity, activityAndBlob.getRight(), true) + .thenApply(task -> CompletableFuture.completedFuture(null)); + } else { + // The activity was not found, so just add a record of this update. + this.innerLogActivity(finalUpdatedActivity) + .thenApply(task -> CompletableFuture.completedFuture(null)); + } + }); + + return CompletableFuture.completedFuture(null); + case ActivityTypes.MESSAGE_DELETE: + innerReadBlob(activity).thenAccept(activityAndBlob -> { + if (activityAndBlob != null && activityAndBlob.getLeft() != null) { + ChannelAccount from = new ChannelAccount(); + from.setId("deleted"); + from.setRole(activityAndBlob.getLeft().getFrom().getRole()); + ChannelAccount recipient = new ChannelAccount(); + recipient.setId("deleted"); + recipient.setRole(activityAndBlob.getLeft().getRecipient().getRole()); + + // tombstone the original message + Activity tombstonedActivity = new Activity(ActivityTypes.MESSAGE_DELETE); + tombstonedActivity.setId(activityAndBlob.getLeft().getId()); + tombstonedActivity.setFrom(from); + tombstonedActivity.setRecipient(recipient); + tombstonedActivity.setLocale(activityAndBlob.getLeft().getLocale()); + tombstonedActivity.setLocalTimestamp(activityAndBlob.getLeft().getTimestamp()); + tombstonedActivity.setTimestamp(activityAndBlob.getLeft().getTimestamp()); + tombstonedActivity.setChannelId(activityAndBlob.getLeft().getChannelId()); + tombstonedActivity.setConversation(activityAndBlob.getLeft().getConversation()); + tombstonedActivity.setServiceUrl(activityAndBlob.getLeft().getServiceUrl()); + tombstonedActivity.setReplyToId(activityAndBlob.getLeft().getReplyToId()); + + logActivityToBlobClient(tombstonedActivity, activityAndBlob.getRight(), true) + .thenApply(task -> CompletableFuture.completedFuture(null)); + } + }); + + return CompletableFuture.completedFuture(null); + default: + this.innerLogActivity(activity) + .thenApply(task -> CompletableFuture.completedFuture(null)); + return CompletableFuture.completedFuture(null); + } + } + + /** + * Get activities for a conversation (Aka the transcript). + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation. + * @param continuationToken The continuation token (if available). + * @param startDate A cutoff date. Activities older than this date are + * not included. + * @return PagedResult of activities. + */ + public CompletableFuture> getTranscriptActivities(String channelId, String conversationId, + @Nullable String continuationToken, + OffsetDateTime startDate) { + if (startDate == null) { + startDate = OffsetDateTime.MIN; + } + + final int pageSize = 20; + + if (StringUtils.isBlank(channelId)) { + throw new IllegalArgumentException("Missing channelId"); + } + + if (StringUtils.isBlank(conversationId)) { + throw new IllegalArgumentException("Missing conversationId"); + } + + PagedResult pagedResult = new PagedResult(); + + String token = null; + List blobs = new ArrayList(); + do { + String prefix = String.format("%s/%s/", sanitizeKey(channelId), sanitizeKey(conversationId)); + Iterable> resultSegment = containerClient + .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) + .iterableByPage(token); + token = null; + for (PagedResponse blobPage: resultSegment) { + for (BlobItem blobItem: blobPage.getValue()) { + OffsetDateTime parseDateTime = OffsetDateTime.parse(blobItem.getMetadata().get("Timestamp")); + if (parseDateTime.isAfter(startDate) + || parseDateTime.isEqual(startDate)) { + if (continuationToken != null) { + if (blobItem.getName().equals(continuationToken)) { + // we found continuation token + continuationToken = null; + } + } else { + blobs.add(blobItem); + if (blobs.size() == pageSize) { + break; + } + } + } + } + + // Get the continuation token and loop until it is empty. + token = blobPage.getContinuationToken(); + } + } while (!StringUtils.isBlank(token) && blobs.size() < pageSize); + + pagedResult.setItems(blobs + .stream() + .map(bl -> { + BlobClient blobClient = containerClient.getBlobClient(bl.getName()); + return this.getActivityFromBlobClient(blobClient); + }) + .map(t -> t.join()) + .collect(Collectors.toList())); + + if (pagedResult.getItems().size() == pageSize) { + pagedResult.setContinuationToken(blobs.get(blobs.size() - 1).getName()); + } + + return CompletableFuture.completedFuture(pagedResult); + } + + /** + * List conversations in the channelId. + * @param channelId The ID of the channel. + * @param continuationToken The continuation token (if available). + * @return A CompletableFuture that represents the work queued to execute. + */ + public CompletableFuture> listTranscripts(String channelId, + @Nullable String continuationToken) { + final int pageSize = 20; + + if (StringUtils.isBlank(channelId)) { + throw new IllegalArgumentException("Missing channelId"); + } + + String token = null; + + List conversations = new ArrayList(); + do { + String prefix = String.format("%s/", sanitizeKey(channelId)); + Iterable> resultSegment = containerClient. + listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) + .iterableByPage(token); + token = null; + for (PagedResponse blobPage: resultSegment) { + for (BlobItem blobItem: blobPage.getValue()) { + // Unescape the Id we escaped when we saved it + String conversationId = new String(); + String lastName = Arrays.stream(blobItem.getName().split("/")) + .reduce((first, second) -> second.length() > 0 ? second : first).get(); + try { + conversationId = URLDecoder.decode(lastName, StandardCharsets.UTF_8.name()); + } catch (UnsupportedEncodingException ex) { + ex.printStackTrace(); + } + TranscriptInfo conversation = + new TranscriptInfo(conversationId, channelId, blobItem.getProperties().getCreationTime()); + if (continuationToken != null) { + if (StringUtils.equals(conversation.getId(), continuationToken)) { + // we found continuation token + continuationToken = null; + } + + // skip record + } else { + conversations.add(conversation); + if (conversations.size() == pageSize) { + break; + } + } + } + } + } while (!StringUtils.isBlank(token) && conversations.size() < pageSize); + + PagedResult pagedResult = new PagedResult(); + pagedResult.setItems(conversations); + + if (pagedResult.getItems().size() == pageSize) { + pagedResult.setContinuationToken(pagedResult.getItems().get(pagedResult.getItems().size() - 1).getId()); + } + + return CompletableFuture.completedFuture(pagedResult); + } + + /** + * Delete a specific conversation and all of it's activities. + * @param channelId The ID of the channel the conversation is in. + * @param conversationId The ID of the conversation to delete. + * @return A CompletableFuture that represents the work queued to execute. + */ + public CompletableFuture deleteTranscript(String channelId, String conversationId) { + if (StringUtils.isBlank(channelId)) { + throw new IllegalArgumentException("Missing channelId"); + } + + if (StringUtils.isBlank(conversationId)) { + throw new IllegalArgumentException("Missing conversationId"); + } + + String token = null; + do { + String prefix = String.format("%s/%s/", sanitizeKey(channelId), sanitizeKey(conversationId)); + Iterable> resultSegment = containerClient + .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null).iterableByPage(token); + token = null; + + for (PagedResponse blobPage: resultSegment) { + for (BlobItem blobItem: blobPage.getValue()) { + BlobClient blobClient = containerClient.getBlobClient(blobItem.getName()); + if (blobClient.exists()) { + try { + blobClient.delete(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + + // Get the continuation token and loop until it is empty. + token = blobPage.getContinuationToken(); + } + } + } while (!StringUtils.isBlank(token)); + + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture> innerReadBlob(Activity activity) { + int i = 0; + while (true) { + try { + String token = null; + do { + String prefix = String.format("%s/%s/", + sanitizeKey(activity.getChannelId()), sanitizeKey(activity.getConversation().getId())); + Iterable> resultSegment = containerClient + .listBlobsByHierarchy("/", + this.getOptionsWithMetadata(prefix), null).iterableByPage(token); + token = null; + for (PagedResponse blobPage: resultSegment) { + for (BlobItem blobItem: blobPage.getValue()) { + if (blobItem.getMetadata().get("Id").equals(activity.getId())) { + BlobClient blobClient = containerClient.getBlobClient(blobItem.getName()); + return this.getActivityFromBlobClient(blobClient) + .thenApply(blobActivity -> + new Pair(blobActivity, blobClient)); + } + } + + // Get the continuation token and loop until it is empty. + token = blobPage.getContinuationToken(); + } + } while (!StringUtils.isBlank(token)); + + return CompletableFuture.completedFuture(null); + } catch (HttpResponseException ex) { + if (ex.getResponse().getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { + // additional retry logic, + // even though this is a read operation blob storage can return 412 if there is contention + if (i++ < retryTimes) { + try { + TimeUnit.MILLISECONDS.sleep(milisecondsTimeout); + continue; + } catch (InterruptedException e) { + break; + } + } + throw ex; + } + // This break will finish the while when the catch if condition is false + break; + } + } + return CompletableFuture.completedFuture(null); + } + + private CompletableFuture getActivityFromBlobClient(BlobClient blobClient) { + ByteArrayOutputStream content = new ByteArrayOutputStream(); + blobClient.download(content); + String contentString = new String(content.toByteArray()); + try { + return CompletableFuture.completedFuture(jsonSerializer.readValue(contentString, Activity.class)); + } catch (IOException ex) { + return CompletableFuture.completedFuture(null); + } + } + + private CompletableFuture innerLogActivity(Activity activity) { + String blobName = this.getBlobName(activity); + BlobClient blobClient = containerClient.getBlobClient(blobName); + return logActivityToBlobClient(activity, blobClient, null); + } + + private CompletableFuture logActivityToBlobClient(Activity activity, BlobClient blobClient, + Boolean overwrite) { + if (overwrite == null) { + overwrite = false; + } + String activityJson = null; + try { + activityJson = jsonSerializer.writeValueAsString(activity); + } catch (IOException ex) { + ex.printStackTrace(); + } + InputStream data = new ByteArrayInputStream(activityJson.getBytes(StandardCharsets.UTF_8)); + + try { + blobClient.upload(data, data.available(), overwrite); + } catch (IOException ex) { + ex.printStackTrace(); + } + Map metaData = new HashMap(); + metaData.put("Id", activity.getId()); + if (activity.getFrom() != null) { + metaData.put("FromId", activity.getFrom().getId()); + } + + if (activity.getRecipient() != null) { + metaData.put("RecipientId", activity.getRecipient().getId()); + } + metaData.put("Timestamp", activity.getTimestamp().toString()); + + blobClient.setMetadata(metaData); + + return CompletableFuture.completedFuture(null); + } + + private String getBlobName(Activity activity) { + String blobName = String.format("%s/%s/%s-%s.json", + sanitizeKey(activity.getChannelId()), sanitizeKey(activity.getConversation().getId()), + this.formatTicks(activity.getTimestamp()), sanitizeKey(activity.getId())); + + return blobName; + } + + private String sanitizeKey(String key) { + // Blob Name rules: case-sensitive any url char + try { + return URLEncoder.encode(key, StandardCharsets.UTF_8.name()); + } catch (Exception ex) { + ex.printStackTrace(); + } + return ""; + } + + private BlobContainerClient getContainerClient(String dataConnectionString, String containerName) { + containerName = containerName.toLowerCase(); + containerClient = new BlobContainerClientBuilder() + .connectionString(dataConnectionString) + .containerName(containerName) + .buildClient(); + if (!CHECKED_CONTAINERS.contains(containerName)) { + CHECKED_CONTAINERS.add(containerName); + if (!containerClient.exists()) { + try { + containerClient.create(); + } catch (Exception ex) { + throw new RuntimeException(ex); + } + } + } + return containerClient; + } + + /** + * Formats a timestamp in a way that is consistent with the C# SDK. + * @param dateTime The dateTime used to get the ticks + * @return The String representing the ticks. + */ + private String formatTicks(OffsetDateTime dateTime) { + final Instant begin = ZonedDateTime.of(1, 1, 1, 0, 0, 0, 0, + ZoneOffset.UTC).toInstant(); + final Instant end = dateTime.toInstant(); + long secsDiff = Math.subtractExact(end.getEpochSecond(), begin.getEpochSecond()); + long totalHundredNanos = Math.multiplyExact(secsDiff, multipleProductValue); + final Long ticks = Math.addExact(totalHundredNanos, (end.getNano() - begin.getNano()) / 100); + return Long.toString(ticks, longRadix); + } + + private ListBlobsOptions getOptionsWithMetadata(String prefix) { + BlobListDetails details = new BlobListDetails(); + details.setRetrieveMetadata(true); + ListBlobsOptions options = new ListBlobsOptions(); + options.setDetails(details); + options.setPrefix(prefix); + return options; + } +} diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/package-info.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/package-info.java new file mode 100644 index 000000000..76f1907f6 --- /dev/null +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for bot-azure. + */ +package com.microsoft.bot.azure.blobs; diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java index 28eafcbba..48232c6e0 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/package-info.java @@ -3,6 +3,6 @@ // license information. /** - * This package contains the classes for bot-integration-core. + * This package contains the classes for bot-azure. */ package com.microsoft.bot.azure; diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java new file mode 100644 index 000000000..b9653bcc4 --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java @@ -0,0 +1,572 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobContainerClientBuilder; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.microsoft.bot.azure.blobs.BlobsTranscriptStore; +import com.microsoft.bot.builder.PagedResult; +import com.microsoft.bot.builder.TranscriptInfo; +import com.microsoft.bot.builder.TranscriptLoggerMiddleware; +import com.microsoft.bot.builder.TranscriptStore; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.ResourceResponse; +import org.apache.commons.lang3.StringUtils; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import java.io.IOException; +import java.time.OffsetDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.UUID; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +/** + * These tests require Azure Storage Emulator v5.7 + * The emulator must be installed at this path C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe + * More info: https://docs.microsoft.com/azure/storage/common/storage-use-emulator + */ +public class TranscriptStoreTests { + + @Rule + public TestName TEST_NAME = new TestName(); + + protected String blobStorageEmulatorConnectionString = + "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"; + + private String channelId = "test"; + + private static final String[] CONVERSATION_IDS = { + "qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm", "123", "456", "789", + "ZAQ", "XSW", "CDE", "VFR", "BGT", "NHY", "NHY", "098", "765", "432", + "zxc", "vbn", "mlk", "jhy", "yui", "kly", "asd", "asw", "aaa", "zzz", + }; + + private static final String[] CONVERSATION_SPECIAL_IDS = { "asd !&/#.'+:?\"", "ASD@123<>|}{][", "$%^;\\*()_" }; + + private String getContainerName() { + return String.format("blobstranscript%s", TEST_NAME.getMethodName().toLowerCase()); + } + + private TranscriptStore getTranscriptStore() { + return new BlobsTranscriptStore(blobStorageEmulatorConnectionString, getContainerName()); + } + + private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; + + @BeforeClass + public static void allTestsInit() throws IOException, InterruptedException { + assertEmulator(); + } + + @After + public void testCleanup() { + BlobContainerClient containerClient = new BlobContainerClientBuilder() + .connectionString(blobStorageEmulatorConnectionString) + .containerName(getContainerName()) + .buildClient(); + + if (containerClient.exists()) { + containerClient.delete(); + } + } + + // These tests require Azure Storage Emulator v5.7 + @Test + public void blobTranscriptParamTest() { + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(null, getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, null)); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(new String(), getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, new String())); + } + + @Test + public void transcriptsEmptyTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + String unusedChannelId = UUID.randomUUID().toString(); + PagedResult transcripts = transcriptStore.listTranscripts(unusedChannelId).join(); + Assert.assertEquals(0, transcripts.getItems().size()); + } + + @Test + public void activityEmptyTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + for(String convoId: CONVERSATION_SPECIAL_IDS) { + PagedResult activities = transcriptStore.getTranscriptActivities(channelId, convoId).join(); + Assert.assertEquals(0, activities.getItems().size()); + } + } + + @Test + public void activityAddTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + Activity[] loggedActivities = new Activity[5]; + List activities = new ArrayList(); + for (int i = 0; i < 5; i++) { + Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_IDS); + transcriptStore.logActivity(a).join(); + activities.add(a); + loggedActivities[i] = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]) + .join().getItems().get(0); + } + + Assert.assertEquals(5, loggedActivities.length); + } + + @Test + public void transcriptRemoveTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + for (int i = 0; i < 5; i++) { + Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_IDS); + transcriptStore.logActivity(a).join(); + transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); + + PagedResult loggedActivities = transcriptStore + .getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join(); + + Assert.assertEquals(0, loggedActivities.getItems().size()); + } + } + + @Test + public void activityAddSpecialCharsTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + Activity[] loggedActivities = new Activity[CONVERSATION_SPECIAL_IDS.length]; + List activities = new ArrayList(); + for (int i = 0; i < CONVERSATION_SPECIAL_IDS.length; i++) { + Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_SPECIAL_IDS); + transcriptStore.logActivity(a).join(); + activities.add(a); + int pos = i; + transcriptStore.getTranscriptActivities(channelId, CONVERSATION_SPECIAL_IDS[i]).thenAccept(result -> { + loggedActivities[pos] = result.getItems().get(0); + }); + } + + Assert.assertEquals(activities.size(), loggedActivities.length); + } + + @Test + public void transcriptRemoveSpecialCharsTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + for (int i = 0; i < CONVERSATION_SPECIAL_IDS.length; i++) { + Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_SPECIAL_IDS); + transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); + + PagedResult loggedActivities = transcriptStore. + getTranscriptActivities(channelId, CONVERSATION_SPECIAL_IDS[i]).join(); + Assert.assertEquals(0, loggedActivities.getItems().size()); + } + } + + @Test + public void activityAddPagedResultTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + String cleanChannel = UUID.randomUUID().toString(); + + List activities = new ArrayList(); + + for (int i = 0; i < CONVERSATION_IDS.length; i++) { + Activity a = TranscriptStoreTests.createActivity(0, i, CONVERSATION_IDS); + a.setChannelId(cleanChannel); + + transcriptStore.logActivity(a).join(); + activities.add(a); + } + + PagedResult loggedPagedResult = transcriptStore.getTranscriptActivities(cleanChannel, CONVERSATION_IDS[0]).join(); + String ct = loggedPagedResult.getContinuationToken(); + Assert.assertEquals(20, loggedPagedResult.getItems().size()); + Assert.assertNotNull(ct); + Assert.assertTrue(loggedPagedResult.getContinuationToken().length() > 0); + loggedPagedResult = transcriptStore.getTranscriptActivities(cleanChannel, CONVERSATION_IDS[0], ct).join(); + ct = loggedPagedResult.getContinuationToken(); + Assert.assertEquals(10, loggedPagedResult.getItems().size()); + Assert.assertNull(ct); + } + + @Test + public void transcriptRemovePagedTest() { + TranscriptStore transcriptStore = getTranscriptStore(); + int i; + for (i = 0; i < CONVERSATION_SPECIAL_IDS.length; i++) { + Activity a = TranscriptStoreTests.createActivity(i ,i , CONVERSATION_IDS); + transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); + } + + PagedResult loggedActivities = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join(); + Assert.assertEquals(0, loggedActivities.getItems().size()); + } + + @Test + public void nullParameterTests() { + TranscriptStore store = getTranscriptStore(); + + Assert.assertThrows(IllegalArgumentException.class, () -> store.logActivity(null)); + Assert.assertThrows(IllegalArgumentException.class, + () -> store.getTranscriptActivities(null, CONVERSATION_IDS[0])); + Assert.assertThrows(IllegalArgumentException.class, () -> store.getTranscriptActivities(channelId, null)); + } + + @Test + public void logActivities() { + TranscriptStore transcriptStore = getTranscriptStore(); + ConversationReference conversation = TestAdapter + .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation) + .use(new TranscriptLoggerMiddleware(transcriptStore)); + new TestFlow(adapter, turnContext -> { + delay(500); + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + turnContext.sendActivity(typingActivity).join(); + delay(500); + turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); + return CompletableFuture.completedFuture(null); + }) + .send("foo") + .assertReply(activity -> + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)) + ) + .assertReply("echo:foo") + .send("bar") + .assertReply(activity -> + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)) + ) + .assertReply("echo:bar") + .startTest().join(); + + PagedResult pagedResult = null; + try { + pagedResult = this.getPagedResult(conversation, 6, null).join(); + } catch (TimeoutException ex) { + Assert.fail(); + } + Assert.assertEquals(6, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertNotNull(pagedResult.getItems().get(1)); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.TYPING)); + Assert.assertTrue(pagedResult.getItems().get(2).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("echo:foo", pagedResult.getItems().get(2).getText()); + Assert.assertTrue(pagedResult.getItems().get(3).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("bar", pagedResult.getItems().get(3).getText()); + Assert.assertNotNull(pagedResult.getItems().get(4)); + Assert.assertTrue(pagedResult.getItems().get(4).isType(ActivityTypes.TYPING)); + Assert.assertTrue(pagedResult.getItems().get(5).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("echo:bar", pagedResult.getItems().get(5).getText()); + for (Activity activity: pagedResult.getItems()) { + Assert.assertTrue(!StringUtils.isBlank(activity.getId())); + Assert.assertTrue(activity.getTimestamp().isAfter(OffsetDateTime.MIN)); + } + } + + @Test + public void logUpdateActivities() { + TranscriptStore transcriptStore = getTranscriptStore(); + ConversationReference conversation = TestAdapter + .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation) + .use(new TranscriptLoggerMiddleware(transcriptStore)); + final Activity[] activityToUpdate = {null}; + new TestFlow(adapter, turnContext -> { + delay(500); + if(turnContext.getActivity().getText().equals("update")) { + activityToUpdate[0].setText("new response"); + turnContext.updateActivity(activityToUpdate[0]).join(); + } else { + Activity activity = turnContext.getActivity().createReply("response"); + ResourceResponse response = turnContext.sendActivity(activity).join(); + activity.setId(response.getId()); + + ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules(); + try { + // clone the activity, so we can use it to do an update + activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), Activity.class); + } catch (JsonProcessingException ex) { + ex.printStackTrace(); + } + } + return CompletableFuture.completedFuture(null); + }).send("foo") + .send("update") + .assertReply("new response") + .startTest().join(); + + PagedResult pagedResult = null; + try { + pagedResult = this.getPagedResult(conversation, 3, null).join(); + } catch (TimeoutException ex) { + Assert.fail(); + } + + Assert.assertEquals(3, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("new response", pagedResult.getItems().get(1).getText()); + Assert.assertTrue(pagedResult.getItems().get(2).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("update", pagedResult.getItems().get(2).getText()); + } + + @Test + public void logMissingUpdateActivity() { + TranscriptStore transcriptStore = getTranscriptStore(); + ConversationReference conversation = TestAdapter + .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation) + .use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] fooId = {new String()}; + ObjectMapper objectMapper = new ObjectMapper() + .findAndRegisterModules(); + new TestFlow(adapter, turnContext -> { + fooId[0] = turnContext.getActivity().getId(); + Activity updateActivity = null; + try { + // clone the activity, so we can use it to do an update + updateActivity = objectMapper.readValue(objectMapper.writeValueAsString(turnContext.getActivity()), Activity.class); + } catch (JsonProcessingException ex) { + ex.printStackTrace(); + } + updateActivity.setText("updated response"); + ResourceResponse response = turnContext.updateActivity(updateActivity).join(); + return CompletableFuture.completedFuture(null); + }).send("foo") + .startTest().join(); + + delay(3000); + + PagedResult pagedResult = null; + try { + pagedResult = this.getPagedResult(conversation, 2, null).join(); + } catch (TimeoutException ex) { + Assert.fail(); + } + + Assert.assertEquals(2, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals(fooId[0], pagedResult.getItems().get(0).getId()); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.MESSAGE)); + Assert.assertTrue(pagedResult.getItems().get(1).getId().startsWith("g_")); + Assert.assertEquals("updated response", pagedResult.getItems().get(1).getText()); + } + + @Test + public void testDateLogUpdateActivities() { + TranscriptStore transcriptStore = getTranscriptStore(); + OffsetDateTime dateTimeStartOffset1 = OffsetDateTime.now(); + ConversationReference conversation = TestAdapter + .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation) + .use(new TranscriptLoggerMiddleware(transcriptStore)); + final Activity[] activityToUpdate = {null}; + new TestFlow(adapter, turnContext -> { + if (turnContext.getActivity().getText().equals("update")) { + activityToUpdate[0].setText("new response"); + turnContext.updateActivity(activityToUpdate[0]).join(); + } else { + Activity activity = turnContext.getActivity().createReply("response"); + + ResourceResponse response = turnContext.sendActivity(activity).join(); + activity.setId(response.getId()); + + ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); + try { + // clone the activity, so we can use it to do an update + activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), Activity.class); + } catch (JsonProcessingException ex) { + ex.printStackTrace(); + } + } + return CompletableFuture.completedFuture(null); + }).send("foo") + .send("update") + .assertReply("new response") + .startTest().join(); + + try { + TimeUnit.MILLISECONDS.sleep(5000); + } catch (InterruptedException e) { + // Empty error + } + + // Perform some queries + PagedResult pagedResult = transcriptStore.getTranscriptActivities( + conversation.getChannelId(), + conversation.getConversation().getId(), + null, + dateTimeStartOffset1).join(); + Assert.assertEquals(3, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("new response", pagedResult.getItems().get(1).getText()); + Assert.assertTrue(pagedResult.getItems().get(2).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("update", pagedResult.getItems().get(2).getText()); + + // Perform some queries + pagedResult = transcriptStore.getTranscriptActivities( + conversation.getChannelId(), + conversation.getConversation().getId(), + null, + OffsetDateTime.MIN).join(); + Assert.assertEquals(3, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("new response", pagedResult.getItems().get(1).getText()); + Assert.assertTrue(pagedResult.getItems().get(2).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("update", pagedResult.getItems().get(2).getText()); + + // Perform some queries + pagedResult = transcriptStore.getTranscriptActivities( + conversation.getChannelId(), + conversation.getConversation().getId(), + null, + OffsetDateTime.MAX).join(); + Assert.assertEquals(0, pagedResult.getItems().size()); + } + + @Test + public void logDeleteActivities() { + TranscriptStore transcriptStore = getTranscriptStore(); + ConversationReference conversation = TestAdapter + .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation) + .use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] activityId = {null}; + new TestFlow(adapter, turnContext -> { + delay(500); + if (turnContext.getActivity().getText().equals("deleteIt")) { + turnContext.deleteActivity(activityId[0]).join(); + } else { + Activity activity = turnContext.getActivity().createReply("response"); + ResourceResponse response = turnContext.sendActivity(activity).join(); + activityId[0] = response.getId(); + } + return CompletableFuture.completedFuture(null); + }).send("foo") + .assertReply("response") + .send("deleteIt") + .startTest().join(); + + PagedResult pagedResult = null; + try { + pagedResult = this.getPagedResult(conversation, 3, null).join(); + } catch (TimeoutException ex) { + Assert.fail(); + } + + Assert.assertEquals(3, pagedResult.getItems().size()); + Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); + Assert.assertNotNull(pagedResult.getItems().get(1)); + Assert.assertTrue(pagedResult.getItems().get(1).isType(ActivityTypes.MESSAGE_DELETE)); + Assert.assertTrue(pagedResult.getItems().get(2).isType(ActivityTypes.MESSAGE)); + Assert.assertEquals("deleteIt", pagedResult.getItems().get(2).getText()); + } + + protected static Activity createActivity(Integer i, Integer j, String[] CONVERSATION_IDS) { + return TranscriptStoreTests.createActivity(j, CONVERSATION_IDS[i]); + } + + private static Activity createActivity(Integer j, String conversationId) { + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId(conversationId); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setId(StringUtils.leftPad(String.valueOf(j + 1), 2, "0")); + activity.setChannelId("test"); + activity.setText("test"); + activity.setConversation(conversationAccount); + activity.setTimestamp(OffsetDateTime.now()); + activity.setFrom(new ChannelAccount("testUser")); + activity.setRecipient(new ChannelAccount("testBot")); + return activity; + } + + /** + * There are some async oddities within TranscriptLoggerMiddleware that make it difficult to set a short delay when + * running this tests that ensures + * the TestFlow completes while also logging transcripts. Some tests will not pass without longer delays, + * but this method minimizes the delay required. + * @param conversation ConversationReference to pass to GetTranscriptActivitiesAsync() + * that contains ChannelId and Conversation.Id. + * @param expectedLength Expected length of pagedResult array. + * @param maxTimeout Maximum time to wait to retrieve pagedResult. + * @return PagedResult. + * @throws TimeoutException + */ + private CompletableFuture> getPagedResult(ConversationReference conversation, + Integer expectedLength, Integer maxTimeout) throws TimeoutException { + TranscriptStore transcriptStore = getTranscriptStore(); + if (maxTimeout == null) { + maxTimeout = 5000; + } + + PagedResult pagedResult = null; + for (int timeout = 0; timeout < maxTimeout; timeout += 500) { + delay(500); + try { + pagedResult = transcriptStore + .getTranscriptActivities(conversation.getChannelId(), conversation.getConversation().getId()).join(); + if (pagedResult.getItems().size() >= expectedLength) { + break; + } + } catch (NoSuchElementException ex) { } + catch (NullPointerException e) { } + } + + if(pagedResult == null) { + throw new TimeoutException("Unable to retrieve pagedResult in time"); + } + + return CompletableFuture.completedFuture(pagedResult); + } + + private static void assertEmulator() throws IOException, InterruptedException { + if (!checkEmulator()) { + Assert.fail(NO_EMULATOR_MESSAGE); + } + } + + private static Boolean checkEmulator() throws IOException, InterruptedException { + Process p = Runtime.getRuntime().exec + ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); + int result = p.waitFor(); + // status = 0: the service was started. + // status = -5: the service is already started. Only one instance of the application + // can be run at the same time. + return result == 0 || result == -5; + } + + /** + * Time period delay. + * @param delay Time to delay. + */ + private void delay(Integer delay) { + try { + Thread.sleep(delay); + } catch (InterruptedException e) { + // Empty error + } + } +} diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java new file mode 100644 index 000000000..9efc4de8c --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java @@ -0,0 +1,291 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.azure.blobs; + +import com.azure.storage.blob.BlobContainerClient; +import com.azure.storage.blob.BlobContainerClientBuilder; +import com.microsoft.bot.builder.BotAdapter; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.StorageBaseTests; +import com.microsoft.bot.builder.StoreItem; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.ConversationReference; +import com.microsoft.bot.schema.ResourceResponse; +import org.junit.After; +import org.junit.Assert; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TestName; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class BlobsStorageTests extends StorageBaseTests { + + @Rule + public TestName testName = new TestName(); + + private final String connectionString = "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"; + + private static boolean emulatorIsRunning = false; + + private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; + + public String getContainerName() { + return "blobs" + testName.getMethodName().toLowerCase().replace("_", ""); + } + + @BeforeClass + public static void allTestsInit() throws IOException, InterruptedException { + Process p = Runtime.getRuntime().exec + ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); + int result = p.waitFor(); + // status = 0: the service was started. + // status = -5: the service is already started. Only one instance of the application + // can be run at the same time. + emulatorIsRunning = result == 0 || result == -5; + } + + @After + public void testCleanup() { + BlobContainerClient containerClient = new BlobContainerClientBuilder() + .connectionString(connectionString) + .containerName(getContainerName()) + .buildClient(); + + if (containerClient.exists()) { + containerClient.delete(); + } + } + + @Test + public void blobStorageParamTest() { + if (runIfEmulator()) { + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(null, getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, null)); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(new String(), getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, new String())); + } + } + + @Test + public void testBlobStorageWriteRead() + { + if (runIfEmulator()) { + // Arrange + Storage storage = new BlobsStorage(connectionString, getContainerName()); + + Map changes = new HashMap(); + changes.put("x", "hello"); + changes.put("y", "world"); + + // Act + storage.write(changes).join(); + Map result = storage.read(new String[] {"x", "y"}).join(); + + // Assert + Assert.assertEquals(2, result.size()); + Assert.assertEquals("hello", result.get("x")); + Assert.assertEquals("world", result.get("y")); + } + } + + @Test + public void testBlobStorageWriteDeleteRead() + { + if (runIfEmulator()) { + // Arrange + Storage storage = new BlobsStorage(connectionString, getContainerName()); + + Map changes = new HashMap(); + changes.put("x", "hello"); + changes.put("y", "world"); + + // Act + storage.write(changes).join(); + storage.delete(new String[] { "x" }).join(); + Map result = storage.read(new String[] {"x", "y"}).join(); + + // Assert + Assert.assertEquals(1, result.size()); + Assert.assertEquals("world", result.get("y")); + } + } + + @Test + public void testBlobStorageChanges() { + if (runIfEmulator()) { + // Arrange + Storage storage = new BlobsStorage(connectionString, getContainerName()); + + // Act + Map changes = new HashMap(); + changes.put("a", "1.0"); + changes.put("b", "2.0"); + storage.write(changes).join(); + + changes.clear(); + changes.put("c", "3.0"); + storage.write(changes).join(); + storage.delete(new String[] { "b" }).join(); + + changes.clear(); + changes.put("a", "1.1"); + storage.write(changes).join(); + + Map result = storage.read(new String[] { "a", "b", "c", "d", "e" }).join(); + + // Assert + Assert.assertEquals(2, result.size()); + Assert.assertEquals("1.1", result.get("a")); + Assert.assertEquals("3.0", result.get("c")); + } + } + + @Test + public void testConversationStateBlobStorage() { + if (runIfEmulator()) { + // Arrange + Storage storage = new BlobsStorage(connectionString, getContainerName()); + + ConversationState conversationState = new ConversationState(storage); + StatePropertyAccessor propAccessor = conversationState.createProperty("prop"); + + TestStorageAdapter adapter = new TestStorageAdapter(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("123"); + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId("abc"); + activity.setConversation(conversationAccount); + + // Act + TurnContext turnContext1 = new TurnContextImpl(adapter, activity); + Prop propValue1 = propAccessor.get(turnContext1, Prop::new).join(); + propValue1.setX("hello"); + propValue1.setY("world"); + conversationState.saveChanges(turnContext1).join(); + + TurnContext turnContext2 = new TurnContextImpl(adapter, activity); + Prop propValue2 = propAccessor.get(turnContext2).join(); + + // Assert + Assert.assertEquals("hello", propValue2.getX()); + Assert.assertEquals("world", propValue2.getY()); + + propAccessor.delete(turnContext1).join(); + conversationState.saveChanges(turnContext1).join(); + } + } + + @Test + public void testConversationStateBlobStorage_TypeNameHandlingDefault() { + if (runIfEmulator()) { + Storage storage = new BlobsStorage(connectionString, getContainerName()); + testConversationStateBlobStorage_Method(storage); + } + } + + @Test + public void statePersistsThroughMultiTurn_TypeNameHandlingNone() { + if (runIfEmulator()) { + Storage storage = new BlobsStorage(connectionString, getContainerName()); + statePersistsThroughMultiTurn(storage); + } + } + + private void testConversationStateBlobStorage_Method(Storage blobs) { + if (runIfEmulator()) { + // Arrange + ConversationState conversationState = new ConversationState(blobs); + StatePropertyAccessor propAccessor = conversationState.createProperty("prop"); + TestStorageAdapter adapter = new TestStorageAdapter(); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("123"); + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId("abc"); + activity.setConversation(conversationAccount); + + // Act + TurnContext turnContext1 = new TurnContextImpl(adapter, activity); + Prop propValue1 = propAccessor.get(turnContext1, Prop::new).join(); + propValue1.setX("hello"); + propValue1.setY("world"); + conversationState.saveChanges(turnContext1).join(); + + TurnContext turnContext2 = new TurnContextImpl(adapter, activity); + Prop propValue2 = propAccessor.get(turnContext2).join(); + + // Assert + Assert.assertEquals("hello", propValue2.getX()); + Assert.assertEquals("world", propValue2.getY()); + } + } + + private boolean runIfEmulator() { + if (!emulatorIsRunning) { + System.out.println(NO_EMULATOR_MESSAGE); + return false; + } + + return true; + } + + private class TestStorageAdapter extends BotAdapter { + + @Override + public CompletableFuture sendActivities(TurnContext context, List activities) { + throw new UnsupportedOperationException(); + } + + @Override + public CompletableFuture updateActivity(TurnContext context, Activity activity) { + throw new UnsupportedOperationException(); + } + + @Override + public CompletableFuture deleteActivity(TurnContext context, ConversationReference reference) { + throw new UnsupportedOperationException(); + } + } + + private static class Prop { + private String X; + private String Y; + StoreItem storeItem; + + public String getX() { + return X; + } + + public void setX(String x) { + X = x; + } + + public String getY() { + return Y; + } + + public void setY(String y) { + Y = y; + } + + public StoreItem getStoreItem() { + return storeItem; + } + + public void setStoreItem(StoreItem storeItem) { + this.storeItem = storeItem; + } + } +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java index 0d7e15674..142e921fc 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java @@ -3,10 +3,13 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; import org.junit.Assert; import java.util.HashMap; import java.util.Map; +import java.util.concurrent.CompletableFuture; public class StorageBaseTests { protected void readUnknownTest(Storage storage) { @@ -241,6 +244,32 @@ protected void deleteUnknownObjectTest(Storage storage) { storage.delete(new String[] { "unknown_key" }).join(); } + protected void statePersistsThroughMultiTurn(Storage storage) { + UserState userState = new UserState(storage); + StatePropertyAccessor testProperty = userState.createProperty("test"); + TestAdapter adapter = new TestAdapter() + .use(new AutoSaveStateMiddleware(userState)); + + new TestFlow(adapter, context -> { + TestPocoState state = testProperty.get(context, TestPocoState::new).join(); + Assert.assertNotNull(state); + switch (context.getActivity().getText()) { + case "set value": + state.setValue("test"); + context.sendActivity("value saved").join(); + break; + case "get value": + context.sendActivity(state.getValue()).join(); + break; + } + + return CompletableFuture.completedFuture(null); + }) + .test("set value", "value saved") + .test("get value", "test") + .startTest().join(); + } + private static class PocoItem { public PocoItem() { diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestPocoState.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestPocoState.java new file mode 100644 index 000000000..e17d40e3d --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestPocoState.java @@ -0,0 +1,16 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.builder; + +public class TestPocoState { + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + private String value; +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java index 0945d80dd..7a1b83598 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java @@ -56,6 +56,7 @@ public final void Transcript_LogActivities() { final String[] conversationId = { null }; new TestFlow(adapter, (context) -> { + delay(500); conversationId[0] = context.getActivity().getConversation().getId(); Activity typingActivity = new Activity(ActivityTypes.TYPING) { { @@ -65,12 +66,7 @@ public final void Transcript_LogActivities() { context.sendActivity(typingActivity).join(); - try { - Thread.sleep(500); - } catch (InterruptedException e) { - e.printStackTrace(); - Assert.fail(); - } + delay(500); context.sendActivity("echo:" + context.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); @@ -109,6 +105,7 @@ public void Transcript_LogUpdateActivities() { final String[] conversationId = { null }; final Activity[] activityToUpdate = { null }; new TestFlow(adapter, (context) -> { + delay(500); conversationId[0] = context.getActivity().getConversation().getId(); if (context.getActivity().getText().equals("update")) { activityToUpdate[0].setText("new response"); @@ -152,6 +149,7 @@ public final void Transcript_LogDeleteActivities() { final String[] conversationId = { null }; final String[] activityId = { null }; new TestFlow(adapter, (context) -> { + delay(500); conversationId[0] = context.getActivity().getConversation().getId(); if (context.getActivity().getText().equals("deleteIt")) { context.deleteActivity(activityId[0]).join(); @@ -210,6 +208,7 @@ public void Transcript_TestDateLogUpdateActivities() { final String[] conversationId = { null }; final Activity[] activityToUpdate = { null }; new TestFlow(adapter, (context) -> { + delay(500); conversationId[0] = context.getActivity().getConversation().getId(); if (context.getActivity().getText().equals("update")) { activityToUpdate[0].setText("new response"); @@ -278,6 +277,7 @@ public final void Transcript_RolesAreFilled() { final String[] conversationId = { null }; new TestFlow(adapter, (context) -> { + delay(500); // The next assert implicitly tests the immutability of the incoming // message. As demonstrated by the asserts after this TestFlow block // the role attribute is present on the activity as it is passed to @@ -300,4 +300,16 @@ public final void Transcript_RolesAreFilled() { System.out.printf("Complete"); } + + /** + * Time period delay. + * @param milliseconds Time to delay. + */ + private void delay(int milliseconds) { + try { + Thread.sleep(milliseconds); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index 5cdbe13a5..f442fb2df 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -19,14 +19,15 @@ import com.microsoft.bot.schema.teams.TeamsMeetingInfo; import org.apache.commons.lang3.StringUtils; +import java.time.Clock; import java.time.LocalDateTime; import java.time.OffsetDateTime; -import java.time.ZoneId; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; import java.util.stream.Collectors; /** @@ -212,7 +213,8 @@ public class Activity { * normally required. */ protected Activity() { - setTimestamp(OffsetDateTime.now(ZoneId.of("UTC"))); + final Clock clock = new NanoClockHelper(); + setTimestamp(OffsetDateTime.now(clock)); } /** @@ -407,9 +409,26 @@ public static Activity clone(Activity activity) { clone.setProperties(entry.getKey(), entry.getValue()); } + clone = ensureActivityHasId(clone); + return clone; } + private static Activity ensureActivityHasId(Activity activity) { + Activity activityWithId = activity; + + if (activity == null) { + throw new IllegalArgumentException("Cannot check or add Id on a null Activity."); + } + + if (activity.getId() == null) { + String generatedId = String.format("g_%s", UUID.randomUUID().toString()); + activity.setId(generatedId); + } + + return activityWithId; + } + /** * Gets the {@link ActivityTypes} of the activity. * diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/NanoClockHelper.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/NanoClockHelper.java new file mode 100644 index 000000000..20496d036 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/NanoClockHelper.java @@ -0,0 +1,65 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +import java.time.Clock; +import java.time.Instant; +import java.time.ZoneId; + +/** + * A customized nanoseconds clock providing access to the current instant, date and time using a time-zone. + */ +public class NanoClockHelper extends Clock { + + private final Clock clock; + private final long initialNanos; + private final Instant initialInstant; + + /** + * Obtains a clock that returns the current instant using the best available + * system clock with nanoseconds. + */ + public NanoClockHelper() { + this(Clock.systemUTC()); + } + + /** + * Obtains a clock that returns the current instant using the best available + * system clock with nanoseconds. + * @param clock A {@link Clock} + */ + public NanoClockHelper(final Clock clock) { + this.clock = clock; + initialInstant = clock.instant(); + initialNanos = getSystemNanos(); + } + + /** + * {@inheritDoc} + */ + @Override + public ZoneId getZone() { + return clock.getZone(); + } + + /** + * {@inheritDoc} + */ + @Override + public Instant instant() { + return initialInstant.plusNanos(getSystemNanos() - initialNanos); + } + + /** + * {@inheritDoc} + */ + @Override + public Clock withZone(final ZoneId zone) { + return new NanoClockHelper(clock.withZone(zone)); + } + + private long getSystemNanos() { + return System.nanoTime(); + } +} From c01ecdd56d244e408989260a635b9283739462d5 Mon Sep 17 00:00:00 2001 From: Franco Alvarez <51216149+fran893@users.noreply.github.com> Date: Tue, 23 Mar 2021 18:07:08 -0300 Subject: [PATCH 114/221] [SDK] Add bot-applicationinsights package (#1075) * Create main structure * Add pom file * Add main classes * Add TelemetryInitializerMiddleware in core folder * Add package-info files * Add MyBotTelemetryClient class in test folder * Add unit tests * Fix issue in WaterfallDialog class Co-authored-by: Federico Bernal <64086728+FedericoBernal@users.noreply.github.com> Co-authored-by: Martin Battaglino --- libraries/bot-applicationinsights/pom.xml | 13 + .../AvailabilityTelemetry.java | 217 ++++++++++++ .../BotTelemetryClientImpl.java | 247 +++++++++++++ .../core/TelemetryInitializerMiddleware.java | 74 ++++ .../core/package-info.java | 8 + .../bot/applicationinsights/package-info.java | 8 + .../BotTelemetryClientTests.java | 183 ++++++++++ .../MyBotTelemetryClient.java | 87 +++++ .../TelemetryInitializerTests.java | 132 +++++++ .../TelemetryWaterfallTests.java | 328 ++++++++++++++++++ .../bot/dialogs/WaterfallDialog.java | 4 +- 11 files changed, 1299 insertions(+), 2 deletions(-) create mode 100644 libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java create mode 100644 libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java create mode 100644 libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java create mode 100644 libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/package-info.java create mode 100644 libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/package-info.java create mode 100644 libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java create mode 100644 libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java create mode 100644 libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryInitializerTests.java create mode 100644 libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryWaterfallTests.java diff --git a/libraries/bot-applicationinsights/pom.xml b/libraries/bot-applicationinsights/pom.xml index c7bfde216..1dc45c1cf 100644 --- a/libraries/bot-applicationinsights/pom.xml +++ b/libraries/bot-applicationinsights/pom.xml @@ -60,9 +60,22 @@ 2.4.1 + + com.microsoft.bot + bot-dialogs + + + org.mockito + mockito-core + test + + com.microsoft.bot bot-builder + ${project.version} + test-jar + test diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java new file mode 100644 index 000000000..80911aa58 --- /dev/null +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java @@ -0,0 +1,217 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.applicationinsights.internal.schemav2.AvailabilityData; +import com.microsoft.applicationinsights.internal.util.LocalStringsUtils; +import com.microsoft.applicationinsights.internal.util.Sanitizer; +import com.microsoft.applicationinsights.telemetry.BaseSampleSourceTelemetry; +import com.microsoft.applicationinsights.telemetry.Duration; +import java.util.Date; +import java.util.concurrent.ConcurrentMap; + +/** + * We took this class from https://github.com/microsoft/ApplicationInsights-Java/issues/1099 + * as this is not already migrated in ApplicationInsights-Java library. + */ +public final class AvailabilityTelemetry extends BaseSampleSourceTelemetry { + private Double samplingPercentage; + private final AvailabilityData data; + + public static final String ENVELOPE_NAME = "Availability"; + + public static final String BASE_TYPE = "AvailabilityData"; + + + /** + * Initializes a new instance of the AvailabilityTelemetry class. + */ + public AvailabilityTelemetry() { + this.data = new AvailabilityData(); + initialize(this.data.getProperties()); + setId(LocalStringsUtils.generateRandomIntegerId()); + + // Setting mandatory fields. + setTimestamp(new Date()); + setSuccess(true); + } + + /** + * Initializes a new instance of the AvailabilityTelemetry class with the given name, + * time stamp, duration, HTTP response code and success property values. + * @param name A user-friendly name for the request. + * @param duration The time of the request. + * @param runLocation The duration, in milliseconds, of the request processing. + * @param message The HTTP response code. + * @param success 'true' if the request was a success, 'false' otherwise. + * @param measurements The measurements. + * @param properties The corresponding properties. + */ + public AvailabilityTelemetry(String name, Duration duration, String runLocation, String message, + boolean success, ConcurrentMap measurements, + ConcurrentMap properties) { + + this.data = new AvailabilityData(); + + this.data.setProperties(properties); + this.data.setMeasurements(measurements); + this.data.setMessage(message); + + initialize(this.data.getProperties()); + + setId(LocalStringsUtils.generateRandomIntegerId()); + + setTimestamp(new Date()); + + setName(name); + setRunLocation(runLocation); + setDuration(duration); + setSuccess(success); + } + + + /** + * Gets the ver value from the data object. + * @return The ver value. + */ + @Override + public int getVer() { + return getData().getVer(); + } + + /** + * Gets a map of application-defined request metrics. + * @return The map of metrics + */ + public ConcurrentMap getMetrics() { + return data.getMeasurements(); + } + + /** + * Sets the StartTime. Uses the default behavior and sets the property on the 'data' start time. + * @param timestamp The timestamp as Date. + */ + @Override + public void setTimestamp(Date timestamp) { + if (timestamp == null) { + timestamp = new Date(); + } + + super.setTimestamp(timestamp); + } + + /** + * Gets or human-readable name of the requested page. + * @return A human-readable name. + */ + public String getName() { + return data.getName(); + } + + /** + * Sets or human-readable name of the requested page. + * @param name A human-readable name. + */ + public void setName(String name) { + data.setName(name); + } + + /** + * Gets or human-readable name of the run location. + * @return A human-readable name. + */ + public String getRunLocation() { + return data.getRunLocation(); + } + + /** + * Sets or human-readable name of the run location. + * @param runLocation A human-readable name + */ + public void setRunLocation(String runLocation) { + data.setRunLocation(runLocation); + } + + /** + * Gets the unique identifier of the request. + * @return Unique identifier. + */ + public String getId() { + return data.getId(); + } + + /** + * Sets the unique identifier of the request. + * @param id Unique identifier. + */ + public void setId(String id) { + data.setId(id); + } + + /** + * Gets a value indicating whether application handled the request successfully. + * @return Success indication. + */ + public boolean isSuccess() { + return data.getSuccess(); + } + + /** + * Sets a value indicating whether application handled the request successfully. + * @param success Success indication. + */ + public void setSuccess(boolean success) { + data.setSuccess(success); + } + + /** + * Gets the amount of time it took the application to handle the request. + * @return Amount of time in milliseconds. + */ + public Duration getDuration() { + return data.getDuration(); + } + + /** + * Sets the amount of time it took the application to handle the request. + * @param duration Amount of time in captured in a {@link com.microsoft.applicationinsights.telemetry.Duration}. + */ + public void setDuration(Duration duration) { + data.setDuration(duration); + } + + @Override + public Double getSamplingPercentage() { + return samplingPercentage; + } + + @Override + public void setSamplingPercentage(Double samplingPercentage) { + this.samplingPercentage = samplingPercentage; + } + + @Override + @Deprecated + protected void additionalSanitize() { + data.setName(Sanitizer.sanitizeName(data.getName())); + data.setId(Sanitizer.sanitizeName(data.getId())); + Sanitizer.sanitizeMeasurements(getMetrics()); + } + + @Override + protected AvailabilityData getData() { + return data; + } + + @Override + public String getEnvelopName() { + return ENVELOPE_NAME; + } + + @Override + public String getBaseTypeName() { + return BASE_TYPE; + } +} + diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java new file mode 100644 index 000000000..5a574164b --- /dev/null +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java @@ -0,0 +1,247 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.telemetry.EventTelemetry; +import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; +import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.SeverityLevel; +import com.microsoft.applicationinsights.telemetry.TraceTelemetry; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.Severity; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A logging client for bot telemetry. + */ +public class BotTelemetryClientImpl implements BotTelemetryClient { + + private final TelemetryClient telemetryClient; + + /** + * Initializes a new instance of the {@link BotTelemetryClient}. + * + * @param withTelemetryClient The telemetry client to forward bot events to. + */ + public BotTelemetryClientImpl(TelemetryClient withTelemetryClient) { + if (withTelemetryClient == null) { + throw new IllegalArgumentException("withTelemetry should be provided"); + } + this.telemetryClient = withTelemetryClient; + } + + /** + * Send information about availability of an application. + * + * @param name Availability test name. + * @param timeStamp The time when the availability was captured. + * @param duration The time taken for the availability test to run. + * @param runLocation Name of the location the availability test was run from. + * @param success True if the availability test ran successfully. + * @param message Error message on availability test run failure. + * @param properties Named string values you can use to classify and search for this availability telemetry. + * @param metrics Additional values associated with this availability telemetry. + */ + @SuppressWarnings("checkstyle:ParameterNumber") + @Override + public void trackAvailability(String name, + OffsetDateTime timeStamp, + Duration duration, + String runLocation, + boolean success, + String message, + Map properties, + Map metrics) { + com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = + new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); + ConcurrentMap concurrentProperties = new ConcurrentHashMap<>(properties); + ConcurrentMap concurrentMetrics = new ConcurrentHashMap<>(metrics); + AvailabilityTelemetry telemetry = new AvailabilityTelemetry( + name, + durationTelemetry, + runLocation, + message, + success, + concurrentMetrics, + concurrentProperties); + if (properties != null) { + for (Map.Entry pair: properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair: metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + /** + * This should be telemetryClient.trackAvailability(telemetry). + * However, it is not present in TelemetryClient class + */ + telemetryClient.track(telemetry); + } + + /** + * Send information about an external dependency (outgoing call) in the application. + * + * @param dependencyTypeName Name of the command initiated with this dependency call. Low cardinality value. + * Examples are SQL, Azure table, and HTTP. + * @param target External dependency target. + * @param dependencyName Name of the command initiated with this dependency call. Low cardinality value. + * Examples are stored procedure name and URL path template. + * @param data Command initiated by this dependency call. Examples are SQL statement and HTTP + * URL's with all query parameters. + * @param startTime The time when the dependency was called. + * @param duration The time taken by the external dependency to handle the call. + * @param resultCode Result code of dependency call execution. + * @param success True if the dependency call was handled successfully. + */ + @SuppressWarnings("checkstyle:ParameterNumber") + @Override + public void trackDependency(String dependencyTypeName, + String target, + String dependencyName, + String data, + OffsetDateTime startTime, + Duration duration, + String resultCode, + boolean success) { + com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = + new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); + + RemoteDependencyTelemetry telemetry = + new RemoteDependencyTelemetry(dependencyName, data, durationTelemetry, success); + + telemetry.setType(dependencyTypeName); + telemetry.setTarget(target); + telemetry.setTimestamp(new Date(startTime.toInstant().toEpochMilli())); + telemetry.setResultCode(resultCode); + + telemetryClient.trackDependency(telemetry); + } + + /** + * Logs custom events with extensible named fields. + * + * @param eventName A name for the event. + * @param properties Named string values you can use to search and classify events. + * @param metrics Measurements associated with this event. + */ + @Override + public void trackEvent(String eventName, Map properties, Map metrics) { + EventTelemetry telemetry = new EventTelemetry(eventName); + if (properties != null) { + for (Map.Entry pair: properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair: metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackEvent(telemetry); + } + + /** + * Logs a system exception. + * + * @param exception The exception to log. + * @param properties Named string values you can use to classify and search for this exception. + * @param metrics Additional values associated with this exception + */ + @Override + public void trackException(Exception exception, Map properties, Map metrics) { + ExceptionTelemetry telemetry = new ExceptionTelemetry(exception); + if (properties != null) { + for (Map.Entry pair: properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair: metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackException(telemetry); + } + + /** + * Send a trace message. + * + * @param message Message to display. + * @param severityLevel Trace severity level {@link Severity}. + * @param properties Named string values you can use to search and classify events. + */ + @Override + public void trackTrace(String message, Severity severityLevel, Map properties) { + TraceTelemetry telemetry = new TraceTelemetry(message); + telemetry.setSeverityLevel(SeverityLevel.values()[severityLevel.ordinal()]); + + if (properties != null) { + for (Map.Entry pair: properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackTrace(telemetry); + } + + /** + * We implemented this method calling the tracePageView method from {@link BotTelemetryClientImpl} as the + * IBotPageViewTelemetryClient has not been implemented. + * {@inheritDoc} + */ + @Override + public void trackDialogView(String dialogName, Map properties, Map metrics) { + trackPageView(dialogName, properties, metrics); + } + + /** + * Logs a dialog entry / as an Application Insights page view. + * + * @param dialogName The name of the dialog to log the entry / start for. + * @param properties Named string values you can use to search and classify events. + * @param metrics Measurements associated with this event. + */ + public void trackPageView(String dialogName, Map properties, Map metrics) { + PageViewTelemetry telemetry = new PageViewTelemetry(dialogName); + + if (properties != null) { + for (Map.Entry pair: properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair: metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackPageView(telemetry); + } + + /** + * Flushes the in-memory buffer and any metrics being pre-aggregated. + */ + @Override + public void flush() { + telemetryClient.flush(); + } +} diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java new file mode 100644 index 000000000..4126e9369 --- /dev/null +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java @@ -0,0 +1,74 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +package com.microsoft.bot.applicationinsights.core; + +import com.microsoft.applicationinsights.core.dependencies.http.client.protocol.HttpClientContext; +import com.microsoft.applicationinsights.core.dependencies.http.protocol.HttpContext; +import com.microsoft.bot.builder.BotAssert; +import com.microsoft.bot.builder.Middleware; +import com.microsoft.bot.builder.NextDelegate; +import com.microsoft.bot.builder.TelemetryLoggerMiddleware; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.schema.Activity; + +import java.util.concurrent.CompletableFuture; + +/** + * Middleware for storing incoming activity on the HttpContext. + */ +public class TelemetryInitializerMiddleware implements Middleware { + + private HttpContext httpContext; + private final String botActivityKey = "BotBuilderActivity"; + private final TelemetryLoggerMiddleware telemetryLoggerMiddleware; + private final Boolean logActivityTelemetry; + + /** + * Initializes a new instance of the {@link TelemetryInitializerMiddleware}. + * @param withTelemetryLoggerMiddleware The TelemetryLoggerMiddleware to use. + * @param withLogActivityTelemetry Boolean determining if you want to log telemetry activity + */ + public TelemetryInitializerMiddleware(TelemetryLoggerMiddleware withTelemetryLoggerMiddleware, + Boolean withLogActivityTelemetry) { + telemetryLoggerMiddleware = withTelemetryLoggerMiddleware; + if (withLogActivityTelemetry == null) { + withLogActivityTelemetry = true; + } + logActivityTelemetry = withLogActivityTelemetry; + } + + /** + * Stores the incoming activity as JSON in the items collection on the HttpContext. + * @param context The incoming TurnContext + * @param next Delegate to run next on + * @return Returns a CompletableFuture with Void value + */ + public CompletableFuture onTurn(TurnContext context, NextDelegate next) { + BotAssert.contextNotNull(context); + + if (context.getActivity() != null) { + Activity activity = context.getActivity(); + + if (this.httpContext == null) { + this.httpContext = HttpClientContext.create(); + } + + Object item = httpContext.getAttribute(botActivityKey); + + if (item != null) { + httpContext.removeAttribute(botActivityKey); + } + + httpContext.setAttribute(botActivityKey, activity); + } + + if (logActivityTelemetry) { + return telemetryLoggerMiddleware.onTurn(context, next); + } else { + return next.next(); + } + } +} + diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/package-info.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/package-info.java new file mode 100644 index 000000000..300ffc6bb --- /dev/null +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for bot-applicationinsights. + */ +package com.microsoft.bot.applicationinsights.core; diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/package-info.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/package-info.java new file mode 100644 index 000000000..4bf130b60 --- /dev/null +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for bot-applicationinsights. + */ +package com.microsoft.bot.applicationinsights; diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java new file mode 100644 index 000000000..5ae1436a7 --- /dev/null +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java @@ -0,0 +1,183 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; +import com.microsoft.applicationinsights.channel.TelemetryChannel; +import com.microsoft.applicationinsights.telemetry.EventTelemetry; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; +import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; +import com.microsoft.applicationinsights.telemetry.TraceTelemetry; +import com.microsoft.applicationinsights.telemetry.SeverityLevel; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.Severity; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mockito; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.HashMap; +import java.util.Map; + +public class BotTelemetryClientTests { + + private BotTelemetryClient botTelemetryClient; + private TelemetryChannel mockTelemetryChannel; + + @Before + public void initialize() { + mockTelemetryChannel = Mockito.mock(TelemetryChannel.class); + + TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(); + telemetryConfiguration.setInstrumentationKey("UNITTEST-INSTRUMENTATION-KEY"); + telemetryConfiguration.setChannel(mockTelemetryChannel); + TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration); + + botTelemetryClient = new BotTelemetryClientImpl(telemetryClient); + } + + @Test + public void nullTelemetryClientThrows() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + new BotTelemetryClientImpl(null); + }); + } + + @Test + public void nonNullTelemetryClientSucceeds() { + TelemetryClient telemetryClient = new TelemetryClient(); + + BotTelemetryClient botTelemetryClient = new BotTelemetryClientImpl(telemetryClient); + } + + @Test + public void overrideTest() { + TelemetryClient telemetryClient = new TelemetryClient(); + MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient(telemetryClient); + } + + @Test + public void trackAvailabilityTest() { + Map properties = new HashMap<>(); + Map metrics = new HashMap<>(); + properties.put("hello", "value"); + metrics.put("metric", 0.6); + + botTelemetryClient.trackAvailability( + "test", + OffsetDateTime.now(), + Duration.ofNanos(1000), + "run location", + true, + "message", + properties, + metrics); + + Mockito.verify(mockTelemetryChannel, invocations -> { + AvailabilityTelemetry availabilityTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + Assert.assertEquals("test", availabilityTelemetry.getName()); + Assert.assertEquals("message", availabilityTelemetry.getData().getMessage()); + Assert.assertEquals("value", availabilityTelemetry.getProperties().get("hello")); + Assert.assertEquals(0, Double.compare(0.6, availabilityTelemetry.getMetrics().get("metric"))); + }).send(Mockito.any(AvailabilityTelemetry.class)); + + } + + @Test + public void trackEventTest() { + Map properties = new HashMap<>(); + properties.put("hello", "value"); + Map metrics = new HashMap<>(); + metrics.put("metric", 0.6); + + botTelemetryClient.trackEvent("test", properties, metrics); + + Mockito.verify(mockTelemetryChannel, invocations -> { + EventTelemetry eventTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + + Assert.assertEquals("test", eventTelemetry.getName()); + Assert.assertEquals("value", eventTelemetry.getProperties().get("hello")); + Assert.assertEquals(0, Double.compare(0.6, eventTelemetry.getMetrics().get("metric"))); + }).send(Mockito.any(AvailabilityTelemetry.class)); + } + + @Test + public void trackDependencyTest() { + botTelemetryClient.trackDependency( + "test", + "target", + "dependencyname", + "data", + OffsetDateTime.now(), + Duration.ofNanos(1000), + "result", false); + + Mockito.verify(mockTelemetryChannel, invocations -> { + RemoteDependencyTelemetry remoteDependencyTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + + Assert.assertEquals("test", remoteDependencyTelemetry.getType()); + Assert.assertEquals("target", remoteDependencyTelemetry.getTarget()); + Assert.assertEquals("dependencyname", remoteDependencyTelemetry.getName()); + Assert.assertEquals("result", remoteDependencyTelemetry.getResultCode()); + Assert.assertFalse(remoteDependencyTelemetry.getSuccess()); + }).send(Mockito.any(AvailabilityTelemetry.class)); + } + + @Test + public void trackExceptionTest() { + Exception expectedException = new Exception("test-exception"); + Map properties = new HashMap<>(); + properties.put("foo", "bar"); + Map metrics = new HashMap<>(); + metrics.put("metric", 0.6); + + botTelemetryClient.trackException(expectedException, properties, metrics); + + Mockito.verify(mockTelemetryChannel, invocations -> { + ExceptionTelemetry exceptionTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + + Assert.assertEquals(expectedException, exceptionTelemetry.getException()); + Assert.assertEquals("bar", exceptionTelemetry.getProperties().get("foo")); + Assert.assertEquals(0, Double.compare(0.6, exceptionTelemetry.getMetrics().get("metric"))); + }).send(Mockito.any(ExceptionTelemetry.class)); + } + + @Test + public void trackTraceTest() { + Map properties = new HashMap<>(); + properties.put("foo", "bar"); + + botTelemetryClient.trackTrace("hello", Severity.CRITICAL, properties); + + Mockito.verify(mockTelemetryChannel, invocations -> { + TraceTelemetry traceTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + + Assert.assertEquals("hello", traceTelemetry.getMessage()); + Assert.assertEquals(SeverityLevel.Critical, traceTelemetry.getSeverityLevel()); + Assert.assertEquals("bar", traceTelemetry.getProperties().get("foo")); + }).send(Mockito.any(TraceTelemetry.class)); + } + + @Test + public void trackPageViewTest() { + Map properties = new HashMap<>(); + properties.put("hello", "value"); + Map metrics = new HashMap<>(); + metrics.put("metric", 0.6); + + botTelemetryClient.trackDialogView("test", properties, metrics); + + Mockito.verify(mockTelemetryChannel, invocations -> { + PageViewTelemetry pageViewTelemetry = invocations.getAllInvocations().get(0).getArgument(0); + + Assert.assertEquals("test", pageViewTelemetry.getName()); + Assert.assertEquals("value", pageViewTelemetry.getProperties().get("hello")); + Assert.assertEquals(0, Double.compare(0.6, pageViewTelemetry.getMetrics().get("metric"))); + }).send(Mockito.any(PageViewTelemetry.class)); + } +} diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java new file mode 100644 index 000000000..5558ca0d0 --- /dev/null +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.bot.builder.Severity; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Map; + +public class MyBotTelemetryClient extends BotTelemetryClientImpl { + public MyBotTelemetryClient(TelemetryClient telemetryClient) { + super(telemetryClient); + } + + @Override + public void trackDependency( + String dependencyTypeName, + String target, + String dependencyName, + String data, + OffsetDateTime startTime, + Duration duration, + String resultCode, + boolean success) + { + super.trackDependency(dependencyName, target, dependencyName, data, startTime, duration, resultCode, success); + } + + @Override + public void trackAvailability( + String name, + OffsetDateTime timeStamp, + Duration duration, + String runLocation, + boolean success, + String message, + Map properties, + Map metrics) + { + super.trackAvailability(name, timeStamp, duration, runLocation, success, message, properties, metrics); + } + + @Override + public void trackEvent( + String eventName, + Map properties, + Map metrics) + { + super.trackEvent(eventName, properties, metrics); + } + + @Override + public void trackException( + Exception exception, + Map properties, + Map metrics) + { + super.trackException(exception, properties, metrics); + } + + @Override + public void trackTrace( + String message, + Severity severityLevel, + Map properties) + { + super.trackTrace(message, severityLevel, properties); + } + + @Override + public void trackPageView( + String name, + Map properties, + Map metrics) + { + super.trackPageView(name, properties, metrics); + } + + @Override + public void flush() + { + super.flush(); + } +} diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryInitializerTests.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryInitializerTests.java new file mode 100644 index 000000000..06592eef9 --- /dev/null +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryInitializerTests.java @@ -0,0 +1,132 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.TimeUnit; + +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mockito; + +import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.TelemetryLoggerMiddleware; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class TelemetryInitializerTests { + + @Captor + ArgumentCaptor eventNameCaptor; + + @Captor + ArgumentCaptor> propertiesCaptor; + + @Test + public void telemetryInitializerMiddlewareLogActivitiesEnabled() { + + // Arrange + BotTelemetryClient mockTelemetryClient = Mockito.mock(BotTelemetryClient.class); + TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(mockTelemetryClient, false); + + TestAdapter testAdapter = new TestAdapter() + .use(new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, true)); + + // Act + // Default case logging Send/Receive Activities + new TestFlow(testAdapter, turnContext -> { + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + + turnContext.sendActivity(typingActivity).join(); + try { + TimeUnit.MILLISECONDS.sleep(500); + } catch (InterruptedException e) { + // Empty error + } + turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); + return CompletableFuture.completedFuture(null); + }) + .send("foo") + .assertReply(activity -> { + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)); + }) + .assertReply("echo:foo") + .send("bar") + .assertReply(activity -> { + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)); + }) + .assertReply("echo:bar") + .startTest().join(); + + // Verify + verify(mockTelemetryClient, times(6)).trackEvent( + eventNameCaptor.capture(), + propertiesCaptor.capture() + ); + + List eventNames = eventNameCaptor.getAllValues(); + Assert.assertEquals(6, eventNames.size()); + + } + + @Test + public void telemetryInitializerMiddlewareNotLogActivitiesDisabled() { + + // Arrange + BotTelemetryClient mockTelemetryClient = Mockito.mock(BotTelemetryClient.class); + TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(mockTelemetryClient, false); + + TestAdapter testAdapter = new TestAdapter() + .use(new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, false)); + + // Act + // Default case logging Send/Receive Activities + new TestFlow(testAdapter, (turnContext) -> { + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + + turnContext.sendActivity(typingActivity).join(); + try { + TimeUnit.MILLISECONDS.sleep(500); + } catch (InterruptedException e) { + // Empty error + } + turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); + return CompletableFuture.completedFuture(null); + }) + .send("foo") + .assertReply(activity -> { + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)); + }) + .assertReply("echo:foo") + .send("bar") + .assertReply(activity -> { + Assert.assertTrue(activity.isType(ActivityTypes.TYPING)); + }) + .assertReply("echo:bar") + .startTest().join(); + + // Verify + verify(mockTelemetryClient, times(0)).trackEvent( + eventNameCaptor.capture(), + propertiesCaptor.capture() + ); + List eventNames = eventNameCaptor.getAllValues(); + Assert.assertEquals(0, eventNames.size()); + } +} diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryWaterfallTests.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryWaterfallTests.java new file mode 100644 index 000000000..70825410b --- /dev/null +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/TelemetryWaterfallTests.java @@ -0,0 +1,328 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.bot.builder.AutoSaveStateMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.StatePropertyAccessor; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MemoryStorage; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogInstance; +import com.microsoft.bot.dialogs.DialogReason; +import com.microsoft.bot.dialogs.DialogSet; +import com.microsoft.bot.dialogs.DialogState; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import org.junit.Assert; +import org.junit.Test; +import org.mockito.Mockito; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +public class TelemetryWaterfallTests { + + @Test + public void waterfall() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("Waterfall", "User1", "Bot")) + .use(new AutoSaveStateMiddleware(convoState)); + + BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + + dialogs.add(new WaterfallDialog("test", newWaterfall())); + dialogs.setTelemetryClient(telemetryClient); + + new TestFlow(adapter, turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + + // C#'s trackEvent method of BotTelemetryClient has nullable parameters, + // therefore it always calls the same method. + // On the other hand, Java's BotTelemetryClient overloads the trackEvent method, + // so instead of calling the same method, it calls a method with less parameters. + // In this particular test, WaterfallDialog's beginDialog calls the method with only two parameters + Mockito.verify(telemetryClient, Mockito.times(4)).trackEvent( + Mockito.anyString(), + Mockito.anyMap() + ); + System.out.printf("Complete"); + } + + @Test + public void waterfallWithCallback() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("WaterfallWithCallback", "User1", "Bot")) + .use(new AutoSaveStateMiddleware(convoState)); + + BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + WaterfallDialog waterfallDialog = new WaterfallDialog("test", newWaterfall()); + + dialogs.add(waterfallDialog); + dialogs.setTelemetryClient(telemetryClient); + + new TestFlow(adapter, turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .startTest() + .join(); + + // C#'s trackEvent method of BotTelemetryClient has nullable parameters, + // therefore it always calls the same method. + // On the other hand, Java's BotTelemetryClient overloads the trackEvent method, + // so instead of calling the same method, it calls a method with less parameters. + // In this particular test, WaterfallDialog's beginDialog calls the method with only two parameters + Mockito.verify(telemetryClient, Mockito.times(4)).trackEvent( + Mockito.anyString(), + Mockito.anyMap() + ); + } + + @Test + public void waterfallWithActionsNull() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); + WaterfallDialog waterfall = new WaterfallDialog("test", null); + waterfall.setTelemetryClient(telemetryClient); + waterfall.addStep(null); + }); + } + + @Test + public void ensureEndDialogCalled() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("EnsureEndDialogCalled", "User1", "Bot")) + .use(new AutoSaveStateMiddleware(convoState)); + + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + HashMap> saved_properties = new HashMap<>(); + final int[] counter = {0}; + + // Set up the client to save all logged property names and associated properties (in "saved_properties"). + BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); + Mockito.doAnswer(invocation -> { + String eventName = invocation.getArgument(0); + Map properties = invocation.getArgument(1); + + StringBuilder sb = new StringBuilder(eventName).append("_").append(counter[0]++); + saved_properties.put(sb.toString(), properties); + + return null; + }).when(telemetryClient).trackEvent(Mockito.anyString(), Mockito.anyMap()); + + MyWaterfallDialog waterfallDialog = new MyWaterfallDialog("test", newWaterfall()); + + dialogs.add(waterfallDialog); + dialogs.setTelemetryClient(telemetryClient); + + new TestFlow(adapter, turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step3") + .send("hello") + .assertReply("step1") + .startTest() + .join(); + + Mockito.verify(telemetryClient, Mockito.times(7)).trackEvent( + Mockito.anyString(), + Mockito.anyMap() + ); + + // Verify: + // Event name is "WaterfallComplete" + // Event occurs on the 4th event logged + // Event contains DialogId + // Event DialogId is set correctly. + Assert.assertTrue(saved_properties.get("WaterfallComplete_4").containsKey("DialogId")); + Assert.assertEquals("test", saved_properties.get("WaterfallComplete_4").get("DialogId")); + Assert.assertTrue(saved_properties.get("WaterfallComplete_4").containsKey("InstanceId")); + Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("InstanceId")); + + // Verify naming on lambda's is "StepXofY" + Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("StepName")); + Assert.assertEquals("Step1of3", saved_properties.get("WaterfallStep_1").get("StepName")); + Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("InstanceId")); + Assert.assertTrue(waterfallDialog.getEndDialogCalled()); + } + + @Test + public void ensureCancelDialogCalled() { + ConversationState convoState = new ConversationState(new MemoryStorage()); + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("EnsureCancelDialogCalled", "User1", "Bot")) + .use(new AutoSaveStateMiddleware(convoState)); + + StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); + DialogSet dialogs = new DialogSet(dialogState); + HashMap> saved_properties = new HashMap<>(); + final int[] counter = {0}; + + // Set up the client to save all logged property names and associated properties (in "saved_properties"). + BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); + Mockito.doAnswer(invocation -> { + String eventName = invocation.getArgument(0); + Map properties = invocation.getArgument(1); + + StringBuilder sb = new StringBuilder(eventName).append("_").append(counter[0]++); + saved_properties.put(sb.toString(), properties); + + return null; + }).when(telemetryClient).trackEvent(Mockito.anyString(), Mockito.anyMap()); + + List steps = new ArrayList<>(); + steps.add(step -> { + step.getContext().sendActivity("step1").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + steps.add(step -> { + step.getContext().sendActivity("step2").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + steps.add(step -> { + step.cancelAllDialogs().join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + + MyWaterfallDialog waterfallDialog = new MyWaterfallDialog("test", steps); + + dialogs.add(waterfallDialog); + dialogs.setTelemetryClient(telemetryClient); + + new TestFlow(adapter, turnContext -> { + DialogContext dc = dialogs.createContext(turnContext).join(); + dc.continueDialog().join(); + if (!turnContext.getResponded()) { + dc.beginDialog("test", null).join(); + } + return CompletableFuture.completedFuture(null); + }) + .send("hello") + .assertReply("step1") + .send("hello") + .assertReply("step2") + .send("hello") + .assertReply("step1") + .startTest() + .join(); + + Mockito.verify(telemetryClient, Mockito.times(7)).trackEvent( + Mockito.anyString(), + Mockito.anyMap() + ); + + // Verify: + // Event name is "WaterfallCancel" + // Event occurs on the 4th event logged + // Event contains DialogId + // Event DialogId is set correctly. + Assert.assertTrue(saved_properties.get("WaterfallStart_0").containsKey("DialogId")); + Assert.assertTrue(saved_properties.get("WaterfallStart_0").containsKey("InstanceId")); + Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("DialogId")); + Assert.assertEquals("test", saved_properties.get("WaterfallCancel_4").get("DialogId")); + Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("StepName")); + Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("InstanceId")); + + // Event contains "StepName" + // Event naming on lambda's is "StepXofY" + Assert.assertEquals("Step3of3", saved_properties.get("WaterfallCancel_4").get("StepName")); + Assert.assertTrue(waterfallDialog.getCancelDialogCalled()); + Assert.assertFalse(waterfallDialog.getEndDialogCalled()); + } + + private static List newWaterfall() { + List waterfall = new ArrayList<>(); + + waterfall.add(step -> { + step.getContext().sendActivity("step1").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + + waterfall.add(step -> { + step.getContext().sendActivity("step2").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + + waterfall.add(step -> { + step.getContext().sendActivity("step3").join(); + return CompletableFuture.completedFuture(Dialog.END_OF_TURN); + }); + + return waterfall; + } + + private class MyWaterfallDialog extends WaterfallDialog { + private Boolean endDialogCalled = false; + private Boolean cancelDialogCalled = false; + + public MyWaterfallDialog(String id, List actions) { + super(id, actions); + } + + @Override + public CompletableFuture endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) { + if (reason == DialogReason.END_CALLED) { + endDialogCalled = true; + } else if (reason == DialogReason.CANCEL_CALLED) { + cancelDialogCalled = true; + } + + return super.endDialog(turnContext, instance, reason); + } + + public Boolean getEndDialogCalled() { + return endDialogCalled; + } + + public Boolean getCancelDialogCalled() { + return cancelDialogCalled; + } + } +} diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java index 597f71ad9..28e0b8bd9 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/WaterfallDialog.java @@ -267,8 +267,8 @@ private String waterfallStepName(int index) { String stepName = steps.get(index).getClass().getSimpleName(); // Default stepname for lambdas - if (StringUtils.isAllBlank(stepName) || stepName.contains("<")) { - stepName = String.format("Step%0of%1", index + 1, steps.size()); + if (StringUtils.isAllBlank(stepName) || stepName.contains("$Lambda$")) { + stepName = String.format("Step%dof%d", index + 1, steps.size()); } return stepName; From 9d60e6718986cfecef5468933b6cff8a2c058124 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 24 Mar 2021 09:39:13 -0500 Subject: [PATCH 115/221] Formatting changes (#1076) --- ...pplicationInsightsBotTelemetryClient.java} | 511 +++++++++--------- .../AvailabilityTelemetry.java | 56 +- .../core/TelemetryInitializerMiddleware.java | 17 +- .../BotTelemetryClientTests.java | 6 +- .../MyBotTelemetryClient.java | 2 +- .../bot/azure/CosmosDbKeyEscape.java | 8 +- .../bot/azure/CosmosDbPartitionedStorage.java | 74 ++- .../CosmosDbPartitionedStorageOptions.java | 1 + .../bot/azure/blobs/BlobsStorage.java | 73 ++- .../bot/azure/blobs/BlobsTranscriptStore.java | 122 +++-- .../bot/azure/queues/AzureQueueStorage.java | 42 +- 11 files changed, 511 insertions(+), 401 deletions(-) rename libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/{BotTelemetryClientImpl.java => ApplicationInsightsBotTelemetryClient.java} (60%) diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java similarity index 60% rename from libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java rename to libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java index 5a574164b..6434a7754 100644 --- a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/BotTelemetryClientImpl.java +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java @@ -1,247 +1,264 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.applicationinsights; - -import com.microsoft.applicationinsights.TelemetryClient; -import com.microsoft.applicationinsights.telemetry.EventTelemetry; -import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; -import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; -import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; -import com.microsoft.applicationinsights.telemetry.SeverityLevel; -import com.microsoft.applicationinsights.telemetry.TraceTelemetry; -import com.microsoft.bot.builder.BotTelemetryClient; -import com.microsoft.bot.builder.Severity; - -import java.time.Duration; -import java.time.OffsetDateTime; -import java.util.Date; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; - -/** - * A logging client for bot telemetry. - */ -public class BotTelemetryClientImpl implements BotTelemetryClient { - - private final TelemetryClient telemetryClient; - - /** - * Initializes a new instance of the {@link BotTelemetryClient}. - * - * @param withTelemetryClient The telemetry client to forward bot events to. - */ - public BotTelemetryClientImpl(TelemetryClient withTelemetryClient) { - if (withTelemetryClient == null) { - throw new IllegalArgumentException("withTelemetry should be provided"); - } - this.telemetryClient = withTelemetryClient; - } - - /** - * Send information about availability of an application. - * - * @param name Availability test name. - * @param timeStamp The time when the availability was captured. - * @param duration The time taken for the availability test to run. - * @param runLocation Name of the location the availability test was run from. - * @param success True if the availability test ran successfully. - * @param message Error message on availability test run failure. - * @param properties Named string values you can use to classify and search for this availability telemetry. - * @param metrics Additional values associated with this availability telemetry. - */ - @SuppressWarnings("checkstyle:ParameterNumber") - @Override - public void trackAvailability(String name, - OffsetDateTime timeStamp, - Duration duration, - String runLocation, - boolean success, - String message, - Map properties, - Map metrics) { - com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = - new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); - ConcurrentMap concurrentProperties = new ConcurrentHashMap<>(properties); - ConcurrentMap concurrentMetrics = new ConcurrentHashMap<>(metrics); - AvailabilityTelemetry telemetry = new AvailabilityTelemetry( - name, - durationTelemetry, - runLocation, - message, - success, - concurrentMetrics, - concurrentProperties); - if (properties != null) { - for (Map.Entry pair: properties.entrySet()) { - telemetry.getProperties().put(pair.getKey(), pair.getValue()); - } - } - - if (metrics != null) { - for (Map.Entry pair: metrics.entrySet()) { - telemetry.getMetrics().put(pair.getKey(), pair.getValue()); - } - } - - /** - * This should be telemetryClient.trackAvailability(telemetry). - * However, it is not present in TelemetryClient class - */ - telemetryClient.track(telemetry); - } - - /** - * Send information about an external dependency (outgoing call) in the application. - * - * @param dependencyTypeName Name of the command initiated with this dependency call. Low cardinality value. - * Examples are SQL, Azure table, and HTTP. - * @param target External dependency target. - * @param dependencyName Name of the command initiated with this dependency call. Low cardinality value. - * Examples are stored procedure name and URL path template. - * @param data Command initiated by this dependency call. Examples are SQL statement and HTTP - * URL's with all query parameters. - * @param startTime The time when the dependency was called. - * @param duration The time taken by the external dependency to handle the call. - * @param resultCode Result code of dependency call execution. - * @param success True if the dependency call was handled successfully. - */ - @SuppressWarnings("checkstyle:ParameterNumber") - @Override - public void trackDependency(String dependencyTypeName, - String target, - String dependencyName, - String data, - OffsetDateTime startTime, - Duration duration, - String resultCode, - boolean success) { - com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = - new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); - - RemoteDependencyTelemetry telemetry = - new RemoteDependencyTelemetry(dependencyName, data, durationTelemetry, success); - - telemetry.setType(dependencyTypeName); - telemetry.setTarget(target); - telemetry.setTimestamp(new Date(startTime.toInstant().toEpochMilli())); - telemetry.setResultCode(resultCode); - - telemetryClient.trackDependency(telemetry); - } - - /** - * Logs custom events with extensible named fields. - * - * @param eventName A name for the event. - * @param properties Named string values you can use to search and classify events. - * @param metrics Measurements associated with this event. - */ - @Override - public void trackEvent(String eventName, Map properties, Map metrics) { - EventTelemetry telemetry = new EventTelemetry(eventName); - if (properties != null) { - for (Map.Entry pair: properties.entrySet()) { - telemetry.getProperties().put(pair.getKey(), pair.getValue()); - } - } - - if (metrics != null) { - for (Map.Entry pair: metrics.entrySet()) { - telemetry.getMetrics().put(pair.getKey(), pair.getValue()); - } - } - - telemetryClient.trackEvent(telemetry); - } - - /** - * Logs a system exception. - * - * @param exception The exception to log. - * @param properties Named string values you can use to classify and search for this exception. - * @param metrics Additional values associated with this exception - */ - @Override - public void trackException(Exception exception, Map properties, Map metrics) { - ExceptionTelemetry telemetry = new ExceptionTelemetry(exception); - if (properties != null) { - for (Map.Entry pair: properties.entrySet()) { - telemetry.getProperties().put(pair.getKey(), pair.getValue()); - } - } - - if (metrics != null) { - for (Map.Entry pair: metrics.entrySet()) { - telemetry.getMetrics().put(pair.getKey(), pair.getValue()); - } - } - - telemetryClient.trackException(telemetry); - } - - /** - * Send a trace message. - * - * @param message Message to display. - * @param severityLevel Trace severity level {@link Severity}. - * @param properties Named string values you can use to search and classify events. - */ - @Override - public void trackTrace(String message, Severity severityLevel, Map properties) { - TraceTelemetry telemetry = new TraceTelemetry(message); - telemetry.setSeverityLevel(SeverityLevel.values()[severityLevel.ordinal()]); - - if (properties != null) { - for (Map.Entry pair: properties.entrySet()) { - telemetry.getProperties().put(pair.getKey(), pair.getValue()); - } - } - - telemetryClient.trackTrace(telemetry); - } - - /** - * We implemented this method calling the tracePageView method from {@link BotTelemetryClientImpl} as the - * IBotPageViewTelemetryClient has not been implemented. - * {@inheritDoc} - */ - @Override - public void trackDialogView(String dialogName, Map properties, Map metrics) { - trackPageView(dialogName, properties, metrics); - } - - /** - * Logs a dialog entry / as an Application Insights page view. - * - * @param dialogName The name of the dialog to log the entry / start for. - * @param properties Named string values you can use to search and classify events. - * @param metrics Measurements associated with this event. - */ - public void trackPageView(String dialogName, Map properties, Map metrics) { - PageViewTelemetry telemetry = new PageViewTelemetry(dialogName); - - if (properties != null) { - for (Map.Entry pair: properties.entrySet()) { - telemetry.getProperties().put(pair.getKey(), pair.getValue()); - } - } - - if (metrics != null) { - for (Map.Entry pair: metrics.entrySet()) { - telemetry.getMetrics().put(pair.getKey(), pair.getValue()); - } - } - - telemetryClient.trackPageView(telemetry); - } - - /** - * Flushes the in-memory buffer and any metrics being pre-aggregated. - */ - @Override - public void flush() { - telemetryClient.flush(); - } -} +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.applicationinsights; + +import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.telemetry.EventTelemetry; +import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; +import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; +import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; +import com.microsoft.applicationinsights.telemetry.SeverityLevel; +import com.microsoft.applicationinsights.telemetry.TraceTelemetry; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.Severity; + +import java.time.Duration; +import java.time.OffsetDateTime; +import java.util.Date; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; + +/** + * A logging client for bot telemetry. + */ +public class ApplicationInsightsBotTelemetryClient implements BotTelemetryClient { + + private final TelemetryClient telemetryClient; + + /** + * Initializes a new instance of the {@link BotTelemetryClient}. + * + * @param withTelemetryClient The telemetry client to forward bot events to. + */ + public ApplicationInsightsBotTelemetryClient(TelemetryClient withTelemetryClient) { + if (withTelemetryClient == null) { + throw new IllegalArgumentException("withTelemetry should be provided"); + } + this.telemetryClient = withTelemetryClient; + } + + /** + * Send information about availability of an application. + * + * @param name Availability test name. + * @param timeStamp The time when the availability was captured. + * @param duration The time taken for the availability test to run. + * @param runLocation Name of the location the availability test was run from. + * @param success True if the availability test ran successfully. + * @param message Error message on availability test run failure. + * @param properties Named string values you can use to classify and search for + * this availability telemetry. + * @param metrics Additional values associated with this availability + * telemetry. + */ + @SuppressWarnings("checkstyle:ParameterNumber") + @Override + public void trackAvailability( + String name, + OffsetDateTime timeStamp, + Duration duration, + String runLocation, + boolean success, + String message, + Map properties, + Map metrics + ) { + com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = + new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); + ConcurrentMap concurrentProperties = new ConcurrentHashMap<>(properties); + ConcurrentMap concurrentMetrics = new ConcurrentHashMap<>(metrics); + AvailabilityTelemetry telemetry = new AvailabilityTelemetry( + name, + durationTelemetry, + runLocation, + message, + success, + concurrentMetrics, + concurrentProperties + ); + if (properties != null) { + for (Map.Entry pair : properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair : metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + /** + * This should be telemetryClient.trackAvailability(telemetry). However, it is + * not present in TelemetryClient class + */ + telemetryClient.track(telemetry); + } + + /** + * Send information about an external dependency (outgoing call) in the + * application. + * + * @param dependencyTypeName Name of the command initiated with this dependency + * call. Low cardinality value. Examples are SQL, + * Azure table, and HTTP. + * @param target External dependency target. + * @param dependencyName Name of the command initiated with this dependency + * call. Low cardinality value. Examples are stored + * procedure name and URL path template. + * @param data Command initiated by this dependency call. Examples + * are SQL statement and HTTP URL's with all query + * parameters. + * @param startTime The time when the dependency was called. + * @param duration The time taken by the external dependency to handle + * the call. + * @param resultCode Result code of dependency call execution. + * @param success True if the dependency call was handled + * successfully. + */ + @SuppressWarnings("checkstyle:ParameterNumber") + @Override + public void trackDependency( + String dependencyTypeName, + String target, + String dependencyName, + String data, + OffsetDateTime startTime, + Duration duration, + String resultCode, + boolean success + ) { + com.microsoft.applicationinsights.telemetry.Duration durationTelemetry = + new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos()); + + RemoteDependencyTelemetry telemetry = + new RemoteDependencyTelemetry(dependencyName, data, durationTelemetry, success); + + telemetry.setType(dependencyTypeName); + telemetry.setTarget(target); + telemetry.setTimestamp(new Date(startTime.toInstant().toEpochMilli())); + telemetry.setResultCode(resultCode); + + telemetryClient.trackDependency(telemetry); + } + + /** + * Logs custom events with extensible named fields. + * + * @param eventName A name for the event. + * @param properties Named string values you can use to search and classify + * events. + * @param metrics Measurements associated with this event. + */ + @Override + public void trackEvent(String eventName, Map properties, Map metrics) { + EventTelemetry telemetry = new EventTelemetry(eventName); + if (properties != null) { + for (Map.Entry pair : properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair : metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackEvent(telemetry); + } + + /** + * Logs a system exception. + * + * @param exception The exception to log. + * @param properties Named string values you can use to classify and search for + * this exception. + * @param metrics Additional values associated with this exception + */ + @Override + public void trackException(Exception exception, Map properties, Map metrics) { + ExceptionTelemetry telemetry = new ExceptionTelemetry(exception); + if (properties != null) { + for (Map.Entry pair : properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair : metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackException(telemetry); + } + + /** + * Send a trace message. + * + * @param message Message to display. + * @param severityLevel Trace severity level {@link Severity}. + * @param properties Named string values you can use to search and classify + * events. + */ + @Override + public void trackTrace(String message, Severity severityLevel, Map properties) { + TraceTelemetry telemetry = new TraceTelemetry(message); + telemetry.setSeverityLevel(SeverityLevel.values()[severityLevel.ordinal()]); + + if (properties != null) { + for (Map.Entry pair : properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackTrace(telemetry); + } + + /** + * We implemented this method calling the tracePageView method from + * {@link ApplicationInsightsBotTelemetryClient} as the + * IBotPageViewTelemetryClient has not been implemented. {@inheritDoc} + */ + @Override + public void trackDialogView(String dialogName, Map properties, Map metrics) { + trackPageView(dialogName, properties, metrics); + } + + /** + * Logs a dialog entry / as an Application Insights page view. + * + * @param dialogName The name of the dialog to log the entry / start for. + * @param properties Named string values you can use to search and classify + * events. + * @param metrics Measurements associated with this event. + */ + public void trackPageView(String dialogName, Map properties, Map metrics) { + PageViewTelemetry telemetry = new PageViewTelemetry(dialogName); + + if (properties != null) { + for (Map.Entry pair : properties.entrySet()) { + telemetry.getProperties().put(pair.getKey(), pair.getValue()); + } + } + + if (metrics != null) { + for (Map.Entry pair : metrics.entrySet()) { + telemetry.getMetrics().put(pair.getKey(), pair.getValue()); + } + } + + telemetryClient.trackPageView(telemetry); + } + + /** + * Flushes the in-memory buffer and any metrics being pre-aggregated. + */ + @Override + public void flush() { + telemetryClient.flush(); + } +} diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java index 80911aa58..3197840a3 100644 --- a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/AvailabilityTelemetry.java @@ -12,8 +12,9 @@ import java.util.concurrent.ConcurrentMap; /** - * We took this class from https://github.com/microsoft/ApplicationInsights-Java/issues/1099 - * as this is not already migrated in ApplicationInsights-Java library. + * We took this class from + * https://github.com/microsoft/ApplicationInsights-Java/issues/1099 as this is + * not already migrated in ApplicationInsights-Java library. */ public final class AvailabilityTelemetry extends BaseSampleSourceTelemetry { private Double samplingPercentage; @@ -23,7 +24,6 @@ public final class AvailabilityTelemetry extends BaseSampleSourceTelemetry measurements, - ConcurrentMap properties) { + public AvailabilityTelemetry( + String name, + Duration duration, + String runLocation, + String message, + boolean success, + ConcurrentMap measurements, + ConcurrentMap properties + ) { this.data = new AvailabilityData(); @@ -70,9 +77,9 @@ public AvailabilityTelemetry(String name, Duration duration, String runLocation, setSuccess(success); } - /** * Gets the ver value from the data object. + * * @return The ver value. */ @Override @@ -82,6 +89,7 @@ public int getVer() { /** * Gets a map of application-defined request metrics. + * * @return The map of metrics */ public ConcurrentMap getMetrics() { @@ -89,7 +97,9 @@ public ConcurrentMap getMetrics() { } /** - * Sets the StartTime. Uses the default behavior and sets the property on the 'data' start time. + * Sets the StartTime. Uses the default behavior and sets the property on the + * 'data' start time. + * * @param timestamp The timestamp as Date. */ @Override @@ -103,6 +113,7 @@ public void setTimestamp(Date timestamp) { /** * Gets or human-readable name of the requested page. + * * @return A human-readable name. */ public String getName() { @@ -111,6 +122,7 @@ public String getName() { /** * Sets or human-readable name of the requested page. + * * @param name A human-readable name. */ public void setName(String name) { @@ -119,6 +131,7 @@ public void setName(String name) { /** * Gets or human-readable name of the run location. + * * @return A human-readable name. */ public String getRunLocation() { @@ -127,6 +140,7 @@ public String getRunLocation() { /** * Sets or human-readable name of the run location. + * * @param runLocation A human-readable name */ public void setRunLocation(String runLocation) { @@ -135,6 +149,7 @@ public void setRunLocation(String runLocation) { /** * Gets the unique identifier of the request. + * * @return Unique identifier. */ public String getId() { @@ -143,6 +158,7 @@ public String getId() { /** * Sets the unique identifier of the request. + * * @param id Unique identifier. */ public void setId(String id) { @@ -151,6 +167,7 @@ public void setId(String id) { /** * Gets a value indicating whether application handled the request successfully. + * * @return Success indication. */ public boolean isSuccess() { @@ -159,6 +176,7 @@ public boolean isSuccess() { /** * Sets a value indicating whether application handled the request successfully. + * * @param success Success indication. */ public void setSuccess(boolean success) { @@ -167,6 +185,7 @@ public void setSuccess(boolean success) { /** * Gets the amount of time it took the application to handle the request. + * * @return Amount of time in milliseconds. */ public Duration getDuration() { @@ -175,7 +194,9 @@ public Duration getDuration() { /** * Sets the amount of time it took the application to handle the request. - * @param duration Amount of time in captured in a {@link com.microsoft.applicationinsights.telemetry.Duration}. + * + * @param duration Amount of time in captured in a + * {@link com.microsoft.applicationinsights.telemetry.Duration}. */ public void setDuration(Duration duration) { data.setDuration(duration); @@ -214,4 +235,3 @@ public String getBaseTypeName() { return BASE_TYPE; } } - diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java index 4126e9369..8bf4377be 100644 --- a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/core/TelemetryInitializerMiddleware.java @@ -27,11 +27,15 @@ public class TelemetryInitializerMiddleware implements Middleware { /** * Initializes a new instance of the {@link TelemetryInitializerMiddleware}. + * * @param withTelemetryLoggerMiddleware The TelemetryLoggerMiddleware to use. - * @param withLogActivityTelemetry Boolean determining if you want to log telemetry activity + * @param withLogActivityTelemetry Boolean determining if you want to log + * telemetry activity */ - public TelemetryInitializerMiddleware(TelemetryLoggerMiddleware withTelemetryLoggerMiddleware, - Boolean withLogActivityTelemetry) { + public TelemetryInitializerMiddleware( + TelemetryLoggerMiddleware withTelemetryLoggerMiddleware, + Boolean withLogActivityTelemetry + ) { telemetryLoggerMiddleware = withTelemetryLoggerMiddleware; if (withLogActivityTelemetry == null) { withLogActivityTelemetry = true; @@ -40,9 +44,11 @@ public TelemetryInitializerMiddleware(TelemetryLoggerMiddleware withTelemetryLog } /** - * Stores the incoming activity as JSON in the items collection on the HttpContext. + * Stores the incoming activity as JSON in the items collection on the + * HttpContext. + * * @param context The incoming TurnContext - * @param next Delegate to run next on + * @param next Delegate to run next on * @return Returns a CompletableFuture with Void value */ public CompletableFuture onTurn(TurnContext context, NextDelegate next) { @@ -71,4 +77,3 @@ public CompletableFuture onTurn(TurnContext context, NextDelegate next) { } } } - diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java index 5ae1436a7..855f139f9 100644 --- a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java @@ -38,13 +38,13 @@ public void initialize() { telemetryConfiguration.setChannel(mockTelemetryChannel); TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration); - botTelemetryClient = new BotTelemetryClientImpl(telemetryClient); + botTelemetryClient = new ApplicationInsightsBotTelemetryClient(telemetryClient); } @Test public void nullTelemetryClientThrows() { Assert.assertThrows(IllegalArgumentException.class, () -> { - new BotTelemetryClientImpl(null); + new ApplicationInsightsBotTelemetryClient(null); }); } @@ -52,7 +52,7 @@ public void nullTelemetryClientThrows() { public void nonNullTelemetryClientSucceeds() { TelemetryClient telemetryClient = new TelemetryClient(); - BotTelemetryClient botTelemetryClient = new BotTelemetryClientImpl(telemetryClient); + BotTelemetryClient botTelemetryClient = new ApplicationInsightsBotTelemetryClient(telemetryClient); } @Test diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java index 5558ca0d0..9ccf38495 100644 --- a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java @@ -10,7 +10,7 @@ import java.time.OffsetDateTime; import java.util.Map; -public class MyBotTelemetryClient extends BotTelemetryClientImpl { +public class MyBotTelemetryClient extends ApplicationInsightsBotTelemetryClient { public MyBotTelemetryClient(TelemetryClient telemetryClient) { super(telemetryClient); } diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java index 0c54444be..d952ed901 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbKeyEscape.java @@ -48,8 +48,8 @@ private CosmosDbKeyEscape() { * means a key of "?test?" would be escaped as "*3ftest*3f". */ private static final Map ILLEGAL_KEY_CHARACTER_REPLACEMENT_MAP = Arrays - .stream(ArrayUtils.toObject(ILLEGAL_KEYS)) - .collect(Collectors.toMap(c -> c, c -> "*" + String.format("%02x", (int) c))); + .stream(ArrayUtils.toObject(ILLEGAL_KEYS)) + .collect(Collectors.toMap(c -> c, c -> "*" + String.format("%02x", (int) c))); /** * Converts the key into a DocumentID that can be used safely with Cosmos DB. @@ -94,8 +94,8 @@ public static String escapeKey(String key, String suffix, Boolean compatibilityM // Allocate a builder that assumes that all remaining characters might be // replaced // to avoid any extra allocations - StringBuilder sanitizedKeyBuilder = new StringBuilder( - key.length() + ((key.length() - firstIllegalCharIndex) * ESCAPE_LENGTH)); + StringBuilder sanitizedKeyBuilder = + new StringBuilder(key.length() + ((key.length() - firstIllegalCharIndex) * ESCAPE_LENGTH)); // Add all good characters up to the first bad character to the builder first for (Integer index = 0; index < firstIllegalCharIndex; index++) { diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java index 3a9c32ee1..25a145113 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java @@ -85,7 +85,8 @@ public CosmosDbPartitionedStorage(CosmosDbPartitionedStorageOptions withCosmosDb if (StringUtils.isNotBlank(withCosmosDbStorageOptions.getKeySuffix())) { if (withCosmosDbStorageOptions.getCompatibilityMode()) { throw new IllegalArgumentException( - "CompatibilityMode cannot be 'true' while using a KeySuffix: withCosmosDbStorageOptions"); + "CompatibilityMode cannot be 'true' while using a KeySuffix: withCosmosDbStorageOptions" + ); } // In order to reduce key complexity, we do not allow invalid characters in a @@ -93,18 +94,28 @@ public CosmosDbPartitionedStorage(CosmosDbPartitionedStorageOptions withCosmosDb // If the KeySuffix has invalid characters, the EscapeKey will not match String suffixEscaped = CosmosDbKeyEscape.escapeKey(withCosmosDbStorageOptions.getKeySuffix()); if (!withCosmosDbStorageOptions.getKeySuffix().equals(suffixEscaped)) { - throw new IllegalArgumentException(String.format("Cannot use invalid Row Key characters: %s %s", - withCosmosDbStorageOptions.getKeySuffix(), "withCosmosDbStorageOptions")); + throw new IllegalArgumentException( + String.format( + "Cannot use invalid Row Key characters: %s %s", + withCosmosDbStorageOptions.getKeySuffix(), + "withCosmosDbStorageOptions" + ) + ); } } cosmosDbStorageOptions = withCosmosDbStorageOptions; objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) - .findAndRegisterModules().enableDefaultTyping(); - - client = new DocumentClient(cosmosDbStorageOptions.getCosmosDbEndpoint(), cosmosDbStorageOptions.getAuthKey(), - cosmosDbStorageOptions.getConnectionPolicy(), cosmosDbStorageOptions.getConsistencyLevel()); + .findAndRegisterModules() + .enableDefaultTyping(); + + client = new DocumentClient( + cosmosDbStorageOptions.getCosmosDbEndpoint(), + cosmosDbStorageOptions.getAuthKey(), + cosmosDbStorageOptions.getConnectionPolicy(), + cosmosDbStorageOptions.getConsistencyLevel() + ); } /** @@ -128,8 +139,15 @@ public CompletableFuture> read(String[] keys) { // Issue all of the reads at once List> documentFutures = new ArrayList<>(); for (String key : keys) { - documentFutures.add(getDocumentById(CosmosDbKeyEscape.escapeKey(key, - cosmosDbStorageOptions.getKeySuffix(), cosmosDbStorageOptions.getCompatibilityMode()))); + documentFutures.add( + getDocumentById( + CosmosDbKeyEscape.escapeKey( + key, + cosmosDbStorageOptions.getKeySuffix(), + cosmosDbStorageOptions.getCompatibilityMode() + ) + ) + ); } // Map each returned Document to it's original value. @@ -190,8 +208,13 @@ public CompletableFuture write(Map changes) { DocumentStoreItem documentChange = new DocumentStoreItem() { { - setId(CosmosDbKeyEscape.escapeKey(change.getKey(), cosmosDbStorageOptions.getKeySuffix(), - cosmosDbStorageOptions.getCompatibilityMode())); + setId( + CosmosDbKeyEscape.escapeKey( + change.getKey(), + cosmosDbStorageOptions.getKeySuffix(), + cosmosDbStorageOptions.getCompatibilityMode() + ) + ); setReadId(change.getKey()); setDocument(node.toString()); setType(change.getValue().getClass().getTypeName()); @@ -243,8 +266,8 @@ public CompletableFuture delete(String[] keys) { // issue the deletes in parallel return getCollection().thenCompose(collection -> Arrays.stream(keys).map(key -> { - String escapedKey = CosmosDbKeyEscape.escapeKey(key, cosmosDbStorageOptions.getKeySuffix(), - cosmosDbStorageOptions.getCompatibilityMode()); + String escapedKey = CosmosDbKeyEscape + .escapeKey(key, cosmosDbStorageOptions.getKeySuffix(), cosmosDbStorageOptions.getCompatibilityMode()); return getDocumentById(escapedKey).thenApplyAsync(document -> { if (document != null) { try { @@ -266,10 +289,10 @@ public CompletableFuture delete(String[] keys) { private Database getDatabase() { if (databaseCache == null) { // Get the database if it exists - List databaseList = client - .queryDatabases("SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getDatabaseId() + "'", - null) - .getQueryIterable().toList(); + List databaseList = client.queryDatabases( + "SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getDatabaseId() + "'", + null + ).getQueryIterable().toList(); if (databaseList.size() > 0) { // Cache the database object so we won't have to query for it @@ -306,9 +329,11 @@ private CompletableFuture getCollection() { return CompletableFuture.supplyAsync(() -> { // Get the collection if it exists. - List collectionList = client.queryCollections(getDatabase().getSelfLink(), - "SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getContainerId() + "'", null) - .getQueryIterable().toList(); + List collectionList = client.queryCollections( + getDatabase().getSelfLink(), + "SELECT * FROM root r WHERE r.id='" + cosmosDbStorageOptions.getContainerId() + "'", + null + ).getQueryIterable().toList(); if (collectionList.size() > 0) { // Cache the collection object so we won't have to query for it @@ -331,8 +356,8 @@ private CompletableFuture getCollection() { }; collectionCache = client - .createCollection(getDatabase().getSelfLink(), collectionDefinition, options) - .getResource(); + .createCollection(getDatabase().getSelfLink(), collectionDefinition, options) + .getResource(); } catch (DocumentClientException e) { // able to query or create the collection. // Verify your connection, endpoint, and key. @@ -350,8 +375,9 @@ private CompletableFuture getDocumentById(String id) { return getCollection().thenApplyAsync(collection -> { // Retrieve the document using the DocumentClient. List documentList = client - .queryDocuments(collection.getSelfLink(), "SELECT * FROM root r WHERE r.id='" + id + "'", null) - .getQueryIterable().toList(); + .queryDocuments(collection.getSelfLink(), "SELECT * FROM root r WHERE r.id='" + id + "'", null) + .getQueryIterable() + .toList(); if (documentList.size() > 0) { return documentList.get(0); diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java index 196f09a16..f51d2bc4b 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorageOptions.java @@ -201,6 +201,7 @@ public void setContainerThroughput(Integer withContainerThroughput) { * also allow for using older collections where no PartitionKey was specified. * * Note: CompatibilityMode cannot be 'true' if KeySuffix is used. + * * @return The compatibilityMode */ public Boolean getCompatibilityMode() { diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java index 19bda96ba..55d4cc331 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsStorage.java @@ -32,13 +32,14 @@ import java.util.concurrent.TimeUnit; /** - * Implements {@link Storage} using Azure Storage Blobs. - * This class uses a single Azure Storage Blob Container. - * Each entity or {@link StoreItem} is serialized into a JSON string and stored in an individual text blob. - * Each blob is named after the store item key, which is encoded so that it conforms a valid blob name. - * an entity is an {@link StoreItem}, the storage object will set the entity's {@link StoreItem} - * property value to the blob's ETag upon read. Afterward, an {@link BlobRequestConditions} with the ETag value - * will be generated during Write. New entities start with a null ETag. + * Implements {@link Storage} using Azure Storage Blobs. This class uses a + * single Azure Storage Blob Container. Each entity or {@link StoreItem} is + * serialized into a JSON string and stored in an individual text blob. Each + * blob is named after the store item key, which is encoded so that it conforms + * a valid blob name. an entity is an {@link StoreItem}, the storage object will + * set the entity's {@link StoreItem} property value to the blob's ETag upon + * read. Afterward, an {@link BlobRequestConditions} with the ETag value will be + * generated during Write. New entities start with a null ETag. */ public class BlobsStorage implements Storage { @@ -50,8 +51,10 @@ public class BlobsStorage implements Storage { /** * Initializes a new instance of the {@link BlobsStorage} class. + * * @param dataConnectionString Azure Storage connection string. - * @param containerName Name of the Blob container where entities will be stored. + * @param containerName Name of the Blob container where entities will be + * stored. */ public BlobsStorage(String dataConnectionString, String containerName) { if (StringUtils.isBlank(dataConnectionString)) { @@ -62,19 +65,18 @@ public BlobsStorage(String dataConnectionString, String containerName) { throw new IllegalArgumentException("containerName is required."); } - objectMapper = new ObjectMapper() - .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) + objectMapper = new ObjectMapper().configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false) .findAndRegisterModules() .enableDefaultTyping(); - containerClient = new BlobContainerClientBuilder() - .connectionString(dataConnectionString) - .containerName(containerName) - .buildClient(); + containerClient = new BlobContainerClientBuilder().connectionString(dataConnectionString) + .containerName(containerName) + .buildClient(); } /** * Deletes entity blobs from the configured container. + * * @param keys An array of entity keys. * @return A task that represents the work queued to execute. */ @@ -84,7 +86,7 @@ public CompletableFuture delete(String[] keys) { throw new IllegalArgumentException("The 'keys' parameter is required."); } - for (String key: keys) { + for (String key : keys) { String blobName = getBlobName(key); BlobClient blobClient = containerClient.getBlobClient(blobName); if (blobClient.exists()) { @@ -102,6 +104,7 @@ public CompletableFuture delete(String[] keys) { /** * Retrieve entities from the configured blob container. + * * @param keys An array of entity keys. * @return A task that represents the work queued to execute. */ @@ -136,6 +139,7 @@ public CompletableFuture> read(String[] keys) { /** * Stores a new entity in the configured blob container. + * * @param changes The changes to write to storage. * @return A task that represents the work queued to execute. */ @@ -157,28 +161,37 @@ public CompletableFuture write(Map changes) { StoreItem storeItem = newValue instanceof StoreItem ? (StoreItem) newValue : null; // "*" eTag in StoreItem converts to null condition for AccessCondition - boolean isNullOrEmpty = storeItem == null || StringUtils.isBlank(storeItem.getETag()) - || storeItem.getETag().equals("*"); - BlobRequestConditions accessCondition = !isNullOrEmpty - ? new BlobRequestConditions().setIfMatch(storeItem.getETag()) - : null; + boolean isNullOrEmpty = + storeItem == null || StringUtils.isBlank(storeItem.getETag()) || storeItem.getETag().equals("*"); + BlobRequestConditions accessCondition = + !isNullOrEmpty ? new BlobRequestConditions().setIfMatch(storeItem.getETag()) : null; String blobName = getBlobName(keyValuePair.getKey()); BlobClient blobReference = containerClient.getBlobClient(blobName); try { String json = objectMapper.writeValueAsString(newValue); InputStream stream = new ByteArrayInputStream(json.getBytes(StandardCharsets.UTF_8)); - //verify the corresponding length - blobReference.uploadWithResponse(stream, stream.available(), - null, null, - null, null, accessCondition, null, Context.NONE); + // verify the corresponding length + blobReference.uploadWithResponse( + stream, + stream.available(), + null, + null, + null, + null, + accessCondition, + null, + Context.NONE + ); } catch (HttpResponseException e) { if (e.getResponse().getStatusCode() == HttpStatus.SC_BAD_REQUEST) { StringBuilder sb = new StringBuilder("An error occurred while trying to write an object. The underlying "); sb.append(BlobErrorCode.INVALID_BLOCK_LIST); - sb.append(" error is commonly caused due to " - + "concurrently uploading an object larger than 128MB in size."); + sb.append( + " error is commonly caused due to " + + "concurrently uploading an object larger than 128MB in size." + ); throw new HttpResponseException(sb.toString(), e.getResponse()); } @@ -210,12 +223,13 @@ private CompletableFuture innerReadBlob(BlobClient blobReference) { while (true) { try (ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) { blobReference.download(outputStream); - String contentString = outputStream.toString(); + String contentString = outputStream.toString(); Object obj; // We are doing this try/catch because we are receiving String or HashMap try { - // We need to deserialize to an Object class since there are contentString which has an Object type + // We need to deserialize to an Object class since there are contentString which + // has an Object type obj = objectMapper.readValue(contentString, Object.class); } catch (MismatchedInputException ex) { // In case of the contentString has the structure of a HashMap, @@ -232,7 +246,8 @@ private CompletableFuture innerReadBlob(BlobClient blobReference) { } catch (HttpResponseException e) { if (e.getResponse().getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { // additional retry logic, - // even though this is a read operation blob storage can return 412 if there is contention + // even though this is a read operation blob storage can return 412 if there is + // contention if (i++ < retryTimes) { try { TimeUnit.MILLISECONDS.sleep(millisecondsTimeout); diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java index ada2b7c32..76b78e837 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/blobs/BlobsTranscriptStore.java @@ -68,8 +68,10 @@ public class BlobsTranscriptStore implements TranscriptStore { /** * Initializes a new instance of the {@link BlobsTranscriptStore} class. + * * @param dataConnectionString Azure Storage connection string. - * @param containerName Name of the Blob container where entities will be stored. + * @param containerName Name of the Blob container where entities will be + * stored. */ public BlobsTranscriptStore(String dataConnectionString, String containerName) { if (StringUtils.isBlank(dataConnectionString)) { @@ -80,8 +82,7 @@ public BlobsTranscriptStore(String dataConnectionString, String containerName) { throw new IllegalArgumentException("containerName"); } - jsonSerializer = new ObjectMapper() - .setSerializationInclusion(JsonInclude.Include.NON_NULL) + jsonSerializer = new ObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL) .enable(SerializationFeature.INDENT_OUTPUT) .findAndRegisterModules(); @@ -91,6 +92,7 @@ public BlobsTranscriptStore(String dataConnectionString, String containerName) { /** * Log an activity to the transcript. + * * @param activity Activity being logged. * @return A CompletableFuture that represents the work queued to execute. */ @@ -101,8 +103,8 @@ public CompletableFuture logActivity(Activity activity) { case ActivityTypes.MESSAGE_UPDATE: Activity updatedActivity = null; try { - updatedActivity = jsonSerializer - .readValue(jsonSerializer.writeValueAsString(activity), Activity.class); + updatedActivity = + jsonSerializer.readValue(jsonSerializer.writeValueAsString(activity), Activity.class); } catch (IOException ex) { ex.printStackTrace(); } @@ -122,6 +124,7 @@ public CompletableFuture logActivity(Activity activity) { }); return CompletableFuture.completedFuture(null); + case ActivityTypes.MESSAGE_DELETE: innerReadBlob(activity).thenAccept(activityAndBlob -> { if (activityAndBlob != null && activityAndBlob.getLeft() != null) { @@ -147,19 +150,20 @@ public CompletableFuture logActivity(Activity activity) { logActivityToBlobClient(tombstonedActivity, activityAndBlob.getRight(), true) .thenApply(task -> CompletableFuture.completedFuture(null)); - } + } }); return CompletableFuture.completedFuture(null); + default: - this.innerLogActivity(activity) - .thenApply(task -> CompletableFuture.completedFuture(null)); + this.innerLogActivity(activity).thenApply(task -> CompletableFuture.completedFuture(null)); return CompletableFuture.completedFuture(null); } } /** * Get activities for a conversation (Aka the transcript). + * * @param channelId The ID of the channel the conversation is in. * @param conversationId The ID of the conversation. * @param continuationToken The continuation token (if available). @@ -167,9 +171,12 @@ public CompletableFuture logActivity(Activity activity) { * not included. * @return PagedResult of activities. */ - public CompletableFuture> getTranscriptActivities(String channelId, String conversationId, - @Nullable String continuationToken, - OffsetDateTime startDate) { + public CompletableFuture> getTranscriptActivities( + String channelId, + String conversationId, + @Nullable String continuationToken, + OffsetDateTime startDate + ) { if (startDate == null) { startDate = OffsetDateTime.MIN; } @@ -194,11 +201,10 @@ public CompletableFuture> getTranscriptActivities(String c .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) .iterableByPage(token); token = null; - for (PagedResponse blobPage: resultSegment) { - for (BlobItem blobItem: blobPage.getValue()) { + for (PagedResponse blobPage : resultSegment) { + for (BlobItem blobItem : blobPage.getValue()) { OffsetDateTime parseDateTime = OffsetDateTime.parse(blobItem.getMetadata().get("Timestamp")); - if (parseDateTime.isAfter(startDate) - || parseDateTime.isEqual(startDate)) { + if (parseDateTime.isAfter(startDate) || parseDateTime.isEqual(startDate)) { if (continuationToken != null) { if (blobItem.getName().equals(continuationToken)) { // we found continuation token @@ -218,14 +224,10 @@ public CompletableFuture> getTranscriptActivities(String c } } while (!StringUtils.isBlank(token) && blobs.size() < pageSize); - pagedResult.setItems(blobs - .stream() - .map(bl -> { - BlobClient blobClient = containerClient.getBlobClient(bl.getName()); - return this.getActivityFromBlobClient(blobClient); - }) - .map(t -> t.join()) - .collect(Collectors.toList())); + pagedResult.setItems(blobs.stream().map(bl -> { + BlobClient blobClient = containerClient.getBlobClient(bl.getName()); + return this.getActivityFromBlobClient(blobClient); + }).map(t -> t.join()).collect(Collectors.toList())); if (pagedResult.getItems().size() == pageSize) { pagedResult.setContinuationToken(blobs.get(blobs.size() - 1).getName()); @@ -236,12 +238,15 @@ public CompletableFuture> getTranscriptActivities(String c /** * List conversations in the channelId. + * * @param channelId The ID of the channel. * @param continuationToken The continuation token (if available). * @return A CompletableFuture that represents the work queued to execute. */ - public CompletableFuture> listTranscripts(String channelId, - @Nullable String continuationToken) { + public CompletableFuture> listTranscripts( + String channelId, + @Nullable String continuationToken + ) { final int pageSize = 20; if (StringUtils.isBlank(channelId)) { @@ -253,16 +258,17 @@ public CompletableFuture> listTranscripts(String cha List conversations = new ArrayList(); do { String prefix = String.format("%s/", sanitizeKey(channelId)); - Iterable> resultSegment = containerClient. - listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) + Iterable> resultSegment = containerClient + .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) .iterableByPage(token); token = null; - for (PagedResponse blobPage: resultSegment) { - for (BlobItem blobItem: blobPage.getValue()) { + for (PagedResponse blobPage : resultSegment) { + for (BlobItem blobItem : blobPage.getValue()) { // Unescape the Id we escaped when we saved it String conversationId = new String(); String lastName = Arrays.stream(blobItem.getName().split("/")) - .reduce((first, second) -> second.length() > 0 ? second : first).get(); + .reduce((first, second) -> second.length() > 0 ? second : first) + .get(); try { conversationId = URLDecoder.decode(lastName, StandardCharsets.UTF_8.name()); } catch (UnsupportedEncodingException ex) { @@ -299,6 +305,7 @@ public CompletableFuture> listTranscripts(String cha /** * Delete a specific conversation and all of it's activities. + * * @param channelId The ID of the channel the conversation is in. * @param conversationId The ID of the conversation to delete. * @return A CompletableFuture that represents the work queued to execute. @@ -316,11 +323,12 @@ public CompletableFuture deleteTranscript(String channelId, String convers do { String prefix = String.format("%s/%s/", sanitizeKey(channelId), sanitizeKey(conversationId)); Iterable> resultSegment = containerClient - .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null).iterableByPage(token); + .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) + .iterableByPage(token); token = null; - for (PagedResponse blobPage: resultSegment) { - for (BlobItem blobItem: blobPage.getValue()) { + for (PagedResponse blobPage : resultSegment) { + for (BlobItem blobItem : blobPage.getValue()) { BlobClient blobClient = containerClient.getBlobClient(blobItem.getName()); if (blobClient.exists()) { try { @@ -345,19 +353,22 @@ private CompletableFuture> innerReadBlob(Activity act try { String token = null; do { - String prefix = String.format("%s/%s/", - sanitizeKey(activity.getChannelId()), sanitizeKey(activity.getConversation().getId())); + String prefix = String.format( + "%s/%s/", + sanitizeKey(activity.getChannelId()), + sanitizeKey(activity.getConversation().getId()) + ); Iterable> resultSegment = containerClient - .listBlobsByHierarchy("/", - this.getOptionsWithMetadata(prefix), null).iterableByPage(token); + .listBlobsByHierarchy("/", this.getOptionsWithMetadata(prefix), null) + .iterableByPage(token); token = null; - for (PagedResponse blobPage: resultSegment) { - for (BlobItem blobItem: blobPage.getValue()) { + for (PagedResponse blobPage : resultSegment) { + for (BlobItem blobItem : blobPage.getValue()) { if (blobItem.getMetadata().get("Id").equals(activity.getId())) { BlobClient blobClient = containerClient.getBlobClient(blobItem.getName()); - return this.getActivityFromBlobClient(blobClient) - .thenApply(blobActivity -> - new Pair(blobActivity, blobClient)); + return this.getActivityFromBlobClient( + blobClient + ).thenApply(blobActivity -> new Pair(blobActivity, blobClient)); } } @@ -370,7 +381,8 @@ private CompletableFuture> innerReadBlob(Activity act } catch (HttpResponseException ex) { if (ex.getResponse().getStatusCode() == HttpStatus.SC_PRECONDITION_FAILED) { // additional retry logic, - // even though this is a read operation blob storage can return 412 if there is contention + // even though this is a read operation blob storage can return 412 if there is + // contention if (i++ < retryTimes) { try { TimeUnit.MILLISECONDS.sleep(milisecondsTimeout); @@ -405,8 +417,11 @@ private CompletableFuture innerLogActivity(Activity activity) { return logActivityToBlobClient(activity, blobClient, null); } - private CompletableFuture logActivityToBlobClient(Activity activity, BlobClient blobClient, - Boolean overwrite) { + private CompletableFuture logActivityToBlobClient( + Activity activity, + BlobClient blobClient, + Boolean overwrite + ) { if (overwrite == null) { overwrite = false; } @@ -440,9 +455,13 @@ private CompletableFuture logActivityToBlobClient(Activity activity, BlobC } private String getBlobName(Activity activity) { - String blobName = String.format("%s/%s/%s-%s.json", - sanitizeKey(activity.getChannelId()), sanitizeKey(activity.getConversation().getId()), - this.formatTicks(activity.getTimestamp()), sanitizeKey(activity.getId())); + String blobName = String.format( + "%s/%s/%s-%s.json", + sanitizeKey(activity.getChannelId()), + sanitizeKey(activity.getConversation().getId()), + this.formatTicks(activity.getTimestamp()), + sanitizeKey(activity.getId()) + ); return blobName; } @@ -459,8 +478,7 @@ private String sanitizeKey(String key) { private BlobContainerClient getContainerClient(String dataConnectionString, String containerName) { containerName = containerName.toLowerCase(); - containerClient = new BlobContainerClientBuilder() - .connectionString(dataConnectionString) + containerClient = new BlobContainerClientBuilder().connectionString(dataConnectionString) .containerName(containerName) .buildClient(); if (!CHECKED_CONTAINERS.contains(containerName)) { @@ -478,12 +496,12 @@ private BlobContainerClient getContainerClient(String dataConnectionString, Stri /** * Formats a timestamp in a way that is consistent with the C# SDK. + * * @param dateTime The dateTime used to get the ticks * @return The String representing the ticks. */ private String formatTicks(OffsetDateTime dateTime) { - final Instant begin = ZonedDateTime.of(1, 1, 1, 0, 0, 0, 0, - ZoneOffset.UTC).toInstant(); + final Instant begin = ZonedDateTime.of(1, 1, 1, 0, 0, 0, 0, ZoneOffset.UTC).toInstant(); final Instant end = dateTime.toInstant(); long secsDiff = Math.subtractExact(end.getEpochSecond(), begin.getEpochSecond()); long totalHundredNanos = Math.multiplyExact(secsDiff, multipleProductValue); diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java index b351cc640..e839e7c85 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/queues/AzureQueueStorage.java @@ -27,8 +27,10 @@ public class AzureQueueStorage extends QueueStorage { /** * Initializes a new instance of the {@link AzureQueueStorage} class. + * * @param queuesStorageConnectionString Azure Storage connection string. - * @param queueName Name of the storage queue where entities will be queued. + * @param queueName Name of the storage queue where entities + * will be queued. */ public AzureQueueStorage(String queuesStorageConnectionString, String queueName) { if (StringUtils.isBlank(queuesStorageConnectionString)) { @@ -39,27 +41,32 @@ public AzureQueueStorage(String queuesStorageConnectionString, String queueName) throw new IllegalArgumentException("queueName is required."); } - queueClient = new QueueClientBuilder() - .connectionString(queuesStorageConnectionString) - .queueName(queueName) - .buildClient(); + queueClient = + new QueueClientBuilder().connectionString(queuesStorageConnectionString).queueName(queueName).buildClient(); } /** - * Queue an Activity to an Azure.Storage.Queues.QueueClient. - * The visibility timeout specifies how long the message should be invisible - * to Dequeue and Peek operations. The message content must be a UTF-8 encoded string that is up to 64KB in size. - * @param activity This is expected to be an {@link Activity} retrieved from a call to - * activity.GetConversationReference().GetContinuationActivity(). - * This enables restarting the conversation using BotAdapter.ContinueConversationAsync. + * Queue an Activity to an Azure.Storage.Queues.QueueClient. The visibility + * timeout specifies how long the message should be invisible to Dequeue and + * Peek operations. The message content must be a UTF-8 encoded string that is + * up to 64KB in size. + * + * @param activity This is expected to be an {@link Activity} retrieved + * from a call to + * activity.GetConversationReference().GetContinuationActivity(). + * This enables restarting the conversation using + * BotAdapter.ContinueConversationAsync. * @param visibilityTimeout Default value of 0. Cannot be larger than 7 days. - * @param timeToLive Specifies the time-to-live interval for the message. - * @return {@link SendMessageResult} as a Json string, from the QueueClient SendMessageAsync operation. + * @param timeToLive Specifies the time-to-live interval for the message. + * @return {@link SendMessageResult} as a Json string, from the QueueClient + * SendMessageAsync operation. */ @Override - public CompletableFuture queueActivity(Activity activity, - @Nullable Duration visibilityTimeout, - @Nullable Duration timeToLive) { + public CompletableFuture queueActivity( + Activity activity, + @Nullable Duration visibilityTimeout, + @Nullable Duration timeToLive + ) { return CompletableFuture.supplyAsync(() -> { if (createQueueIfNotExists) { try { @@ -68,7 +75,8 @@ public CompletableFuture queueActivity(Activity activity, throw new RuntimeException(e); } - // This is an optimization flag to check if the container creation call has been made. + // This is an optimization flag to check if the container creation call has been + // made. // It is okay if this is called more than once. createQueueIfNotExists = false; } From 82e0747b0985efe3af26ebf2215f5a61568a623f Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Wed, 24 Mar 2021 10:16:38 -0500 Subject: [PATCH 116/221] Added missing unit test - ShouldNotLogContinueConversation (#1077) * Added unit test for BotFrameworkAdapter - ShouldNotLogContinueConversation * Corrected wrong constant --- .../bot/builder/BotFrameworkAdapter.java | 3 +- .../bot/builder/BotFrameworkAdapterTests.java | 40 ++++++++++++++++++- .../bot/connector/rest/RestAttachments.java | 4 +- .../bot/connector/rest/RestConversations.java | 24 +++++------ .../bot/schema/ConversationReference.java | 2 +- 5 files changed, 56 insertions(+), 17 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index fffef0b33..e3aa09a72 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -31,6 +31,7 @@ import com.microsoft.bot.connector.rest.RestOAuthClient; import com.microsoft.bot.schema.AadResourceUrls; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityEventNames; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.CallerIdConstants; import com.microsoft.bot.schema.ChannelAccount; @@ -1040,7 +1041,7 @@ public CompletableFuture createConversation( .thenCompose(conversationResourceResponse -> { // Create a event activity to represent the result. Activity eventActivity = Activity.createEventActivity(); - eventActivity.setName("CreateConversation"); + eventActivity.setName(ActivityEventNames.CREATE_CONVERSATION); eventActivity.setChannelId(channelId); eventActivity.setServiceUrl(serviceUrl); eventActivity.setId( diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java index 58a72a3e7..d8772559f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java @@ -7,6 +7,8 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; import com.microsoft.bot.connector.Channels; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; @@ -19,6 +21,7 @@ import com.microsoft.bot.connector.authentication.SimpleChannelProvider; import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityEventNames; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.CallerIdConstants; import com.microsoft.bot.schema.ConversationAccount; @@ -32,6 +35,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.UUID; +import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; @@ -66,7 +71,7 @@ public void CreateConversationOverloadProperlySetsTenantId() { final String ActivityIdValue = "SendActivityId"; final String ConversationIdValue = "NewConversationId"; final String TenantIdValue = "theTenantId"; - final String EventActivityName = "CreateConversation"; + final String EventActivityName = ActivityEventNames.CREATE_CONVERSATION; // so we can provide a mock ConnectorClient. class TestBotFrameworkAdapter extends BotFrameworkAdapter { @@ -319,6 +324,39 @@ private void processActivityCreatesCorrectCredsAndClient( }, callback).join(); } + @Test + public void ShouldNotLogContinueConversation() { + TranscriptStore transcriptStore = new MemoryTranscriptStore(); + TranscriptLoggerMiddleware sut = new TranscriptLoggerMiddleware(transcriptStore); + + String conversationId = UUID.randomUUID().toString(); + TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference(conversationId, "User1", "Bot")) + .use(sut); + + Activity continueConversation = new Activity(ActivityTypes.EVENT); + continueConversation.setName(ActivityEventNames.CONTINUE_CONVERSATION); + + new TestFlow(adapter, turnContext -> { + return turnContext.sendActivity("bar").thenApply(resourceResponse -> null); + }) + .send("foo") + .assertReply(activity -> { + Assert.assertEquals("bar", activity.getText()); + PagedResult activities = transcriptStore.getTranscriptActivities(activity.getChannelId(), conversationId).join(); + Assert.assertEquals(2, activities.getItems().size()); + }) + .send(continueConversation) + .assertReply(activity -> { + // Ensure the event hasn't been added to the transcript. + PagedResult activities = transcriptStore.getTranscriptActivities(activity.getChannelId(), conversationId).join(); + + Assert.assertFalse(activities.getItems().stream().anyMatch(a -> a.isType(ActivityTypes.EVENT) && StringUtils + .equals(a.getName(), ActivityEventNames.CONTINUE_CONVERSATION))); + Assert.assertEquals(3, activities.getItems().size()); + }) + .startTest().join(); + } + private static void getAppCredentialsAndAssertValues( TurnContext turnContext, String expectedAppId, diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java index e8705ac53..7fbc2d006 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestAttachments.java @@ -100,7 +100,7 @@ public CompletableFuture getAttachmentInfo(String attachmentId) } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("getAttachmentInfoAsync", responseBodyResponse); + throw new ErrorResponseException("getAttachmentInfo", responseBodyResponse); } }); } @@ -146,7 +146,7 @@ public CompletableFuture getAttachment(String attachmentId, String } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("getAttachmentAsync", responseBodyResponse); + throw new ErrorResponseException("getAttachment", responseBodyResponse); } }); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java index 14600a500..aea20187c 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/rest/RestConversations.java @@ -236,7 +236,7 @@ public CompletableFuture getConversations(String continuati } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("getConversationsAsync", responseBodyResponse); + throw new ErrorResponseException("getConversations", responseBodyResponse); } }); } @@ -279,7 +279,7 @@ public CompletableFuture createConversation( throw e; } catch (Throwable t) { throw new ErrorResponseException( - "createConversationAsync", + "createConversation", responseBodyResponse ); } @@ -339,7 +339,7 @@ public CompletableFuture sendToConversation( } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("sendToConversationAsync", responseBodyResponse); + throw new ErrorResponseException("sendToConversation", responseBodyResponse); } }); } @@ -402,7 +402,7 @@ public CompletableFuture updateActivity( throw e; } catch (Throwable t) { throw new ErrorResponseException( - "updateActivityAsync", responseBodyResponse); + "updateActivity", responseBodyResponse); } }); }); @@ -463,7 +463,7 @@ public CompletableFuture replyToActivity( } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("replyToActivityAsync", responseBodyResponse); + throw new ErrorResponseException("replyToActivity", responseBodyResponse); } }); } @@ -511,7 +511,7 @@ public CompletableFuture deleteActivity(String conversationId, String acti } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("deleteActivityAsync", responseBodyResponse); + throw new ErrorResponseException("deleteActivity", responseBodyResponse); } }); } @@ -553,7 +553,7 @@ public CompletableFuture> getConversationMembers(String con throw e; } catch (Throwable t) { throw new ErrorResponseException( - "getConversationMembersAsync", + "getConversationMembers", responseBodyResponse ); } @@ -603,7 +603,7 @@ public CompletableFuture getConversationMember( throw e; } catch (Throwable t) { throw new ErrorResponseException( - "getConversationMembersAsync", + "getConversationMember", responseBodyResponse ); } @@ -656,7 +656,7 @@ public CompletableFuture deleteConversationMember( throw e; } catch (Throwable t) { throw new ErrorResponseException( - "deleteConversationMemberAsync", + "deleteConversationMember", responseBodyResponse ); } @@ -707,7 +707,7 @@ public CompletableFuture> getActivityMembers( } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("getActivityMembersAsync", responseBodyResponse); + throw new ErrorResponseException("getActivityMembers", responseBodyResponse); } }); } @@ -757,7 +757,7 @@ public CompletableFuture uploadAttachment( } catch (ErrorResponseException e) { throw e; } catch (Throwable t) { - throw new ErrorResponseException("uploadAttachmentAsync", responseBodyResponse); + throw new ErrorResponseException("uploadAttachment", responseBodyResponse); } }); } @@ -812,7 +812,7 @@ public CompletableFuture sendConversationHistory( throw e; } catch (Throwable t) { throw new ErrorResponseException( - "sendConversationHistoryAsync", + "sendConversationHistory", responseBodyResponse ); } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java index bd6d59e3f..e67a97ff9 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java @@ -73,7 +73,7 @@ public static ConversationReference clone(ConversationReference conversationRefe @JsonIgnore public Activity getContinuationActivity() { Activity activity = Activity.createEventActivity(); - activity.setName("ContinueConversation"); + activity.setName(ActivityEventNames.CONTINUE_CONVERSATION); activity.setId(UUID.randomUUID().toString()); activity.setChannelId(getChannelId()); activity.setConversation(getConversation()); From 382f657aabf80ca1c3ba1d57360125fb236789ec Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 26 Mar 2021 10:42:51 -0500 Subject: [PATCH 117/221] Added support for Command activities (#1092) --- .../bot/builder/ActivityHandler.java | 62 ++++++++++++++ .../bot/builder/ActivityHandlerTests.java | 48 +++++++++++ .../microsoft/bot/schema/ActivityTypes.java | 10 +++ .../bot/schema/CommandResultValue.java | 84 +++++++++++++++++++ .../microsoft/bot/schema/CommandValue.java | 61 ++++++++++++++ 5 files changed, 265 insertions(+) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandResultValue.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandValue.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index e671f9edb..e9c4b6918 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -85,6 +85,12 @@ public CompletableFuture onTurn(TurnContext turnContext) { case ActivityTypes.INSTALLATION_UPDATE: return onInstallationUpdate(turnContext); + case ActivityTypes.COMMAND: + return onCommandActivity(turnContext); + + case ActivityTypes.COMMAND_RESULT: + return onCommandResultActivity(turnContext); + case ActivityTypes.END_OF_CONVERSATION: return onEndOfConversationActivity(turnContext); @@ -528,6 +534,62 @@ protected CompletableFuture onInstallationUpdate(TurnContext turnContext) } } + /** + * Invoked when a command activity is received when the base behavior of + * {@link ActivityHandler#onTurn(TurnContext)} is used. Commands are requests to perform an + * action and receivers typically respond with one or more commandResult + * activities. Receivers are also expected to explicitly reject unsupported + * command activities. + * + * @param turnContext A strongly-typed context Object for this + * turn. + * + * @return A task that represents the work queued to execute. + * + * When the {@link ActivityHandler#onTurn(TurnContext)} method receives a command activity, + * it calls this method. In a derived class, override this method to add + * logic that applies to all comand activities. Add logic to apply before + * the specific command-handling logic before the call to the base class + * {@link ActivityHandler#onCommandActivity(TurnContext)} method. Add + * logic to apply after the specific command-handling logic after the call + * to the base class + * {@link ActivityHandler#onCommandActivity(TurnContext)} method. Command + * activities communicate programmatic information from a client or channel + * to a bot. The meaning of an command activity is defined by the + * name property, which is meaningful within the scope of a channel. + */ + protected CompletableFuture onCommandActivity(TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + + /** + * Invoked when a CommandResult activity is received when the + * base behavior of {@link ActivityHandler#onTurn(TurnContext)} is used. CommandResult + * activities can be used to communicate the result of a command execution. + * + * @param turnContext A strongly-typed context Object for this + * turn. + * + * @return A task that represents the work queued to execute. + * + * When the {@link ActivityHandler#onTurn(TurnContext)} method receives a CommandResult + * activity, it calls this method. In a derived class, override this method + * to add logic that applies to all comand activities. Add logic to apply + * before the specific CommandResult-handling logic before the call to the + * base class + * {@link ActivityHandler#onCommandResultActivity(TurnContext)} + * method. Add logic to apply after the specific CommandResult-handling + * logic after the call to the base class + * {@link ActivityHandler#onCommandResultActivity(TurnContext)} + * method. CommandResult activities communicate programmatic information + * from a client or channel to a bot. The meaning of an CommandResult + * activity is defined by the name property, + * which is meaningful within the scope of a channel. + */ + protected CompletableFuture onCommandResultActivity(TurnContext turnContext) { + return CompletableFuture.completedFuture(null); + } + /** * Override this in a derived class to provide logic specific to ActivityTypes.InstallationUpdate * activities with 'action' set to 'add'. diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index b6a5af8eb..6c8c30b2c 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -408,6 +408,42 @@ public void TestEventNullNameAsync() { Assert.assertEquals("onEvent", bot.getRecord().get(1)); } + @Test + public void TestCommandActivityType() { + Activity activity = new Activity(ActivityTypes.COMMAND); + activity.setName("application/test"); + CommandValue commandValue = new CommandValue(); + commandValue.setCommandId("Test"); + commandValue.setData(new Object()); + activity.setValue(commandValue); + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(bot.getRecord().size(), 1); + Assert.assertEquals("onCommandActivity", bot.record.get(0)); + } + + @Test + public void TestCommandResultActivityType() { + Activity activity = new Activity(ActivityTypes.COMMAND_RESULT); + activity.setName("application/test"); + CommandResultValue commandValue = new CommandResultValue(); + commandValue.setCommandId("Test"); + commandValue.setData(new Object()); + activity.setValue(commandValue); + + TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(bot.getRecord().size(), 1); + Assert.assertEquals("onCommandResultActivity", bot.record.get(0)); + } + @Test public void TestUnrecognizedActivityType() { Activity activity = new Activity() { @@ -570,5 +606,17 @@ protected CompletableFuture onUnrecognizedActivityType(TurnContext turnContext) return super.onUnrecognizedActivityType(turnContext); } + @Override + protected CompletableFuture onCommandActivity(TurnContext turnContext){ + record.add("onCommandActivity"); + return super.onCommandActivity(turnContext); + } + + @Override + protected CompletableFuture onCommandResultActivity(TurnContext turnContext) { + record.add("onCommandResultActivity"); + return super.onCommandResultActivity(turnContext); + } + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityTypes.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityTypes.java index cfcf66abb..d6a350659 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityTypes.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ActivityTypes.java @@ -31,6 +31,16 @@ private ActivityTypes() { */ public static final String TYPING = "typing"; + /** + * Enum value for Command Activities. + */ + public static final String COMMAND = "command"; + + /** + * Enum value for Command Result Activities. + */ + public static final String COMMAND_RESULT = "commandResult"; + /** * Enum value endOfConversation. */ diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandResultValue.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandResultValue.java new file mode 100644 index 000000000..4818dca60 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandResultValue.java @@ -0,0 +1,84 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The value field of a CommandResultActivity contains metadata related + * to a command result.An optional extensible data payload may be included if + * defined by the command result activity name. The presence of an error field + * indicates that the original command failed to complete. + * @param The type of the CommandResultValue. + */ +public class CommandResultValue { + + @JsonProperty(value = "commandId") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String commandId; + + @JsonProperty(value = "data") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private T data; + + @JsonProperty(value = "error") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Error error; + + /** + * Gets the id of the command. + * @return the CommandId value as a String. + */ + public String getCommandId() { + return this.commandId; + } + + /** + * Sets the id of the command. + * @param withCommandId The CommandId value. + */ + public void setCommandId(String withCommandId) { + this.commandId = withCommandId; + } + + /** + * Gets the data field containing optional parameters specific to + * this command result activity, as defined by the name. The value of the + * data field is a complex type. + * @return the Data value as a T. + */ + public T getData() { + return this.data; + } + + /** + * Sets the data field containing optional parameters specific to + * this command result activity, as defined by the name. The value of the + * data field is a complex type. + * @param withData The Data value. + */ + public void setData(T withData) { + this.data = withData; + } + + /** + * Gets the optional error, if the command result indicates a + * failure. + * @return the Error value as a getError(). + */ + public Error getError() { + return this.error; + } + + /** + * Sets the optional error, if the command result indicates a + * failure. + * @param withError The Error value. + */ + public void setError(Error withError) { + this.error = withError; + } + +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandValue.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandValue.java new file mode 100644 index 000000000..d80b9e708 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CommandValue.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * The value field of a CommandActivity contains metadata related to a + * command.An optional extensible data payload may be included if defined by + * the command activity name. + * @param The type of the CommandValue. + */ +public class CommandValue { + + @JsonProperty(value = "commandId") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String commandId; + + @JsonProperty(value = "data") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private T data; + + /** + * Gets the id of the command. + * @return the CommandId value as a String. + */ + public String getCommandId() { + return this.commandId; + } + + /** + * Sets the id of the command. + * @param withCommandId The CommandId value. + */ + public void setCommandId(String withCommandId) { + this.commandId = withCommandId; + } + + /** + * Gets the data field containing optional parameters specific to + * this command activity, as defined by the name. The value of the data + * field is a complex type. + * @return the Data value as a T. + */ + public T getData() { + return this.data; + } + + /** + * Sets the data field containing optional parameters specific to + * this command activity, as defined by the name. The value of the data + * field is a complex type. + * @param withData The Data value. + */ + public void setData(T withData) { + this.data = withData; + } + +} From 918b55cf4b18be6d1733ece7645c5fd56033f366 Mon Sep 17 00:00:00 2001 From: tracyboehrer Date: Fri, 26 Mar 2021 13:06:42 -0500 Subject: [PATCH 118/221] Added a gaggle of missing joins in unit tests (#1099) * Added a gaggle of missing joins in unit tests * Corrected some CompletableFuture exceptional returns in unit tests * More missing joins in unit tests. --- README.md | 2 +- .../microsoft/bot/azure/AzureQueueTests.java | 7 ++-- .../bot/azure/TranscriptStoreTests.java | 3 +- .../bot/builder/ActivityHandlerTests.java | 7 ++-- .../microsoft/bot/builder/BotStateTests.java | 2 +- .../bot/builder/MessageFactoryTests.java | 2 +- .../bot/builder/OnTurnErrorTests.java | 2 +- .../SkillConversationIdFactoryTests.java | 2 +- .../bot/builder/TestAdapterTests.java | 3 +- .../bot/builder/TurnContextTests.java | 7 ++-- .../TeamsActivityHandlerHidingTests.java | 7 ++-- .../dialogs/PromptValidatorContextTests.java | 2 +- .../bot/dialogs/SkillDialogTests.java | 4 +-- .../microsoft/bot/dialogs/WaterfallTests.java | 34 +++++++++---------- .../dialogs/prompts/ChoicePromptTests.java | 2 +- .../dialogs/prompts/NumberPromptTests.java | 22 ++++++------ .../bot/dialogs/prompts/OAuthPromptTests.java | 8 ++--- .../bot/dialogs/prompts/TextPromptTests.java | 4 +-- 18 files changed, 63 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index f94e24d81..69b6bb550 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ For more information jump to a section below. | Branch | Description | Build Status | Coverage Status | |--------|-------------|--------------|-----------------| - |Main | Preview 8 Builds | [![Build Status](https://travis-ci.org/Microsoft/botbuilder-java.svg?branch=main)](https://travis-ci.org/Microsoft/botbuilder-java) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | + |Main | Preview 8 Builds | [![Build Status](https://fuselabs.visualstudio.com/SDK_v4/_apis/build/status/Java/BotBuilder-Java-4.0-daily?branchName=main)](https://fuselabs.visualstudio.com/SDK_v4/_build/latest?definitionId=1202&branchName=main) | [![Coverage Status](https://coveralls.io/repos/github/microsoft/botbuilder-java/badge.svg?branch=823847c676b7dbb0fa348a308297ae375f5141ef)](https://coveralls.io/github/microsoft/botbuilder-java?branch=823847c676b7dbb0fa348a308297ae375f5141ef) | ## Getting Started To get started building bots using the SDK, see the [Azure Bot Service Documentation](https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0). diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java index fa03fa5e3..663e7b826 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java @@ -14,6 +14,7 @@ import com.microsoft.bot.builder.UserState; import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.dialogs.Dialog; import com.microsoft.bot.dialogs.DialogContext; import com.microsoft.bot.dialogs.DialogManager; @@ -167,13 +168,13 @@ public CompletableFuture beginDialog(DialogContext dc, Object try { date = LocalDateTime.parse(dateString); } catch (DateTimeParseException ex) { - throw new IllegalArgumentException("Date is invalid"); + return Async.completeExceptionally(new IllegalArgumentException("Date is invalid")); } ZonedDateTime zonedDate = date.atZone(ZoneOffset.UTC); ZonedDateTime now = LocalDateTime.now().atZone(ZoneOffset.UTC); if (zonedDate.isBefore(now)) { - throw new IllegalArgumentException("Date must be in the future"); + return Async.completeExceptionally(new IllegalArgumentException("Date must be in the future")); } // create ContinuationActivity from the conversation reference. @@ -185,7 +186,7 @@ public CompletableFuture beginDialog(DialogContext dc, Object QueueStorage queueStorage = dc.getContext().getTurnState().get("QueueStorage"); if (queueStorage == null) { - throw new NullPointerException("Unable to locate QueueStorage in HostContext"); + return Async.completeExceptionally(new NullPointerException("Unable to locate QueueStorage in HostContext")); } return queueStorage.queueActivity(activity, visibility, ttl).thenCompose(receipt -> { // return the receipt as the result diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java index b9653bcc4..668971103 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java @@ -14,6 +14,7 @@ import com.microsoft.bot.builder.TranscriptStore; import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; @@ -536,7 +537,7 @@ private CompletableFuture> getPagedResult(ConversationRefe } if(pagedResult == null) { - throw new TimeoutException("Unable to retrieve pagedResult in time"); + return Async.completeExceptionally(new TimeoutException("Unable to retrieve pagedResult in time")); } return CompletableFuture.completedFuture(pagedResult); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 6c8c30b2c..66401f54b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.*; import org.junit.Assert; import org.junit.Test; @@ -467,7 +468,7 @@ public CompletableFuture sendActivities( TurnContext context, List activities ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } @Override @@ -475,7 +476,7 @@ public CompletableFuture updateActivity( TurnContext context, Activity activity ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } @Override @@ -483,7 +484,7 @@ public CompletableFuture deleteActivity( TurnContext context, ConversationReference reference ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java index 6264a05fe..a673b27a0 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotStateTests.java @@ -73,7 +73,7 @@ public CompletableFuture delete(String[] keys) { Assert.assertEquals(1, readCount[0]); Assert.assertEquals(0, storeCount[0]); - propertyA.set(context, "there"); + propertyA.set(context, "there").join(); Assert.assertEquals(0, storeCount[0]); // Set on property should not bump userState.saveChanges(context).join(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java index babf68f8c..75501d86d 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java @@ -545,7 +545,7 @@ public void ValidateIMBackWithNoTest() { null ); - turnContext.sendActivity(activity); + turnContext.sendActivity(activity).join(); } return CompletableFuture.completedFuture(null); }; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java index 9fd3e5287..d7324212b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/OnTurnErrorTests.java @@ -29,7 +29,7 @@ public void OnTurnError_Test() { new TestFlow(adapter, (turnContext -> { if (StringUtils.equals(turnContext.getActivity().getText(), "foo")) { - turnContext.sendActivity(turnContext.getActivity().getText()); + turnContext.sendActivity(turnContext.getActivity().getText()).join(); } if ( diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java index 2ca4d4625..864b66ff0 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java @@ -51,7 +51,7 @@ public void SkillConversationIdFactoryHappyPath() { skillConversationIdFactory.getSkillConversationReference(skillConversationId).join(); // Delete - skillConversationIdFactory.deleteConversationReference(skillConversationId); + skillConversationIdFactory.deleteConversationReference(skillConversationId).join(); // Retrieve again SkillConversationReference deletedConversationReference = diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java index efd49f498..58dfb3fd6 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java @@ -5,6 +5,7 @@ import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.ActionTypes; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; @@ -71,7 +72,7 @@ public void TestAdapter_ExceptionInBotOnReceive() { try { new TestFlow(adapter, turnContext -> { - throw new RuntimeException(uniqueExceptionId); + return Async.completeExceptionally(new RuntimeException(uniqueExceptionId)); }).test("foo", activity -> { Assert.assertNull(activity); }).startTest().join(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java index c46567d31..8fb64edf5 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java @@ -5,6 +5,7 @@ import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.connector.Attachments; import com.microsoft.bot.connector.ConnectorClient; import com.microsoft.bot.connector.Conversations; @@ -63,7 +64,7 @@ public void CacheValueUsingSetAndGet() { case "TestResponded": if (turnContext.getResponded()) { - throw new RuntimeException("Responded is true"); + return Async.completeExceptionally(new RuntimeException("Responded is true")); } return turnContext.sendActivity( @@ -419,7 +420,7 @@ public void DeleteOneActivityToAdapter() { TurnContext c = new TurnContextImpl(a, TestMessage.Message()); - c.deleteActivity("12345"); + c.deleteActivity("12345").join(); Assert.assertTrue(activityDeleted[0]); } @@ -440,7 +441,7 @@ public void DeleteConversationReferenceToAdapter() { } }; - c.deleteActivity(reference); + c.deleteActivity(reference).join(); Assert.assertTrue(activityDeleted[0]); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java index 9160ae1f5..c51ec6ea3 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java @@ -8,6 +8,7 @@ import com.microsoft.bot.builder.MessageFactory; import com.microsoft.bot.builder.TurnContext; import com.microsoft.bot.builder.TurnContextImpl; +import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; @@ -346,7 +347,7 @@ public CompletableFuture sendActivities( TurnContext context, List activities ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } @Override @@ -354,7 +355,7 @@ public CompletableFuture updateActivity( TurnContext context, Activity activity ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } @Override @@ -362,7 +363,7 @@ public CompletableFuture deleteActivity( TurnContext context, ConversationReference reference ) { - throw new RuntimeException(); + return Async.completeExceptionally(new RuntimeException()); } } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java index 539255999..a56bd6f83 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/PromptValidatorContextTests.java @@ -107,7 +107,7 @@ public void PromptValidatorContextRetryEnd() { return CompletableFuture.completedFuture(true); } else { promptContext.getContext().sendActivity( - MessageFactory.text("Please send a name that is longer than 3 characters.")); + MessageFactory.text("Please send a name that is longer than 3 characters.")).join(); } return CompletableFuture.completedFuture(false); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java index 6c56f035b..8c18e32dd 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/SkillDialogTests.java @@ -352,7 +352,7 @@ public void ShouldThrowHttpExceptionOnPostFailure() { conversationState); // Send something to the dialog - Assert.assertThrows(Exception.class, () -> client.sendActivity("irrelevant")); + Assert.assertThrows(Exception.class, () -> client.sendActivity("irrelevant").join()); } @Test @@ -548,7 +548,7 @@ public void EndOfConversationFromExpectRepliesCallsDeleteConversationReference() conversationState); // Send something to the dialog to start it - client.sendActivity("hello"); + client.sendActivity("hello").join(); SimpleConversationIdFactory factory = null; if (dialogOptions.getConversationIdFactory() != null diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java index ed5bd91cf..b7f2d2b22 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/WaterfallTests.java @@ -70,13 +70,13 @@ public void Waterfall() { DialogSet dialogs = new DialogSet(dialogState); WaterfallStep[] steps = new WaterfallStep[] {(step) -> { - step.getContext().sendActivity("step1"); + step.getContext().sendActivity("step1").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, (step) -> { - step.getContext().sendActivity("step2"); + step.getContext().sendActivity("step2").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, (step) -> { - step.getContext().sendActivity("step3"); + step.getContext().sendActivity("step3").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, }; @@ -140,13 +140,13 @@ public void WaterfallWithCallback() { StatePropertyAccessor dialogState = convoState.createProperty("dialogState"); DialogSet dialogs = new DialogSet(dialogState); WaterfallStep[] steps = new WaterfallStep[] {(step) -> { - step.getContext().sendActivity("step1"); + step.getContext().sendActivity("step1").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, (step) -> { - step.getContext().sendActivity("step2"); + step.getContext().sendActivity("step2").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, (step) -> { - step.getContext().sendActivity("step3"); + step.getContext().sendActivity("step3").join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); }, }; WaterfallDialog waterfallDialog = new WaterfallDialog("test", Arrays.asList(steps)); @@ -375,7 +375,7 @@ private WaterfallDialog Create_Waterfall2() { private class Waterfall2_Step1 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity("step1"); + stepContext.getContext().sendActivity("step1").join(); PromptOptions options = new PromptOptions(); options.setPrompt(MessageFactory.text("Enter a number.")); options.setRetryPrompt(MessageFactory.text("It must be a number")); @@ -388,10 +388,10 @@ private class Waterfall2_Step2 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { if (stepContext.getValues() != null) { int numberResult = (int) stepContext.getResult(); - stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)).join(); } - stepContext.getContext().sendActivity("step2"); + stepContext.getContext().sendActivity("step2").join(); PromptOptions options = new PromptOptions(); options.setPrompt(MessageFactory.text("Enter a number.")); options.setRetryPrompt(MessageFactory.text("It must be a number")); @@ -404,10 +404,10 @@ private class Waterfall2_Step3 implements WaterfallStep { public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { if (stepContext.getValues() != null) { int numberResult = (int) stepContext.getResult(); - stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)); + stepContext.getContext().sendActivity(String.format("Thanks for '%d'", numberResult)).join(); } - stepContext.getContext().sendActivity("step3"); + stepContext.getContext().sendActivity("step3").join(); Map resultMap = new HashMap(); resultMap.put("Value", "All Done!"); return stepContext.endDialog(resultMap); @@ -416,7 +416,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall3_Step1 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step1")); + stepContext.getContext().sendActivity(MessageFactory.text("step1")).join(); return stepContext.beginDialog("test-waterfall-b", null); } } @@ -424,7 +424,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall3_Step2 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step2")); + stepContext.getContext().sendActivity(MessageFactory.text("step2")).join(); return stepContext.beginDialog("test-waterfall-c", null); } } @@ -432,7 +432,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall4_Step1 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step1.1")); + stepContext.getContext().sendActivity(MessageFactory.text("step1.1")).join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } } @@ -440,7 +440,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall4_Step2 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step1.2")); + stepContext.getContext().sendActivity(MessageFactory.text("step1.2")).join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } } @@ -448,7 +448,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall5_Step1 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step2.1")); + stepContext.getContext().sendActivity(MessageFactory.text("step2.1")).join(); return CompletableFuture.completedFuture(Dialog.END_OF_TURN); } } @@ -456,7 +456,7 @@ public CompletableFuture waterfallStep(WaterfallStepContext st private class Waterfall5_Step2 implements WaterfallStep { @Override public CompletableFuture waterfallStep(WaterfallStepContext stepContext) { - stepContext.getContext().sendActivity(MessageFactory.text("step2.2")); + stepContext.getContext().sendActivity(MessageFactory.text("step2.2")).join(); return stepContext.endDialog(); } } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java index 42ea35187..38bc0e342 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -536,7 +536,7 @@ public void ShouldCallCustomValidator() { DialogSet dialogs = new DialogSet(dialogState); PromptValidator validator = (promptContext) -> { - promptContext.getContext().sendActivity(MessageFactory.text("validator called")); + promptContext.getContext().sendActivity(MessageFactory.text("validator called")).join(); return CompletableFuture.completedFuture(true); }; diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java index a8939f2a5..8a042586f 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java @@ -132,7 +132,7 @@ public void NumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int numberResult = (int) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -174,7 +174,7 @@ public void NumberPromptRetry() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int numberResult = (int) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -229,7 +229,7 @@ public void NumberPromptValidator() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { int numberResult = (int) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -270,7 +270,7 @@ public void FloatNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Float numberResult = (Float) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -309,7 +309,7 @@ public void LongNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Long numberResult = (Long) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%d'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -348,7 +348,7 @@ public void DoubleNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -387,7 +387,7 @@ public void CurrencyNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) .send("hello") @@ -425,7 +425,7 @@ public void AgeNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -464,7 +464,7 @@ public void DimensionNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -503,7 +503,7 @@ public void TemperatureNumberPrompt() throws UnsupportedDataTypeException { } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.0f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) @@ -621,7 +621,7 @@ public void NumberPromptDefaultsToEnUsLocale() throws UnsupportedDataTypeExcepti } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { Double numberResult = (Double) results.getResult(); turnContext.sendActivity( - MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))); + MessageFactory.text(String.format("Bot received the number '%.2f'.", numberResult))).join(); } return CompletableFuture.completedFuture(null); }) diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java index 8e07128c4..88deb325b 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java @@ -130,9 +130,9 @@ public void OAuthPromptWithMagicCode() { dc.prompt("OAuthPrompt", new PromptOptions()); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { if (results.getResult() instanceof TokenResponse) { - turnContext.sendActivity(MessageFactory.text("Logged in.")); + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); } else { - turnContext.sendActivity(MessageFactory.text("Failed.")); + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); } } return CompletableFuture.completedFuture(null); @@ -599,9 +599,9 @@ private void OAuthPrompt(Storage storage) { dc.prompt("OAuthPrompt", new PromptOptions()); } else if (results.getStatus() == DialogTurnStatus.COMPLETE) { if (results.getResult() instanceof TokenResponse) { - turnContext.sendActivity(MessageFactory.text("Logged in.")); + turnContext.sendActivity(MessageFactory.text("Logged in.")).join(); } else { - turnContext.sendActivity(MessageFactory.text("Failed.")); + turnContext.sendActivity(MessageFactory.text("Failed.")).join(); } } return CompletableFuture.completedFuture(null); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java index 1c06ab1ec..4c0f59afb 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/TextPromptTests.java @@ -176,7 +176,7 @@ public void TextPromptValidator() { String value = promptContext.getRecognized().getValue(); if (value.length() <= 3) { promptContext.getContext() - .sendActivity(MessageFactory.text("Make sure the text is greater than three characters.")); + .sendActivity(MessageFactory.text("Make sure the text is greater than three characters.")).join(); return CompletableFuture.completedFuture(false); } else { return CompletableFuture.completedFuture(true); @@ -270,7 +270,7 @@ public void TextPromptValidatorWithMessageShouldNotSendRetryPrompt() { String value = promptContext.getRecognized().getValue(); if (value.length() <= 3) { promptContext.getContext() - .sendActivity(MessageFactory.text("The text should be greater than 3 chars.")); + .sendActivity(MessageFactory.text("The text should be greater than 3 chars.")).join(); return CompletableFuture.completedFuture(false); } else { return CompletableFuture.completedFuture(true); From 1eb5a855a6be85b62cd4892fcf19f13cad9aabd9 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 26 Mar 2021 14:15:27 -0500 Subject: [PATCH 119/221] Added SetSpeakMiddleware.java and Unit tests (#1100) Co-authored-by: tracyboehrer --- .../bot/builder/SetSpeakMiddleware.java | 103 +++++++++++ .../bot/builder/SetSpeakMiddlewareTests.java | 161 ++++++++++++++++++ 2 files changed, 264 insertions(+) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java new file mode 100644 index 000000000..23576df5b --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java @@ -0,0 +1,103 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. +package com.microsoft.bot.builder; + +import java.io.IOException; +import java.util.concurrent.CompletableFuture; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; + +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; + +import org.apache.commons.lang3.StringUtils; +import org.w3c.dom.Document; +import org.xml.sax.SAXException; + +/** + * Support the DirectLine speech and telephony channels to ensure the + * appropriate SSML tags are set on the Activity Speak property. + */ +public class SetSpeakMiddleware implements Middleware { + + private final String voiceName; + private final boolean fallbackToTextForSpeak; + private final String lang; + + /** + * Initializes a new instance of the {@link SetSpeakMiddleware} class. + * + * @param voiceName The SSML voice name attribute value. + * @param lang The xml:lang value. + * @param fallbackToTextForSpeak true if an empt Activity.Speak is populated + * with Activity.getText(). + */ + public SetSpeakMiddleware(String voiceName, String lang, boolean fallbackToTextForSpeak) { + this.voiceName = voiceName; + this.fallbackToTextForSpeak = fallbackToTextForSpeak; + if (lang == null) { + throw new IllegalArgumentException("lang cannot be null."); + } else { + this.lang = lang; + } + } + + /** + * Processes an incoming activity. + * + * @param turnContext The context Object for this turn. + * @param next The delegate to call to continue the bot middleware + * pipeline. + * + * @return A task that represents the work queued to execute. + */ + public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { + turnContext.onSendActivities((ctx, activities, nextSend) -> { + for (Activity activity : activities) { + if (activity.getType().equals(ActivityTypes.MESSAGE)) { + if (fallbackToTextForSpeak && StringUtils.isBlank(activity.getSpeak())) { + activity.setSpeak(activity.getText()); + } + + if (StringUtils.isNotBlank(activity.getSpeak()) && StringUtils.isNotBlank(voiceName) + && (StringUtils.compareIgnoreCase(turnContext.getActivity().getChannelId(), + Channels.DIRECTLINESPEECH) == 0 + || StringUtils.compareIgnoreCase(turnContext.getActivity().getChannelId(), + Channels.EMULATOR) == 0 + || StringUtils.compareIgnoreCase(turnContext.getActivity().getChannelId(), + "telephony") == 0)) { + if (!hasTag("speak", activity.getSpeak()) && !hasTag("voice", activity.getSpeak())) { + activity.setSpeak( + String.format("%s", voiceName, activity.getSpeak())); + } + + activity.setSpeak(String + .format("%s", lang, activity.getSpeak())); + } + } + } + return nextSend.get(); + }); + return next.next(); + } + + private boolean hasTag(String tagName, String speakText) { + try { + DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); + DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); + Document doc = dBuilder.parse(speakText); + + if (doc.getElementsByTagName(tagName).getLength() > 0) { + return true; + } + + return false; + } catch (SAXException | ParserConfigurationException | IOException ex) { + return false; + } + } +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java new file mode 100644 index 000000000..19ebf79f0 --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java @@ -0,0 +1,161 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.ConversationReference; + +import org.junit.Assert; +import org.junit.Test; + +public class SetSpeakMiddlewareTests { + + @Test + public void ConstructorValidation() { + // no 'lang' + Assert.assertThrows(IllegalArgumentException.class, () -> new SetSpeakMiddleware("voice", null, false)); + } + + @Test + public void NoFallback() { + TestAdapter adapter = new TestAdapter(createConversation("NoFallback")) + .use(new SetSpeakMiddleware("male", "en-us", false)); + + new TestFlow(adapter, turnContext -> { + Activity activity = MessageFactory.text("OK"); + + return turnContext.sendActivity(activity).thenApply(result -> null); + }).send("foo").assertReply(obj -> { + Activity activity = (Activity) obj; + Assert.assertNull(activity.getSpeak()); + }).startTest().join(); + } + + // fallback instanceof true, for any ChannelId other than emulator, + // directlinespeech, or telephony should + // just set Activity.Speak to Activity.Text if Speak instanceof empty. + @Test + public void FallbackNullSpeak() { + TestAdapter adapter = new TestAdapter(createConversation("NoFallback")) + .use(new SetSpeakMiddleware("male", "en-us", true)); + + new TestFlow(adapter, turnContext -> { + Activity activity = MessageFactory.text("OK"); + + return turnContext.sendActivity(activity).thenApply(result -> null); + }).send("foo").assertReply(obj -> { + Activity activity = (Activity) obj; + Assert.assertEquals(activity.getText(), activity.getSpeak()); + }).startTest().join(); + } + + // fallback instanceof true, for any ChannelId other than emulator, + // directlinespeech, or telephony should + // leave a non-empty Speak unchanged. + @Test + public void FallbackWithSpeak() { + TestAdapter adapter = new TestAdapter(createConversation("Fallback")) + .use(new SetSpeakMiddleware("male", "en-us", true)); + + new TestFlow(adapter, turnContext -> { + Activity activity = MessageFactory.text("OK"); + activity.setSpeak("speak value"); + + return turnContext.sendActivity(activity).thenApply(result -> null); + }).send("foo").assertReply(obj -> { + Activity activity = (Activity) obj; + Assert.assertEquals("speak value", activity.getSpeak()); + }).startTest().join(); + } + + @Test + public void AddVoiceEmulator() { + AddVoice(Channels.EMULATOR); + } + + @Test + public void AddVoiceDirectlineSpeech() { + AddVoice(Channels.DIRECTLINESPEECH); + } + + @Test + public void AddVoiceTelephony() { + AddVoice("telephony"); + } + + + // Voice instanceof added to Speak property. + public void AddVoice(String channelId) { + TestAdapter adapter = new TestAdapter(createConversation("Fallback", channelId)) + .use(new SetSpeakMiddleware("male", "en-us", true)); + + new TestFlow(adapter, turnContext -> { + Activity activity = MessageFactory.text("OK"); + + return turnContext.sendActivity(activity).thenApply(result -> null); + }).send("foo").assertReply(obj -> { + Activity activity = (Activity) obj; + Assert.assertEquals("OK", + activity.getSpeak()); + }).startTest().join(); + } + + @Test + public void AddNoVoiceEmulator() { + AddNoVoice(Channels.EMULATOR); + } + + @Test + public void AddNoVoiceDirectlineSpeech() { + AddNoVoice(Channels.DIRECTLINESPEECH); + } + + @Test + public void AddNoVoiceTelephony() { + AddNoVoice("telephony"); + } + + + // With no 'voice' specified, the Speak property instanceof unchanged. + public void AddNoVoice(String channelId) { + TestAdapter adapter = new TestAdapter(createConversation("Fallback", channelId)) + .use(new SetSpeakMiddleware(null, "en-us", true)); + + new TestFlow(adapter, turnContext -> { + Activity activity = MessageFactory.text("OK"); + + return turnContext.sendActivity(activity).thenApply(result -> null); + }).send("foo").assertReply(obj -> { + Activity activity = (Activity) obj; + Assert.assertEquals("OK", + activity.getSpeak()); + }).startTest().join(); + } + + + private static ConversationReference createConversation(String name) { + return createConversation(name, "User1", "Bot", "test"); + } + + private static ConversationReference createConversation(String name, String channelId) { + return createConversation(name, "User1", "Bot", channelId); + } + + private static ConversationReference createConversation(String name, String user, String bot, String channelId) { + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setChannelId(channelId); + conversationReference.setServiceUrl("https://test.com"); + conversationReference.setConversation(new ConversationAccount(false, name, name)); + conversationReference.setUser(new ChannelAccount(user.toLowerCase(), user)); + conversationReference.setBot(new ChannelAccount(bot.toLowerCase(), bot)); + conversationReference.setLocale("en-us"); + return conversationReference; + } +} From 4573f0029c1f58de2ef5ca96c07455bfb0fdcb46 Mon Sep 17 00:00:00 2001 From: Victor Grycuk Date: Fri, 26 Mar 2021 16:26:18 -0300 Subject: [PATCH 120/221] [Generator] Include Core Bot in the bot generator (#1097) * Add core-bot as a template * Add missing ApplicationTest in sample and template * Rename generator to follow documentation as yo botbuilder-java, cleaning dependencies * Update echo and empty templates to use artifact property to execute the bot manually * Add descriptions for Core Bot Sample * Integrate Core Bot in generator, use of kebabCase for artifact, add big and tinyBot Co-authored-by: Martin Battaglino --- Generator/generator-botbuilder-java/README.md | 8 +- .../generators/app/index.js | 47 +- .../app/templates/core/project/README-LUIS.md | 216 + .../app/templates/core/project/README.md | 67 + .../cognitiveModels/FlightBooking.json | 339 + .../template-with-new-rg.json | 291 + .../template-with-preexisting-rg.json | 259 + .../app/templates/core/project/pom.xml | 248 + .../src/main/resources/application.properties | 6 + .../src/main/resources/cards/welcomeCard.json | 46 + .../project/src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../project/src/main/webapp/WEB-INF/web.xml | 12 + .../core/project/src/main/webapp/index.html | 417 ++ .../core/src/main/java/Application.java | 79 + .../core/src/main/java/BookingDetails.java | 69 + .../core/src/main/java/BookingDialog.java | 135 + .../src/main/java/CancelAndHelpDialog.java | 80 + .../src/main/java/DateResolverDialog.java | 111 + .../src/main/java/DialogAndWelcomeBot.java | 98 + .../core/src/main/java/DialogBot.java | 127 + .../main/java/FlightBookingRecognizer.java | 157 + .../core/src/main/java/MainDialog.java | 232 + .../core/src/main/java/package-info.java | 8 + .../core/src/test/java/ApplicationTest.java | 19 + .../app/templates/echo/project/README.md | 2 +- .../app/templates/empty/project/README.md | 2 +- .../package-lock.json | 6609 ++++++++--------- .../generator-botbuilder-java/package.json | 3 +- .../bot/sample/core/ApplicationTest.java | 17 + 30 files changed, 6177 insertions(+), 3548 deletions(-) create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java create mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java diff --git a/Generator/generator-botbuilder-java/README.md b/Generator/generator-botbuilder-java/README.md index 9ab728d91..f4ad7506b 100644 --- a/Generator/generator-botbuilder-java/README.md +++ b/Generator/generator-botbuilder-java/README.md @@ -15,6 +15,7 @@ The generator supports three different template options. The table below can he | ---------- | --------- | | Echo Bot | A good template if you want a little more than "Hello World!", but not much more. This template handles the very basics of sending messages to a bot, and having the bot process the messages by repeating them back to the user. This template produces a bot that simply "echoes" back to the user anything the user says to the bot. | | Empty Bot | A good template if you are familiar with Bot Framework v4, and simply want a basic skeleton project. Also a good option if you want to take sample code from the documentation and paste it into a minimal bot in order to learn. | +| Core Bot | A good template if you want to create advanced bots, as it uses multi-turn dialogs and [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. This template creates a bot that can extract places and dates to book a flight. | ### How to Choose a Template @@ -22,17 +23,22 @@ The generator supports three different template options. The table below can he | -------- | -------- | | Echo Bot | You are new to Bot Framework v4 and want a working bot with minimal features. | | Empty Bot | You are a seasoned Bot Framework v4 developer. You've built bots before, and want the minimum skeleton of a bot. | +| Core Bot | You are a medium to advanced user of Bot Framework v4 and want to start integrating language understanding as well as multi-turn dialogs in your bots. | ### Template Overview #### Echo Bot Template -The Echo Bot template is slightly more than the a classic "Hello World!" example, but not by much. This template shows the basic structure of a bot, how a bot recieves messages from a user, and how a bot sends messages to a user. The bot will "echo" back to the user, what the user says to the bot. It is a good choice for first time, new to Bot Framework v4 developers. +The Echo Bot template is slightly more than the a classic "Hello World!" example, but not by much. This template shows the basic structure of a bot, how a bot receives messages from a user, and how a bot sends messages to a user. The bot will "echo" back to the user, what the user says to the bot. It is a good choice for first time, new to Bot Framework v4 developers. #### Empty Bot Template The Empty Bot template is the minimal skeleton code for a bot. It provides a stub `onTurn` handler but does not perform any actions. If you are experienced writing bots with Bot Framework v4 and want the minimum scaffolding, the Empty template is for you. +#### Core Bot Template + +The Core Bot template uses [LUIS](https://www.luis.ai) to implement core AI capabilities, a multi-turn conversation using Dialogs, handles user interruptions, and prompts for and validate requests for information from the user. This template implements a basic three-step waterfall dialog, where the first step asks the user for an input to book a flight, then asks the user if the information is correct, and finally confirms the booking with the user. Choose this template if want to create an advanced bot that can extract information from the user's input. + ## Installation 1. Install [Yeoman](http://yeoman.io) using [npm](https://www.npmjs.com) (we assume you have pre-installed [node.js](https://nodejs.org/)). diff --git a/Generator/generator-botbuilder-java/generators/app/index.js b/Generator/generator-botbuilder-java/generators/app/index.js index 725a0f68b..66b306531 100644 --- a/Generator/generator-botbuilder-java/generators/app/index.js +++ b/Generator/generator-botbuilder-java/generators/app/index.js @@ -5,10 +5,9 @@ const pkg = require('../../package.json'); const Generator = require('yeoman-generator'); const path = require('path'); -const _ = require('lodash'); const chalk = require('chalk'); const mkdirp = require('mkdirp'); -const camelCase = require('camelcase'); +const _ = require('lodash'); const BOT_TEMPLATE_NAME_EMPTY = 'Empty Bot'; const BOT_TEMPLATE_NAME_SIMPLE = 'Echo Bot'; @@ -18,6 +17,33 @@ const BOT_TEMPLATE_NOPROMPT_EMPTY = 'empty'; const BOT_TEMPLATE_NOPROMPT_SIMPLE = 'echo'; const BOT_TEMPLATE_NOPROMPT_CORE = 'core'; +const bigBot = + ` ╭─────────────────────────────╮\n` + + ` ` + + chalk.blue.bold(`//`) + + ` ` + + chalk.blue.bold(`\\\\`) + + ` │ Welcome to the │\n` + + ` ` + + chalk.blue.bold(`//`) + + ` () () ` + + chalk.blue.bold(`\\\\`) + + ` │ Microsoft Java Bot Builder │\n` + + ` ` + + chalk.blue.bold(`\\\\`) + + ` ` + + chalk.blue.bold(`//`) + + ` /│ generator! │\n` + + ` ` + + chalk.blue.bold(`\\\\`) + + ` ` + + chalk.blue.bold(`//`) + + ` ╰─────────────────────────────╯\n` + + ` v${pkg.version}`; + +const tinyBot = + ` ` + chalk.blue.bold(`<`) + ` ** ` + chalk.blue.bold(`>`) + ` `; + module.exports = class extends Generator { constructor(args, opts) { super(args, opts); @@ -31,7 +57,7 @@ module.exports = class extends Generator { initializing() { // give the user some data before we start asking them questions - this.log(`\nWelcome to the Microsoft Java Bot Builder generator v${pkg.version}. `); + this.log(bigBot); } prompting() { @@ -59,8 +85,8 @@ module.exports = class extends Generator { const botName = this.templateConfig.botName; const packageName = this.templateConfig.packageName.toLowerCase(); const packageTree = packageName.replace(/\./g, '/'); - const artifact = camelCase(this.templateConfig.botName); - const directoryName = camelCase(this.templateConfig.botName); + const artifact = _.kebabCase(this.templateConfig.botName).replace(/([^a-z0-9-]+)/gi, ``); + const directoryName = _.camelCase(this.templateConfig.botName); const template = this.templateConfig.template.toLowerCase(); if (path.basename(this.destinationPath()) !== directoryName) { @@ -104,16 +130,16 @@ module.exports = class extends Generator { this.log(chalk.green('------------------------ ')); this.log(chalk.green(' Your new bot is ready! ')); this.log(chalk.green('------------------------ ')); - this.log(`Your bot should be in a directory named "${camelCase(this.templateConfig.botName)}"`); + this.log(`Your bot should be in a directory named "${_.camelCase(this.templateConfig.botName)}"`); this.log('Open the ' + chalk.green.bold('README.md') + ' to learn how to run your bot. '); this.log('Thank you for using the Microsoft Bot Framework. '); - this.log('\n< ** > The Bot Framework Team'); + this.log(`\n${tinyBot} The Bot Framework Team`); } else { this.log(chalk.red.bold('-------------------------------- ')); this.log(chalk.red.bold(' New bot creation was canceled. ')); this.log(chalk.red.bold('-------------------------------- ')); this.log('Thank you for using the Microsoft Bot Framework. '); - this.log('\n< ** > The Bot Framework Team'); + this.log(`\n${tinyBot} The Bot Framework Team`); } } @@ -191,14 +217,11 @@ module.exports = class extends Generator { { name: BOT_TEMPLATE_NAME_EMPTY, value: BOT_TEMPLATE_NOPROMPT_EMPTY - } - - /*, + }, { name: BOT_TEMPLATE_NAME_CORE, value: BOT_TEMPLATE_NOPROMPT_CORE } - */ ], default: (this.options.template ? _.toLower(this.options.template) : BOT_TEMPLATE_NOPROMPT_SIMPLE) }).then(answer => { diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md new file mode 100644 index 000000000..12bc78ed0 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md @@ -0,0 +1,216 @@ +# Setting up LUIS via CLI: + +This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. + +> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ +> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ +> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ + + [Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app + [Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app + +## Table of Contents: + +- [Prerequisites](#Prerequisites) +- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) +- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) + +___ + +## [Prerequisites](#Table-of-Contents): + +#### Install Azure CLI >=2.0.61: + +Visit the following page to find the correct installer for your OS: +- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest + +#### Install LUIS CLI >=2.4.0: + +Open a CLI of your choice and type the following: + +```bash +npm i -g luis-apis@^2.4.0 +``` + +#### LUIS portal account: + +You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. + +After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. + + [LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] + [LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key + +___ + +## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) + +### 1. Import the local LUIS application to luis.ai + +```bash +luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" +``` + +Outputs the following JSON: + +```json +{ + "id": "########-####-####-####-############", + "name": "FlightBooking", + "description": "A LUIS model that uses intent and entities.", + "culture": "en-us", + "usageScenario": "", + "domain": "", + "versionsCount": 1, + "createdDateTime": "2019-03-29T18:32:02Z", + "endpoints": {}, + "endpointHitsCount": 0, + "activeVersion": "0.1", + "ownerEmail": "bot@contoso.com", + "tokenizerVersion": "1.0.0" +} +``` + +For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. + +### 2. Train the LUIS Application + +```bash +luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait +``` + +### 3. Publish the LUIS Application + +```bash +luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" +``` + +> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
+> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
+> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
+> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. + + [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 + +Outputs the following: + +```json + { + "versionId": "0.1", + "isStaging": false, + "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", + "region": "westus", + "assignedEndpointKey": null, + "endpointRegion": "westus", + "failedRegions": "", + "publishedDateTime": "2019-03-29T18:40:32Z", + "directVersionPublish": false +} +``` + +To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. + + [README-LUIS]: ./README-LUIS.md + +___ + +## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) + +### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI + +> _Note:_
+> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ +> ```bash +> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" +> ``` +> _To see a list of valid locations, use `az account list-locations`_ + + +```bash +# Use Azure CLI to create the LUIS Key resource on Azure +az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +The command will output a response similar to the JSON below: + +```json +{ + "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", + "etag": "\"########-####-####-####-############\"", + "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", + "internalId": "################################", + "kind": "luis", + "location": "westus", + "name": "NewLuisResourceName", + "provisioningState": "Succeeded", + "resourceGroup": "ResourceGroupName", + "sku": { + "name": "S0", + "tier": null + }, + "tags": null, + "type": "Microsoft.CognitiveServices/accounts" +} +``` + + + +Take the output from the previous command and create a JSON file in the following format: + +```json +{ + "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", + "resourceGroup": "ResourceGroupName", + "accountName": "NewLuisResourceName" +} +``` + +### 2. Retrieve ARM access token via Azure CLI + +```bash +az account get-access-token --subscription "AzureSubscriptionGuid" +``` + +This will return an object that looks like this: + +```json +{ + "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", + "expiresOn": "2200-12-31 23:59:59.999999", + "subscription": "AzureSubscriptionGuid", + "tenant": "tenant-guid", + "tokenType": "Bearer" +} +``` + +The value needed for the next step is the `"accessToken"`. + +### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application + +```bash +luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" +``` + +If successful, it should yield a response like this: + +```json +{ + "code": "Success", + "message": "Operation Successful" +} +``` + +### 4. See the LUIS Cognitive Services' keys + +```bash +az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +This will return an object that looks like this: + +```json +{ + "key1": "9a69####dc8f####8eb4####399f####", + "key2": "####f99e####4b1a####fb3b####6b9f" +} +``` diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md new file mode 100644 index 000000000..5428395fc --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md @@ -0,0 +1,67 @@ +# <%= botName %> + +Bot Framework v4 core bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: + +- Use [LUIS](https://www.luis.ai) to implement core AI capabilities +- Implement a multi-turn conversation using Dialogs +- Handle user interruptions for such things as `Help` or `Cancel` +- Prompt for and validate requests for information from the user + +## Prerequisites + +This sample **requires** prerequisites in order to run. + +### Overview + +This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. + +### Create a LUIS Application to enable language understanding + +The LUIS model for this example can be found under `cognitiveModels/FlightBooking.json` and the LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). + +Once you created the LUIS model, update `application.properties` with your `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName`. + +``` + LuisAppId="Your LUIS App Id" + LuisAPIKey="Your LUIS Subscription key here" + LuisAPIHostName="Your LUIS App region here (i.e: westus.api.cognitive.microsoft.com)" +``` + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json new file mode 100644 index 000000000..603dd06b2 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json @@ -0,0 +1,339 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "FlightBooking", + "desc": "Luis Model for <%= botName %>", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "BookFlight" + }, + { + "name": "Cancel" + }, + { + "name": "GetWeather" + }, + { + "name": "None" + } + ], + "entities": [], + "composites": [ + { + "name": "From", + "children": [ + "Airport" + ], + "roles": [] + }, + { + "name": "To", + "children": [ + "Airport" + ], + "roles": [] + } + ], + "closedLists": [ + { + "name": "Airport", + "subLists": [ + { + "canonicalForm": "Paris", + "list": [ + "paris", + "cdg" + ] + }, + { + "canonicalForm": "London", + "list": [ + "london", + "lhr" + ] + }, + { + "canonicalForm": "Berlin", + "list": [ + "berlin", + "txl" + ] + }, + { + "canonicalForm": "New York", + "list": [ + "new york", + "jfk" + ] + }, + { + "canonicalForm": "Seattle", + "list": [ + "seattle", + "sea" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "datetimeV2", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [], + "utterances": [ + { + "text": "book a flight", + "intent": "BookFlight", + "entities": [] + }, + { + "text": "book a flight from new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 26 + } + ] + }, + { + "text": "book a flight from seattle", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 25 + } + ] + }, + { + "text": "book a hotel in new york", + "intent": "None", + "entities": [] + }, + { + "text": "book a restaurant", + "intent": "None", + "entities": [] + }, + { + "text": "book flight from london to paris on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 17, + "endPos": 22 + }, + { + "entity": "To", + "startPos": 27, + "endPos": 31 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 22, + "endPos": 27 + }, + { + "entity": "To", + "startPos": 32, + "endPos": 36 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "find an airport near me", + "intent": "None", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + }, + { + "entity": "From", + "startPos": 21, + "endPos": 26 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 9, + "endPos": 14 + }, + { + "entity": "To", + "startPos": 19, + "endPos": 23 + } + ] + }, + { + "text": "go to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 6, + "endPos": 10 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 11, + "endPos": 15 + }, + { + "entity": "To", + "startPos": 20, + "endPos": 25 + } + ] + }, + { + "text": "i'd like to rent a car", + "intent": "None", + "entities": [] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel from new york to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 12, + "endPos": 19 + }, + { + "entity": "To", + "startPos": 24, + "endPos": 28 + } + ] + }, + { + "text": "travel to new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 17 + } + ] + }, + { + "text": "travel to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "what's the forecast for this friday?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like for tomorrow", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like in new york", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "winter is coming", + "intent": "None", + "entities": [] + } + ], + "settings": [] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml new file mode 100644 index 000000000..ad9649da6 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml @@ -0,0 +1,248 @@ + + + + 4.0.0 + + <%= packageName %> + <%= artifact %> + 1.0.0 + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Core Bot sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + <%= packageName %>.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + <%= packageName %>.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties new file mode 100644 index 000000000..255d7cd56 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties @@ -0,0 +1,6 @@ +MicrosoftAppId= +MicrosoftAppPassword= +LuisAppId= +LuisAPIKey= +LuisAPIHostName= +server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json new file mode 100644 index 000000000..9b6389e39 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "Welcome to Bot Framework!", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": true, + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html new file mode 100644 index 000000000..997ee3708 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html @@ -0,0 +1,417 @@ + + + + + + + <%= botName %> + + + + + +
+
+
+
<%= botName %>
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java new file mode 100644 index 000000000..4f92dae33 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java @@ -0,0 +1,79 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + /** + * The start method. + * + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method with the + * @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + UserState userState, + ConversationState conversationState + ) { + FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration); + MainDialog dialog = new MainDialog(recognizer, new BookingDialog()); + return new DialogAndWelcomeBot<>(conversationState, userState, dialog); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} + diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java new file mode 100644 index 000000000..30b0dc9e9 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java @@ -0,0 +1,69 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +/** + * The model class to retrieve the information of the booking. + */ +public class BookingDetails { + + private String destination; + private String origin; + private String travelDate; + + /** + * Gets the destination of the booking. + * + * @return The destination. + */ + public String getDestination() { + return destination; + } + + + /** + * Sets the destination of the booking. + * + * @param withDestination The new destination. + */ + public void setDestination(String withDestination) { + this.destination = withDestination; + } + + /** + * Gets the origin of the booking. + * + * @return The origin. + */ + public String getOrigin() { + return origin; + } + + /** + * Sets the origin of the booking. + * + * @param withOrigin The new origin. + */ + public void setOrigin(String withOrigin) { + this.origin = withOrigin; + } + + /** + * Gets the travel date of the booking. + * + * @return The travel date. + */ + public String getTravelDate() { + return travelDate; + } + + /** + * Sets the travel date of the booking. + * + * @param withTravelDate The new travel date. + */ + public void setTravelDate(String withTravelDate) { + this.travelDate = withTravelDate; + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java new file mode 100644 index 000000000..7068cc7ac --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java @@ -0,0 +1,135 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the booking dialogs. + */ +public class BookingDialog extends CancelAndHelpDialog { + + private final String destinationStepMsgText = "Where would you like to travel to?"; + private final String originStepMsgText = "Where are you traveling from?"; + + /** + * The constructor of the Booking Dialog class. + */ + public BookingDialog() { + super("BookingDialog"); + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new ConfirmPrompt("ConfirmPrompt")); + addDialog(new DateResolverDialog(null)); + WaterfallStep[] waterfallSteps = { + this::destinationStep, + this::originStep, + this::travelDateStep, + this::confirmStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + + private CompletableFuture destinationStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + if (bookingDetails.getDestination().isEmpty()) { + Activity promptMessage = + MessageFactory.text(destinationStepMsgText, destinationStepMsgText, + InputHints.EXPECTING_INPUT + ); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getDestination()); + } + + + private CompletableFuture originStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setDestination(stepContext.getResult().toString()); + + if (bookingDetails.getOrigin().isEmpty()) { + Activity promptMessage = + MessageFactory + .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getOrigin()); + } + + + private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setOrigin(stepContext.getResult().toString()); + + if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { + return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); + } + + return stepContext.next(bookingDetails.getTravelDate()); + } + + + private CompletableFuture confirmStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setTravelDate(stepContext.getResult().toString()); + + String messageText = + String.format( + "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", + bookingDetails.getDestination(), bookingDetails.getOrigin(), + bookingDetails.getTravelDate() + ); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); + + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + + return stepContext.prompt("ConfirmPrompt", promptOptions); + } + + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + if ((Boolean) stepContext.getResult()) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + return stepContext.endDialog(bookingDetails); + } + + return stepContext.endDialog(null); + } + + private static boolean isAmbiguous(String timex) { + TimexProperty timexProperty = new TimexProperty(timex); + return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java new file mode 100644 index 000000000..3a35b494a --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of the dialog interruptions. + */ +public class CancelAndHelpDialog extends ComponentDialog { + + private final String helpMsgText = "Show help here"; + private final String cancelMsgText = "Cancelling..."; + + /** + * The constructor of the CancelAndHelpDialog class. + * + * @param id The dialog's Id. + */ + public CancelAndHelpDialog(String id) { + super(id); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. + * + * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. + * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is + * successful, the result indicates whether the dialog is still active after the turn has been + * processed by the dialog. The result may also contain a return value. + */ + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return interrupt(innerDc).thenCompose(result -> { + if (result != null) { + return CompletableFuture.completedFuture(result); + } + return super.onContinueDialog(innerDc); + }); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + switch (text) { + case "help": + case "?": + Activity helpMessage = MessageFactory + .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); + return innerDc.getContext().sendActivity(helpMessage) + .thenCompose(sendResult -> + CompletableFuture + .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); + case "cancel": + case "quit": + Activity cancelMessage = MessageFactory + .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); + return innerDc.getContext() + .sendActivity(cancelMessage) + .thenCompose(sendResult -> innerDc.cancelAllDialogs()); + default: + break; + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java new file mode 100644 index 000000000..0aa5b4ff9 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java @@ -0,0 +1,111 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.DateTimeResolution; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the date resolver dialogs. + */ +public class DateResolverDialog extends CancelAndHelpDialog { + private final String promptMsgText = "When would you like to travel?"; + private final String repromptMsgText = + "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; + + + /** + * The constructor of the DateResolverDialog class. + * @param id The dialog's id. + */ + public DateResolverDialog(@Nullable String id) { + super(id != null ? id : "DateResolverDialog"); + + + addDialog(new DateTimePrompt("DateTimePrompt", + DateResolverDialog::dateTimePromptValidator, null)); + WaterfallStep[] waterfallSteps = { + this::initialStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture initialStep(WaterfallStepContext stepContext) { + String timex = (String) stepContext.getOptions(); + + Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); + Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); + + if (timex == null) { + // We were not given any date at all so prompt the user. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + promptOptions.setRetryPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + // We have a Date we just need to check it is unambiguous. + TimexProperty timexProperty = new TimexProperty(timex); + if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { + // This is essentially a "reprompt" of the data we were given up front. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + DateTimeResolution dateTimeResolution = new DateTimeResolution() {{ + setTimex(timex); + }}; + List dateTimeResolutions = new ArrayList() {{ + add(dateTimeResolution); + }}; + return stepContext.next(dateTimeResolutions); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); + return stepContext.endDialog(timex); + } + + private static CompletableFuture dateTimePromptValidator( + PromptValidatorContext> promptContext + ) { + if (promptContext.getRecognized().getSucceeded()) { + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the + // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. + // e.g. missing a Year. + String timex = ((List) promptContext.getRecognized().getValue()) + .get(0).getTimex().split("T")[0]; + + // If this is a definite Date including year, month and day we are good otherwise reprompt. + // A better solution might be to let the user know what part is actually missing. + Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); + + return CompletableFuture.completedFuture(isDefinite); + } + + return CompletableFuture.completedFuture(false); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java new file mode 100644 index 000000000..5f4fa6d98 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; +import com.microsoft.applicationinsights.core.dependencies.apachecommons.lang3.StringUtils; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Serialization; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the welcome dialog. + * + * @param is a Dialog. + */ +public class DialogAndWelcomeBot extends DialogBot { + + /** + * Creates a DialogBot. + * + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogAndWelcomeBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { + super(withConversationState, withUserState, withDialog); + } + + /** + * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation + * update activity that indicates one or more users other than the bot are joining the + * conversation, it calls this method. + * + * @param membersAdded A list of all the members added to the conversation, as described by the + * conversation update activity + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. + Attachment welcomeCard = createAdaptiveCardAttachment(); + Activity response = MessageFactory + .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); + + return turnContext.sendActivity(response).thenApply(sendResult -> { + return Dialog.run(getDialog(), turnContext, + getConversationState().createProperty("DialogState") + ); + }); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + // Load attachment from embedded resource. + private Attachment createAdaptiveCardAttachment() { + try ( + InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") + ) { + String adaptiveCardJson = IOUtils + .toString(inputStream, StandardCharsets.UTF_8.toString()); + + return new Attachment() {{ + setContentType("application/vnd.microsoft.card.adaptive"); + setContent(Serialization.jsonToTree(adaptiveCardJson)); + }}; + + } catch (IOException e) { + e.printStackTrace(); + return new Attachment(); + } + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java new file mode 100644 index 000000000..395d168d3 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow + * multiple different bots to be run at different endpoints within the same project. This can be + * achieved by defining distinct Controller types each with dependency on distinct Bot types. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been + * used in a Dialog implementation, and the requirement is that all BotState objects are saved at + * the end of a turn. + * + * @param parameter of a type inheriting from Dialog + */ +public class DialogBot extends ActivityHandler { + + private Dialog dialog; + private BotState conversationState; + private BotState userState; + + /** + * Gets the dialog in use. + * + * @return instance of dialog + */ + protected Dialog getDialog() { + return dialog; + } + + /** + * Gets the conversation state. + * + * @return instance of conversationState + */ + protected BotState getConversationState() { + return conversationState; + } + + /** + * Gets the user state. + * + * @return instance of userState + */ + protected BotState getUserState() { + return userState; + } + + /** + * Sets the dialog in use. + * + * @param withDialog the dialog (of Dialog type) to be set + */ + protected void setDialog(Dialog withDialog) { + dialog = withDialog; + } + + /** + * Sets the conversation state. + * + * @param withConversationState the conversationState (of BotState type) to be set + */ + protected void setConversationState(BotState withConversationState) { + conversationState = withConversationState; + } + + /** + * Sets the user state. + * + * @param withUserState the userState (of BotState type) to be set + */ + protected void setUserState(BotState withUserState) { + userState = withUserState; + } + + /** + * Creates a DialogBot. + * + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { + this.conversationState = withConversationState; + this.userState = withUserState; + this.dialog = withDialog; + } + + /** + * Saves the BotState objects at the end of each turn. + * + * @param turnContext + * @return + */ + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return super.onTurn(turnContext) + .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) + .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); + } + + /** + * This method is executed when the turnContext receives a message activity. + * + * @param turnContext + * @return + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java new file mode 100644 index 000000000..68ef39304 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java @@ -0,0 +1,157 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.builder.Recognizer; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.integration.Configuration; +import org.apache.commons.lang3.StringUtils; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of recognizing the booking information. + */ +public class FlightBookingRecognizer implements Recognizer { + + private LuisRecognizer recognizer; + + /** + * The constructor of the FlightBookingRecognizer class. + * + * @param configuration The Configuration object to use. + */ + public FlightBookingRecognizer(Configuration configuration) { + Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); + if (luisIsConfigured) { + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + String.format("https://%s", configuration.getProperty("LuisAPIHostName")) + ); + // Set the recognizer options depending on which endpoint version you want to use. + // More details can be found in + // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3( + luisApplication) { + { + setIncludeInstanceData(true); + } + }; + + this.recognizer = new LuisRecognizer(recognizerOptions); + } + } + + /** + * Verify if the recognizer is configured. + * + * @return True if it's configured, False if it's not. + */ + public Boolean isConfigured() { + return this.recognizer != null; + } + + /** + * Return an object with preformatted LUIS results for the bot's dialogs to consume. + * + * @param context A {link TurnContext} + * @return A {link RecognizerResult} + */ + public CompletableFuture executeLuisQuery(TurnContext context) { + // Returns true if luis is configured in the application.properties and initialized. + return this.recognizer.recognize(context); + } + + /** + * Gets the From data from the entities which is part of the result. + * + * @param result The recognizer result. + * @return The object node representing the From data. + */ + public ObjectNode getFromEntities(RecognizerResult result) { + String fromValue = "", fromAirportValue = ""; + if (result.getEntities().get("$instance").get("From") != null) { + fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") + .asText(); + } + if (!fromValue.isEmpty() + && result.getEntities().get("From").get(0).get("Airport") != null) { + fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) + .asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("from", fromValue); + entitiesNode.put("airport", fromAirportValue); + return entitiesNode; + } + + /** + * Gets the To data from the entities which is part of the result. + * + * @param result The recognizer result. + * @return The object node representing the To data. + */ + public ObjectNode getToEntities(RecognizerResult result) { + String toValue = "", toAirportValue = ""; + if (result.getEntities().get("$instance").get("To") != null) { + toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); + } + if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { + toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) + .asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("to", toValue); + entitiesNode.put("airport", toAirportValue); + return entitiesNode; + } + + /** + * This value will be a TIMEX. And we are only interested in a Date so grab the first result and + * drop the Time part. TIMEX is a format that represents DateTime expressions that include some + * ambiguity. e.g. missing a Year. + * + * @param result A {link RecognizerResult} + * @return The Timex value without the Time model + */ + public String getTravelDate(RecognizerResult result) { + JsonNode datetimeEntity = result.getEntities().get("datetime"); + if (datetimeEntity == null || datetimeEntity.get(0) == null) { + return null; + } + + JsonNode timex = datetimeEntity.get(0).get("timex"); + if (timex == null || timex.get(0) == null) { + return null; + } + + String datetime = timex.get(0).asText().split("T")[0]; + return datetime; + } + + /** + * Runs an utterance through a recognizer and returns a generic recognizer result. + * + * @param turnContext Turn context. + * @return Analysis of utterance. + */ + @Override + public CompletableFuture recognize(TurnContext turnContext) { + return this.recognizer.recognize(turnContext); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java new file mode 100644 index 000000000..919985175 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java @@ -0,0 +1,232 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; + +/** + * The class containing the main dialog for the sample. + */ +public class MainDialog extends ComponentDialog { + + private final FlightBookingRecognizer luisRecognizer; + private final Integer plusDayValue = 7; + + /** + * The constructor of the Main Dialog class. + * + * @param withLuisRecognizer The FlightBookingRecognizer object. + * @param bookingDialog The BookingDialog object with booking dialogs. + */ + public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog bookingDialog) { + super("MainDialog"); + + luisRecognizer = withLuisRecognizer; + + addDialog(new TextPrompt("TextPrompt")); + addDialog(bookingDialog); + WaterfallStep[] waterfallSteps = { + this::introStep, + this::actStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + /** + * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a + * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the + * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture introStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + Activity text = MessageFactory.text("NOTE: LUIS is not configured. " + + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " + + "to the appsettings.json file.", null, InputHints.IGNORING_INPUT); + return stepContext.getContext().sendActivity(text) + .thenCompose(sendResult -> stepContext.next(null)); + } + + // Use the text provided in FinalStepAsync or the default if it is the first time. + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); + String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); + String messageText = stepContext.getOptions() != null + ? stepContext.getOptions().toString() + : String.format("What can I help you with today?\n" + + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + /** + * Second step in the waterfall. This will use LUIS to attempt to extract the origin, + * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect + * any remaining details. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture actStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. + return stepContext.beginDialog("BookingDialog", new BookingDetails()); + } + + // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) + return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { + switch (luisResult.getTopScoringIntent().intent) { + case "BookFlight": + // Extract the values for the composite entities from the LUIS result. + ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); + ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); + + // Show a warning for Origin and Destination if we can't resolve them. + return showWarningForUnsupportedCities( + stepContext.getContext(), fromEntities, toEntities) + .thenCompose(showResult -> { + // Initialize BookingDetails with any entities we may have found in the response. + + BookingDetails bookingDetails = new BookingDetails(); + bookingDetails.setDestination(toEntities.get("airport").asText()); + bookingDetails.setOrigin(fromEntities.get("airport").asText()); + bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); + // Run the BookingDialog giving it whatever details we have from the LUIS call, + // it will fill out the remainder. + return stepContext.beginDialog("BookingDialog", bookingDetails); + } + ); + case "GetWeather": + // We haven't implemented the GetWeatherDialog so we just display a TODO message. + String getWeatherMessageText = "TODO: get weather flow here"; + Activity getWeatherMessage = MessageFactory + .text( + getWeatherMessageText, getWeatherMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(getWeatherMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); + + default: + // Catch all for unhandled intents + String didntUnderstandMessageText = String.format( + "Sorry, I didn't get that. Please " + + " try asking in a different way (intent was %s)", + luisResult.getTopScoringIntent().intent + ); + Activity didntUnderstandMessage = MessageFactory + .text( + didntUnderstandMessageText, didntUnderstandMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(didntUnderstandMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); + } + }); + } + + /** + * Shows a warning if the requested From or To cities are recognized as entities but they are + * not in the Airport entity list. In some cases LUIS will recognize the From and To composite + * entities as a valid cities but the From and To Airport values will be empty if those entity + * values can't be mapped to a canonical item in the Airport. + * + * @param turnContext A {@link WaterfallStepContext} + * @param fromEntities An ObjectNode with the entities of From object + * @param toEntities An ObjectNode with the entities of To object + * @return A task + */ + private static CompletableFuture showWarningForUnsupportedCities( + TurnContext turnContext, + ObjectNode fromEntities, + ObjectNode toEntities + ) { + List unsupportedCities = new ArrayList(); + + if (StringUtils.isNotBlank(fromEntities.get("from").asText()) + && StringUtils.isBlank(fromEntities.get("airport").asText())) { + unsupportedCities.add(fromEntities.get("from").asText()); + } + + if (StringUtils.isNotBlank(toEntities.get("to").asText()) + && StringUtils.isBlank(toEntities.get("airport").asText())) { + unsupportedCities.add(toEntities.get("to").asText()); + } + + if (!unsupportedCities.isEmpty()) { + String messageText = String.format( + "Sorry but the following airports are not supported: %s", + String.join(", ", unsupportedCities) + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(message) + .thenApply(sendResult -> null); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" + * interaction with a simple confirmation. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + CompletableFuture stepResult = CompletableFuture.completedFuture(null); + + // If the child dialog ("BookingDialog") was cancelled, + // the user failed to confirm or if the intent wasn't BookFlight + // the Result here will be null. + if (stepContext.getResult() instanceof BookingDetails) { + // Now we have all the booking details call the booking service. + // If the call to the booking service was successful tell the user. + BookingDetails result = (BookingDetails) stepContext.getResult(); + TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); + String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); + String messageText = String.format("I have you booked to %s from %s on %s", + result.getDestination(), result.getOrigin(), travelDateMsg + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); + } + + // Restart the main dialog with a different message the second time around + String promptMessage = "What else can I do for you?"; + return stepResult + .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); + } +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java new file mode 100644 index 000000000..3b99d3596 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the core-bot sample. + */ +package <%= packageName %>; diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java new file mode 100644 index 000000000..675c5e987 --- /dev/null +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package <%= packageName %>; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md index 4ff2cc08e..0d514610a 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md +++ b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md @@ -13,7 +13,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p ## To try this sample locally - From the root of this project folder: - Build the sample using `mvn package` - - Run it by using `java -jar .\target\<%= botName %>-1.0.0.jar` + - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` - Test the bot using Bot Framework Emulator diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md index 4ff2cc08e..0d514610a 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md +++ b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md @@ -13,7 +13,7 @@ This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven p ## To try this sample locally - From the root of this project folder: - Build the sample using `mvn package` - - Run it by using `java -jar .\target\<%= botName %>-1.0.0.jar` + - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` - Test the bot using Bot Framework Emulator diff --git a/Generator/generator-botbuilder-java/package-lock.json b/Generator/generator-botbuilder-java/package-lock.json index c51903834..c8350daf6 100644 --- a/Generator/generator-botbuilder-java/package-lock.json +++ b/Generator/generator-botbuilder-java/package-lock.json @@ -1,3623 +1,3170 @@ { - "name": "generator-java", - "version": "4.9.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.10.4.tgz", - "integrity": "sha512-vG6SvB6oYEhvgisZNFRmRCUkLz11c7rp+tbNTynGqc6mS1d5ATd/sGyV6W0KZZnXRKMTzZDRgQT3Ou9jhpAfUg==", - "requires": { - "@babel/highlight": "^7.10.4" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.10.4.tgz", - "integrity": "sha512-3U9y+43hz7ZM+rzG24Qe2mufW5KhvFg/NhnNph+i9mgCtdTCtMJuI1TMkrIUiK7Ix4PYlRF9I5dhqaLYA/ADXw==" - }, - "@babel/highlight": { - "version": "7.10.4", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.10.4.tgz", - "integrity": "sha512-i6rgnR/YgPEQzZZnbTHHuZdlE8qyoBNalD6F+q4vAFlcMEcqmkoG+mPqJYJCo63qPf74+Y1UZsl3l6f7/RIkmA==", - "requires": { - "@babel/helper-validator-identifier": "^7.10.4", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { + "name": "generator-botbuilder-java", + "version": "4.9.1", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.12.11", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", + "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" + }, + "@babel/highlight": { + "version": "7.13.10", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", + "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", + "requires": { + "@babel/helper-validator-identifier": "^7.12.11", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@mrmlnc/readdir-enhanced": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", + "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", + "optional": true, + "requires": { + "call-me-maybe": "^1.0.1", + "glob-to-regexp": "^0.3.0" + } + }, + "@nodelib/fs.stat": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", + "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", + "optional": true + }, + "@types/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", + "optional": true, + "requires": { + "@types/minimatch": "*", + "@types/node": "*" + } + }, + "@types/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", + "optional": true + }, + "@types/node": { + "version": "14.14.36", + "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.36.tgz", + "integrity": "sha512-kjivUwDJfIjngzbhooRnOLhGYz6oRFi+L+EpMjxroDYXwDw9lHrJJ43E+dJ6KAd3V3WxWAJ/qZE9XKYHhjPOFQ==", + "optional": true + }, + "@types/normalize-package-data": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", + "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" + }, + "JSONStream": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", + "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", + "optional": true, + "requires": { + "jsonparse": "^1.2.0", + "through": ">=2.2.7 <3" + } + }, + "ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "optional": true, + "requires": { + "type-fest": "^0.21.3" + }, + "dependencies": { + "type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "optional": true + } + } + }, + "ansi-regex": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", + "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", + "optional": true + }, "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "arr-diff": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", + "optional": true + }, + "arr-flatten": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", + "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", + "optional": true + }, + "arr-union": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", + "optional": true + }, + "array-differ": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", + "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", + "optional": true + }, + "array-union": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", + "optional": true, + "requires": { + "array-uniq": "^1.0.1" + } + }, + "array-uniq": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", + "optional": true + }, + "array-unique": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", + "optional": true + }, + "arrify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", + "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", + "optional": true + }, + "assign-symbols": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", + "optional": true + }, + "async": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", + "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", + "requires": { + "lodash": "^4.17.14" + } + }, + "atob": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", + "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", + "optional": true + }, + "axios": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", + "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", + "optional": true, + "requires": { + "follow-redirects": "^1.10.0" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "base": { + "version": "0.11.2", + "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", + "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", + "optional": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "binaryextensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", + "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "optional": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "cache-base": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", + "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", + "optional": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + } + }, + "call-me-maybe": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", + "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", + "optional": true + }, + "capture-stack-trace": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", + "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" }, "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", + "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "chardet": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", + "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "optional": true + }, + "class-utils": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", + "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", + "optional": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "optional": true, + "requires": { + "restore-cursor": "^3.1.0" + } + }, + "cli-table": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", + "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", + "requires": { + "colors": "1.0.3" + } + }, + "cli-width": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", + "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "optional": true + }, + "clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", + "optional": true + }, + "clone-buffer": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", + "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", + "optional": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "optional": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "clone-stats": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", + "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", + "optional": true + }, + "cloneable-readable": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", + "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", + "optional": true, + "requires": { + "inherits": "^2.0.1", + "process-nextick-args": "^2.0.0", + "readable-stream": "^2.3.5" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, + "collection-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", + "optional": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } }, "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } }, "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "colors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", + "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "optional": true + }, + "component-emitter": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", + "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "copy-descriptor": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", + "optional": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "optional": true + }, + "create-error-class": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", + "requires": { + "capture-stack-trace": "^1.0.0" + } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "cross-spawn": { + "version": "6.0.5", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", + "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", + "requires": { + "nice-try": "^1.0.4", + "path-key": "^2.0.1", + "semver": "^5.5.0", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "dargs": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", + "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==" + }, + "dateformat": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", + "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "requires": { + "ms": "2.1.2" + } + }, + "decode-uri-component": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", + "optional": true + }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "optional": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "optional": true, - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - } - }, - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "optional": true - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "optional": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.3.tgz", - "integrity": "sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA==", - "optional": true - }, - "@types/node": { - "version": "14.14.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.11.tgz", - "integrity": "sha512-BJ97wAUuU3NUiUCp44xzUFquQEvnk1wu7q4CMEUYKJWjdkr0YWYDsm4RFtAvxYsNjLsKcrFt6RvK8r+mnzMbEQ==", - "optional": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "optional": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "ajv": { - "version": "6.12.6", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", - "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", - "optional": true, - "requires": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - } - }, - "ansi-escapes": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.1.tgz", - "integrity": "sha512-JWF7ocqNrp8u9oqpgV+wH5ftbt+cfvv+PTjOvKLT3AdYly/LmORARfEVT1iyjwN+4MqE5UmVKoAdIBqeoCHgLA==", - "optional": true, - "requires": { - "type-fest": "^0.11.0" - }, - "dependencies": { - "type-fest": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.11.0.tgz", - "integrity": "sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==", - "optional": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==" - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "optional": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "optional": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "optional": true - }, - "array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "optional": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "optional": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "optional": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "optional": true - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true - }, - "asn1": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", - "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==", - "optional": true, - "requires": { - "safer-buffer": "~2.1.0" - } - }, - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "optional": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "optional": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "optional": true - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "optional": true - }, - "aws-sign2": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "optional": true - }, - "aws4": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", - "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", - "optional": true - }, - "axios": { - "version": "0.18.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.18.1.tgz", - "integrity": "sha512-0BfJq4NSfQXd+SkFdrvFbG7addhYSBA2mQwISr46pD6E5iqkWg02RAs8vyTT/j0RTnoYmeXauBuSv1qKwR179g==", - "optional": true, - "requires": { - "follow-redirects": "1.5.10", - "is-buffer": "^2.0.2" - }, - "dependencies": { - "is-buffer": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", - "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", - "optional": true - } - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "optional": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", + "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", + "optional": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "dir-glob": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", + "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", + "optional": true, + "requires": { + "path-type": "^3.0.0" + } + }, + "download-stats": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", + "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", + "optional": true, + "requires": { + "JSONStream": "^1.2.1", + "lazy-cache": "^2.0.1", + "moment": "^2.15.1" + } + }, + "duplexer3": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" + }, + "editions": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", + "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", + "requires": { + "errlop": "^2.0.0", + "semver": "^6.3.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "ejs": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", + "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", + "optional": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "optional": true + }, + "end-of-stream": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", + "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", + "optional": true, + "requires": { + "once": "^1.4.0" + } + }, + "errlop": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", + "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==" + }, + "error": { + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", + "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", + "requires": { + "string-template": "~0.2.1" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" + }, + "execa": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", + "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", + "optional": true, + "requires": { + "cross-spawn": "^7.0.0", + "get-stream": "^5.0.0", + "human-signals": "^1.1.1", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.0", + "onetime": "^5.1.0", + "signal-exit": "^3.0.2", + "strip-final-newline": "^2.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "optional": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "get-stream": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", + "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", + "optional": true, + "requires": { + "pump": "^3.0.0" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "optional": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "optional": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "optional": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "optional": true + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "optional": true, + "requires": { + "isexe": "^2.0.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", + "optional": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true + } + } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "bcrypt-pbkdf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", - "optional": true, - "requires": { - "tweetnacl": "^0.14.3" - } - }, - "binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "optional": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "optional": true - }, - "camelcase": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.0.0.tgz", - "integrity": "sha512-8KMDF1Vz2gzOq54ONPJS65IvTUaB1cHJ2DMM7MbPmLZljDH1qpzzLsWdiN9pHh6qvkRVDTi/07+eNGch/oLU4w==" - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "optional": true - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "optional": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "optional": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.4.tgz", - "integrity": "sha512-1vinpnX/ZERcmE443i3SZTmU5DF0rPO9DrL4I2iVAllhxzCM9SzPlHnz19fsZB78htkKZvYBvj6SZ6vXnaxmTA==", - "requires": { - "chalk": "^2.4.1", - "string-width": "^4.2.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", + "optional": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "external-editor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", + "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", + "optional": true, + "requires": { + "chardet": "^0.7.0", + "iconv-lite": "^0.4.24", + "tmp": "^0.0.33" + } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } + "extglob": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", + "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", + "optional": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "fast-glob": { + "version": "2.2.7", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", + "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", + "optional": true, + "requires": { + "@mrmlnc/readdir-enhanced": "^2.2.1", + "@nodelib/fs.stat": "^1.1.2", + "glob-parent": "^3.1.0", + "is-glob": "^4.0.0", + "merge2": "^1.2.3", + "micromatch": "^3.1.10" + } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "figures": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", + "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", + "optional": true, + "requires": { + "escape-string-regexp": "^1.0.5" + } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "optional": true - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "optional": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "optional": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "optional": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "optional": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } + "fill-range": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "optional": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "optional": true, - "requires": { - "delayed-stream": "~1.0.0" - } - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "optional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "dargs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", - "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==" - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "optional": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "first-chunk-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", + "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", + "optional": true, + "requires": { + "readable-stream": "^2.0.2" + }, + "dependencies": { + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "optional": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "optional": true + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "optional": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "follow-redirects": { + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", + "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", + "optional": true + }, + "for-in": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", + "optional": true + }, + "fragment-cache": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", + "optional": true, + "requires": { + "map-cache": "^0.2.2" + } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "optional": true - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "optional": true, - "requires": { - "path-type": "^3.0.0" - } - }, - "download-stats": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", - "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", - "optional": true, - "requires": { - "JSONStream": "^1.2.1", - "lazy-cache": "^2.0.1", - "moment": "^2.15.1" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "ecc-jsbn": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", - "optional": true, - "requires": { - "jsbn": "~0.1.0", - "safer-buffer": "^2.1.0" - } - }, - "editions": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", - "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", - "requires": { - "errlop": "^2.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "optional": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "optional": true, - "requires": { - "once": "^1.4.0" - } - }, - "errlop": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", - "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==" - }, - "error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "requires": { - "string-template": "~0.2.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "optional": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "optional": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" }, "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "requires": { - "pump": "^3.0.0" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" + }, + "get-value": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", + "optional": true + }, + "gh-got": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", + "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", + "requires": { + "got": "^6.2.0", + "is-plain-obj": "^1.1.0" + } }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true + "github-username": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", + "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", + "requires": { + "gh-got": "^5.0.0" + } }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "optional": true, - "requires": { - "shebang-regex": "^3.0.0" - } + "glob-parent": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", + "optional": true, + "requires": { + "is-glob": "^3.1.0", + "path-dirname": "^1.0.0" + }, + "dependencies": { + "is-glob": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", + "optional": true, + "requires": { + "is-extglob": "^2.1.0" + } + } + } }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "optional": true + "glob-to-regexp": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", + "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", + "optional": true }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "optional": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } + "globby": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", + "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", + "optional": true, + "requires": { + "@types/glob": "^7.1.1", + "array-union": "^1.0.2", + "dir-glob": "^2.2.2", + "fast-glob": "^2.2.6", + "glob": "^7.1.3", + "ignore": "^4.0.3", + "pify": "^4.0.1", + "slash": "^2.0.0" + } }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "optional": true + }, + "grouped-queue": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", + "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", + "optional": true, + "requires": { + "lodash": "^4.17.15" + } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } - } - }, - "extend": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", - "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", - "optional": true - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "optional": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "optional": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "has-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", + "optional": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + } + }, + "has-values": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", + "optional": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", + "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + }, + "human-signals": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", + "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", + "optional": true + }, + "iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "optional": true, + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "optional": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "inquirer": { + "version": "7.3.3", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", + "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", + "optional": true, + "requires": { + "ansi-escapes": "^4.2.1", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-width": "^3.0.0", + "external-editor": "^3.0.3", + "figures": "^3.0.0", + "lodash": "^4.17.19", + "mute-stream": "0.0.8", + "run-async": "^2.4.0", + "rxjs": "^6.6.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0", + "through": "^2.3.6" + }, + "dependencies": { + "chalk": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", + "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", + "optional": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" + }, + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "optional": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "requires": { + "has": "^1.0.3" + } }, "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } }, "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "extsprintf": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "optional": true - }, - "fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "optional": true, - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - } - }, - "fast-json-stable-stringify": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", - "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "optional": true - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "optional": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "optional": true + } + } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "follow-redirects": { - "version": "1.5.10", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", - "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", - "optional": true, - "requires": { - "debug": "=3.1.0" - }, - "dependencies": { - "debug": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", - "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", - "optional": true, - "requires": { - "ms": "2.0.0" - } + "is-extendable": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", + "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", + "optional": true + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "optional": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "optional": true }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } - } - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "optional": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "optional": true - }, - "form-data": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", - "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", - "optional": true, - "requires": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.6", - "mime-types": "^2.1.12" - } - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "optional": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "optional": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0" - } - }, - "gh-got": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", - "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", - "requires": { - "got": "^6.2.0", - "is-plain-obj": "^1.1.0" - } - }, - "github-username": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", - "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", - "requires": { - "gh-got": "^5.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "optional": true - }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "optional": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.4", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", - "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", - "optional": true - }, - "grouped-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", - "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", - "optional": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "har-schema": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "optional": true - }, - "har-validator": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", - "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", - "optional": true, - "requires": { - "ajv": "^6.12.3", - "har-schema": "^2.0.0" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "optional": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" - }, - "http-signature": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0", - "jsprim": "^1.2.2", - "sshpk": "^1.7.0" - } - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "optional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "optional": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "optional": true, - "requires": { - "color-convert": "^2.0.1" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "optional": true, + "requires": { + "is-extglob": "^2.1.1" + } }, - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "optional": true, - "requires": { - "color-name": "~1.1.4" - } + "is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "optional": true, + "requires": { + "isobject": "^3.0.1" + } }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "optional": true + "is-redirect": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" + }, + "is-retry-allowed": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", + "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" + }, + "is-scoped": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", + "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", + "optional": true, + "requires": { + "scoped-regex": "^1.0.0" + } }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "optional": true + "is-stream": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" + }, + "is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", + "optional": true + }, + "is-windows": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", + "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", + "optional": true + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "optional": true + }, + "isbinaryfile": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", + "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", + "optional": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "optional": true + }, + "istextorbinary": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", + "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", + "requires": { + "binaryextensions": "^2.1.2", + "editions": "^2.2.0", + "textextensions": "^2.5.0" + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" + }, + "jsonparse": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", + "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", + "optional": true }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "optional": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "optional": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "optional": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "optional": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "optional": true, - "requires": { - "scoped-regex": "^1.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "optional": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "optional": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "optional": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "isbinaryfile": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", - "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", - "optional": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "optional": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "optional": true - }, - "istextorbinary": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", - "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", - "requires": { - "binaryextensions": "^2.1.2", - "editions": "^2.2.0", - "textextensions": "^2.5.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "optional": true - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "optional": true - }, - "json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "optional": true - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "optional": true - }, - "jsprim": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", - "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", - "optional": true, - "requires": { - "assert-plus": "1.0.0", - "extsprintf": "1.3.0", - "json-schema": "0.2.3", - "verror": "1.10.0" - } - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "optional": true - }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "optional": true, - "requires": { - "set-getter": "^0.1.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.20", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", - "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "optional": true, - "requires": { - "chalk": "^2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, - "requires": { - "color-convert": "^1.9.0" - } + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "optional": true + }, + "lazy-cache": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", + "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", + "optional": true, + "requires": { + "set-getter": "^0.1.0" + } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "lines-and-columns": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", + "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, - "requires": { - "color-name": "1.1.3" - } + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "optional": true, + "requires": { + "chalk": "^2.0.1" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "optional": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "optional": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "optional": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true + "lowercase-keys": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", + "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" + } + } + }, + "map-cache": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", + "optional": true + }, + "map-visit": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", + "optional": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "mem-fs": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", + "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", + "optional": true, + "requires": { + "through2": "^3.0.0", + "vinyl": "^2.0.1", + "vinyl-file": "^3.0.0" + } + }, + "mem-fs-editor": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", + "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", + "optional": true, + "requires": { + "commondir": "^1.0.1", + "deep-extend": "^0.6.0", + "ejs": "^2.6.1", + "glob": "^7.1.4", + "globby": "^9.2.0", + "isbinaryfile": "^4.0.0", + "mkdirp": "^0.5.0", + "multimatch": "^4.0.0", + "rimraf": "^2.6.3", + "through2": "^3.0.1", + "vinyl": "^2.2.0" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "optional": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "optional": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "optional": true + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "optional": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mixin-deep": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", + "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", + "optional": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", + "optional": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "optional": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "optional": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "mem-fs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", - "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", - "optional": true, - "requires": { - "through2": "^3.0.0", - "vinyl": "^2.0.1", - "vinyl-file": "^3.0.0" - } - }, - "mem-fs-editor": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", - "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", - "optional": true, - "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^2.6.1", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^0.5.0", - "multimatch": "^4.0.0", - "rimraf": "^2.6.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0" - }, - "dependencies": { "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "optional": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "optional": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mime-db": { - "version": "1.44.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz", - "integrity": "sha512-/NOTfLrsPBVeH7YtFPgsVWveuL+4SjjYxaQ1xtM1KMFj7HdxlBlxeyNLzhyJVx7r4rZGJAZ/6lkKCitSc/Nmpg==", - "optional": true - }, - "mime-types": { - "version": "2.1.27", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.27.tgz", - "integrity": "sha512-JIhqnCasI9yD+SsmkquHBxTSEuZdQX5BuQnS2Vc7puQQQ+8yiP5AY5uWhpdv4YL4VM5c6iliiYWPgJ/nJQLp7w==", - "optional": true, - "requires": { - "mime-db": "1.44.0" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "optional": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", - "optional": true, - "requires": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "optional": true - } - } - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "npm-api": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.0.tgz", - "integrity": "sha512-gtJhIhGq07g9H5sIAB9TZzTySW8MYtcYqg+e+J+5q1GmDsDLLVfyvVBL1VklzjtRsElph11GUtLBS191RDOJxQ==", - "optional": true, - "requires": { - "JSONStream": "^1.3.5", - "clone-deep": "^4.0.1", - "download-stats": "^0.3.4", - "moment": "^2.24.0", - "paged-request": "^2.0.1", - "request": "^2.88.0" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "optional": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" + }, + "moment": { + "version": "2.29.1", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", + "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", + "optional": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "multimatch": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", + "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", + "optional": true, + "requires": { + "@types/minimatch": "^3.0.3", + "array-differ": "^3.0.0", + "array-union": "^2.1.0", + "arrify": "^2.0.1", + "minimatch": "^3.0.4" + }, + "dependencies": { + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "optional": true + } + } + }, + "mute-stream": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", + "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "optional": true + }, + "nanomatch": { + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "optional": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + } + }, + "nice-try": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", + "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" + }, + "node-fetch": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", + "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", + "optional": true + }, + "normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "requires": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + }, + "dependencies": { + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + } + } + }, + "npm-api": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.1.tgz", + "integrity": "sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==", + "optional": true, + "requires": { + "JSONStream": "^1.3.5", + "clone-deep": "^4.0.1", + "download-stats": "^0.3.4", + "moment": "^2.24.0", + "node-fetch": "^2.6.0", + "paged-request": "^2.0.1" + } + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "optional": true, + "requires": { + "path-key": "^3.0.0" + }, + "dependencies": { + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "optional": true + } + } + }, + "object-copy": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", + "optional": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", + "optional": true, + "requires": { + "isobject": "^3.0.0" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "optional": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "optional": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "optional": true + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "paged-request": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.2.tgz", + "integrity": "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==", + "optional": true, + "requires": { + "axios": "^0.21.1" + } + }, + "parse-json": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", + "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", + "requires": { + "@babel/code-frame": "^7.0.0", + "error-ex": "^1.3.1", + "json-parse-even-better-errors": "^2.3.0", + "lines-and-columns": "^1.1.6" + } + }, + "pascalcase": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", + "optional": true + }, + "path-dirname": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", + "optional": true + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true - } - } - }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", - "optional": true - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "optional": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + }, + "path-type": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", + "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", + "optional": true, + "requires": { + "pify": "^3.0.0" + }, + "dependencies": { + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "optional": true + } + } }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "optional": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "optional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "optional": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "paged-request": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.1.tgz", - "integrity": "sha512-C0bB/PFk9rQskD1YEiz7uuchzqKDQGgdsEHN1ahify0UUWzgmMK4NDG9fhlQg2waogmNFwEvEeHfMRvJySpdVw==", - "optional": true, - "requires": { - "axios": "^0.18.0" - } - }, - "parse-json": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.1.0.tgz", - "integrity": "sha512-+mi/lmVVNKFNVyLXV31ERiy2CY5E1/F6QtJFEzoChPRwwngMNXRDQ9GJ5WdE2Z2P4AujsOi0/+2qHID68KwfIQ==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "optional": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "optional": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "optional": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true - } - } - }, - "performance-now": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", - "optional": true - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "optional": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "pretty-bytes": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.4.1.tgz", - "integrity": "sha512-s1Iam6Gwz3JI5Hweaz4GoCD1WUNUIyzePFy5+Js2hjwGVt2Z79wNN+ZKOZ2vB6C+Xs6njyB84Z1IthQg8d9LxA==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, - "psl": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", - "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", - "optional": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "punycode": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true - }, - "qs": { - "version": "6.5.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", - "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "optional": true - }, - "read-chunk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", - "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", - "requires": { - "pify": "^4.0.1", - "with-open-file": "^0.1.6" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - } - }, - "read-pkg-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", - "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^5.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "^1.1.6" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "optional": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "optional": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "optional": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "optional": true - }, - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "optional": true - }, - "request": { - "version": "2.88.2", - "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", - "integrity": "sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==", - "optional": true, - "requires": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~2.3.2", - "har-validator": "~5.1.3", - "http-signature": "~1.2.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "oauth-sign": "~0.9.0", - "performance-now": "^2.1.0", - "qs": "~6.5.2", - "safe-buffer": "^5.1.2", - "tough-cookie": "~2.5.0", - "tunnel-agent": "^0.6.0", - "uuid": "^3.3.2" - }, - "dependencies": { - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "optional": true - } - } - }, - "resolve": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", - "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", - "requires": { - "is-core-module": "^2.1.0", - "path-parse": "^1.0.6" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "optional": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "optional": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "optional": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "6.6.3", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.3.tgz", - "integrity": "sha512-trsQc+xYYXZ3urjOiJOuCOa5N3jAZ3eiSpQB5hIT8zGlL2QfnHLJ2r7GMkBGuIausdJN1OneaI6gQlsqNHHmZQ==", - "optional": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "optional": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true - }, - "scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "optional": true - }, - "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "optional": true, - "requires": { - "to-object-path": "^0.3.0" - } - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "optional": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "optional": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "optional": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "optional": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", + "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" + }, + "posix-character-classes": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", + "optional": true + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" + }, + "pretty-bytes": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", + "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "optional": true + }, + "pump": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", + "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", + "optional": true, + "requires": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } + "read-chunk": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", + "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", + "requires": { + "pify": "^4.0.1", + "with-open-file": "^0.1.6" + } }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } + "read-pkg": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", + "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", + "requires": { + "@types/normalize-package-data": "^2.4.0", + "normalize-package-data": "^2.5.0", + "parse-json": "^5.0.0", + "type-fest": "^0.6.0" + } }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "optional": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } + "read-pkg-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", + "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", + "requires": { + "find-up": "^3.0.0", + "read-pkg": "^5.0.0" + } }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "readable-stream": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", + "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "requires": { + "resolve": "^1.1.6" + } }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "optional": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "optional": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "optional": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=", - "optional": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "optional": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "sshpk": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", - "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", - "optional": true, - "requires": { - "asn1": "~0.2.3", - "assert-plus": "^1.0.0", - "bcrypt-pbkdf": "^1.0.0", - "dashdash": "^1.12.0", - "ecc-jsbn": "~0.1.1", - "getpass": "^0.1.1", - "jsbn": "~0.1.0", - "safer-buffer": "^2.0.2", - "tweetnacl": "~0.14.0" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "optional": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, - "string-width": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz", - "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==", - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "requires": { - "ansi-regex": "^5.0.0" - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "optional": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-bom-buf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", - "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", - "optional": true, - "requires": { - "is-utf8": "^0.2.1" - } - }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "optional": true, - "requires": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "optional": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "optional": true - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "optional": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "optional": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "tough-cookie": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", - "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", - "optional": true, - "requires": { - "psl": "^1.1.28", - "punycode": "^2.1.1" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "optional": true, - "requires": { - "safe-buffer": "^5.0.1" - } - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "optional": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "optional": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "optional": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "optional": true, - "requires": { - "isarray": "1.0.0" - } - } - } + "regex-not": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", + "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", + "optional": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "optional": true - } - } - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "optional": true - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, - "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "optional": true, - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "optional": true - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", - "optional": true, - "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" - } - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "optional": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-file": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", - "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", - "optional": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "strip-bom-buf": "^1.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "optional": true - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "with-open-file": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", - "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", - "requires": { - "p-finally": "^1.0.0", - "p-try": "^2.1.0", - "pify": "^4.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yeoman-environment": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", - "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", - "optional": true, - "requires": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "diff": "^3.5.0", - "escape-string-regexp": "^1.0.2", - "execa": "^4.0.0", - "globby": "^8.0.1", - "grouped-queue": "^1.1.0", - "inquirer": "^7.1.0", - "is-scoped": "^1.0.0", - "lodash": "^4.17.10", - "log-symbols": "^2.2.0", - "mem-fs": "^1.1.0", - "mem-fs-editor": "^6.0.0", - "npm-api": "^1.0.0", - "semver": "^7.1.3", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "untildify": "^3.0.3", - "yeoman-generator": "^4.8.2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "optional": true + }, + "repeat-element": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", + "optional": true + }, + "repeat-string": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", + "optional": true + }, + "replace-ext": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", + "optional": true + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } }, - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, - "requires": { - "color-convert": "^1.9.0" - } + "resolve-url": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", + "optional": true + }, + "restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "optional": true, + "requires": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + } }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "optional": true + "ret": { + "version": "0.1.15", + "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", + "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", + "optional": true + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "run-async": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", + "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" + }, + "rxjs": { + "version": "6.6.6", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz", + "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==", + "optional": true, + "requires": { + "tslib": "^1.9.0" + } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, - "requires": { - "color-name": "1.1.3" - } + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "safe-regex": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", + "optional": true, + "requires": { + "ret": "~0.1.10" + } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true + "safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "optional": true }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } + "scoped-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", + "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", + "optional": true }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "optional": true + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "requires": { + "lru-cache": "^6.0.0" + } }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "optional": true, - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } + "set-getter": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", + "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", + "optional": true, + "requires": { + "to-object-path": "^0.3.0" + } }, - "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "optional": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } + "set-value": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", + "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", + "optional": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "optional": true, + "requires": { + "kind-of": "^6.0.2" + } }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "optional": true + "shebang-command": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", + "requires": { + "shebang-regex": "^1.0.0" + } }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true + "shebang-regex": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" + }, + "shelljs": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", + "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "optional": true }, "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "optional": true + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", + "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", + "optional": true + }, + "snapdragon": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", + "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", + "optional": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "optional": true, + "requires": { + "ms": "2.0.0" + } + }, + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", + "optional": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "optional": true + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", + "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", + "optional": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", + "optional": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "optional": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "optional": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", + "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", + "optional": true, + "requires": { + "kind-of": "^3.2.0" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "optional": true + }, + "source-map-resolve": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", + "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", + "optional": true, + "requires": { + "atob": "^2.1.2", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", + "optional": true + }, + "spdx-correct": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" + }, + "spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", + "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" + }, + "split-string": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", + "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", + "optional": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "static-extend": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", + "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", + "optional": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", + "optional": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string-template": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", + "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" + }, + "string-width": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", + "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", + "optional": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", + "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", + "optional": true, + "requires": { + "ansi-regex": "^5.0.0" + } + } + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } }, "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - } + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "optional": true, + "requires": { + "ansi-regex": "^3.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "optional": true + } + } + }, + "strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", + "optional": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-bom-buf": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", + "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", + "optional": true, + "requires": { + "is-utf8": "^0.2.1" + } + }, + "strip-bom-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", + "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", + "optional": true, + "requires": { + "first-chunk-stream": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "optional": true }, "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "yeoman-generator": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.10.1.tgz", - "integrity": "sha512-QgbtHSaqBAkyJJM0heQUhT63ubCt34NBFMEBydOBUdAuy8RBvGSzeqVBSZOjdh1tSLrwWXlU3Ck6y14awinF6Q==", - "requires": { - "async": "^2.6.2", - "chalk": "^2.4.2", - "cli-table": "^0.3.1", - "cross-spawn": "^6.0.5", - "dargs": "^6.1.0", - "dateformat": "^3.0.3", - "debug": "^4.1.1", - "diff": "^4.0.1", - "error": "^7.0.2", - "find-up": "^3.0.0", - "github-username": "^3.0.0", - "grouped-queue": "^1.1.0", - "istextorbinary": "^2.5.1", - "lodash": "^4.17.11", - "make-dir": "^3.0.0", - "mem-fs-editor": "^6.0.0", - "minimist": "^1.2.5", - "pretty-bytes": "^5.2.0", - "read-chunk": "^3.2.0", - "read-pkg-up": "^5.0.0", - "rimraf": "^2.6.3", - "run-async": "^2.0.0", - "semver": "^7.2.1", - "shelljs": "^0.8.3", - "text-table": "^0.2.0", - "through2": "^3.0.1", - "yeoman-environment": "^2.9.5" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" + }, + "textextensions": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", + "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "optional": true + }, + "through2": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", + "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", + "requires": { + "inherits": "^2.0.4", + "readable-stream": "2 || 3" + } }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } + "timed-out": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", + "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" + }, + "tmp": { + "version": "0.0.33", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", + "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", + "optional": true, + "requires": { + "os-tmpdir": "~1.0.2" + } }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + "to-object-path": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", + "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", + "optional": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "optional": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + "to-regex": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", + "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", + "optional": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } + "to-regex-range": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", + "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", + "optional": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "optional": true + }, + "type-fest": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", + "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" + }, + "union-value": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", + "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", + "optional": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^2.0.1" + } + }, + "unset-value": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", + "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", + "optional": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", + "optional": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", + "optional": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", + "optional": true + } + } + }, + "untildify": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", + "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", + "optional": true + }, + "unzip-response": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", + "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", + "optional": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "requires": { + "prepend-http": "^1.0.1" + } + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vinyl": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", + "optional": true, + "requires": { + "clone": "^2.1.1", + "clone-buffer": "^1.0.0", + "clone-stats": "^1.0.0", + "cloneable-readable": "^1.0.0", + "remove-trailing-separator": "^1.0.1", + "replace-ext": "^1.0.0" + } + }, + "vinyl-file": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", + "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", + "optional": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.3.0", + "strip-bom-buf": "^1.0.0", + "strip-bom-stream": "^2.0.0", + "vinyl": "^2.0.1" + }, + "dependencies": { + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", + "optional": true + } + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "with-open-file": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", + "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", + "requires": { + "p-finally": "^1.0.0", + "p-try": "^2.1.0", + "pify": "^4.0.1" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, + "yeoman-environment": { + "version": "2.10.3", + "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", + "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", + "optional": true, + "requires": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "diff": "^3.5.0", + "escape-string-regexp": "^1.0.2", + "execa": "^4.0.0", + "globby": "^8.0.1", + "grouped-queue": "^1.1.0", + "inquirer": "^7.1.0", + "is-scoped": "^1.0.0", + "lodash": "^4.17.10", + "log-symbols": "^2.2.0", + "mem-fs": "^1.1.0", + "mem-fs-editor": "^6.0.0", + "npm-api": "^1.0.0", + "semver": "^7.1.3", + "strip-ansi": "^4.0.0", + "text-table": "^0.2.0", + "untildify": "^3.0.3", + "yeoman-generator": "^4.8.2" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "optional": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "arrify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", + "optional": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "optional": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "optional": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "optional": true + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "optional": true, + "requires": { + "ms": "^2.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "optional": true + }, + "dir-glob": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", + "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", + "optional": true, + "requires": { + "arrify": "^1.0.1", + "path-type": "^3.0.0" + } + }, + "globby": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", + "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", + "optional": true, + "requires": { + "array-union": "^1.0.1", + "dir-glob": "2.0.0", + "fast-glob": "^2.0.2", + "glob": "^7.1.2", + "ignore": "^3.3.5", + "pify": "^3.0.0", + "slash": "^1.0.0" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "optional": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "optional": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "optional": true + }, + "slash": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", + "optional": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "optional": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "yeoman-generator": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.10.1.tgz", + "integrity": "sha512-QgbtHSaqBAkyJJM0heQUhT63ubCt34NBFMEBydOBUdAuy8RBvGSzeqVBSZOjdh1tSLrwWXlU3Ck6y14awinF6Q==", + "requires": { + "async": "^2.6.2", + "chalk": "^2.4.2", + "cli-table": "^0.3.1", + "cross-spawn": "^6.0.5", + "dargs": "^6.1.0", + "dateformat": "^3.0.3", + "debug": "^4.1.1", + "diff": "^4.0.1", + "error": "^7.0.2", + "find-up": "^3.0.0", + "github-username": "^3.0.0", + "grouped-queue": "^1.1.0", + "istextorbinary": "^2.5.1", + "lodash": "^4.17.11", + "make-dir": "^3.0.0", + "mem-fs-editor": "^6.0.0", + "minimist": "^1.2.5", + "pretty-bytes": "^5.2.0", + "read-chunk": "^3.2.0", + "read-pkg-up": "^5.0.0", + "rimraf": "^2.6.3", + "run-async": "^2.0.0", + "semver": "^7.2.1", + "shelljs": "^0.8.3", + "text-table": "^0.2.0", + "through2": "^3.0.1", + "yeoman-environment": "^2.9.5" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } } - } } - } } diff --git a/Generator/generator-botbuilder-java/package.json b/Generator/generator-botbuilder-java/package.json index c4e374cf5..cab1b039c 100644 --- a/Generator/generator-botbuilder-java/package.json +++ b/Generator/generator-botbuilder-java/package.json @@ -1,5 +1,5 @@ { - "name": "generator-java", + "name": "generator-botbuilder-java", "version": "4.9.1", "description": "A yeoman generator for creating Java bots built with Bot Framework v4", "homepage": "https://github.com/Microsoft/BotBuilder-Samples/tree/master/generators/generator-botbuilder", @@ -27,7 +27,6 @@ "chalk": "~4.0.0", "lodash": "~4.17.15", "mkdirp": "^1.0.4", - "camelcase": "~6.0.0", "yeoman-generator": "~4.10.1" } } diff --git a/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java b/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java index d194bed47..6c266138a 100644 --- a/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java +++ b/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java @@ -1,2 +1,19 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. + +package com.microsoft.bot.sample.core; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From e01aef132207e1d5712c8a71613da08b58e053bc Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Mon, 29 Mar 2021 10:43:15 -0300 Subject: [PATCH 121/221] [Samples & SDK] Replace Double Brace initialization with Standard Initialization (#1103) * Fix double brace in botbuilder library * Fix double brace in botbuilder-dialogs library * Fix double brace in bot-schema library * Fix double brace in bot-ai-luis library * Fix double brace in bot-ai-qna library * Fix double brace in bot-azure library * Fix double brace in bot-connector library * Fix double brace in bot-integration library * Fix double brace in sample 3 * Fix double brace in sample 6 * Fix double brace in sample 7 * Fix double brace in sample 8 * Fix double brace in sample 11 * Fix double brace in sample 15 * Fix double brace in sample 17 * Fix double brace in sample 50 * Fix double brace in sample 51 * Fix double brace in sample 52 * Fix double brace in sample 53 * Fix double brace in sample 54 * Fix double brace in sample 55 * Fix double brace in sample 56 * Fix double brace in sample 57 * Fix double brace in sample 58 * Fix double brace in sample 81 * Fix double brace in sample 13 * Replicate change in generator for core bot template --- .../src/main/java/DateResolverDialog.java | 10 +- .../src/main/java/DialogAndWelcomeBot.java | 8 +- .../main/java/FlightBookingRecognizer.java | 8 +- .../bot/ai/luis/LuisRecognizerOptionsV3.java | 15 +- .../ai/luis/LuisRecognizerOptionsV3Tests.java | 56 +- .../bot/ai/luis/LuisRecognizerTests.java | 299 +++--- .../bot/ai/qna/QnAMakerRecognizer.java | 125 ++- .../bot/ai/qna/utils/GenerateAnswerUtils.java | 25 +- .../bot/ai/qna/utils/QnACardBuilder.java | 53 +- .../bot/ai/qna/QnAMakerRecognizerTests.java | 72 +- .../microsoft/bot/ai/qna/QnAMakerTests.java | 854 +++++++----------- .../bot/ai/qna/QnAMakerTraceInfoTests.java | 27 +- .../bot/azure/CosmosDbPartitionedStorage.java | 32 +- .../azure/CosmosDbPartitionStorageTests.java | 90 +- .../bot/builder/BotFrameworkAdapter.java | 104 +-- .../com/microsoft/bot/builder/BotState.java | 7 +- .../microsoft/bot/builder/MessageFactory.java | 23 +- .../bot/builder/ShowTypingMiddleware.java | 7 +- .../builder/TelemetryLoggerMiddleware.java | 81 +- .../builder/TranscriptLoggerMiddleware.java | 9 +- .../bot/builder/TurnContextImpl.java | 7 +- .../bot/builder/ActivityHandlerTests.java | 223 ++--- .../builder/AutoSaveStateMiddlewareTests.java | 157 ++-- .../bot/builder/BotAdapterTests.java | 51 +- .../bot/builder/BotFrameworkAdapterTests.java | 141 ++- .../bot/builder/InspectionTests.java | 18 +- .../bot/builder/MemoryConversations.java | 16 +- .../bot/builder/MessageFactoryTests.java | 120 +-- .../bot/builder/StorageBaseTests.java | 70 +- .../bot/builder/TelemetryMiddlewareTests.java | 136 +-- .../bot/builder/TestAdapterTests.java | 183 ++-- .../microsoft/bot/builder/TestMessage.java | 27 +- .../microsoft/bot/builder/TestUtilities.java | 24 +- .../bot/builder/TranscriptMiddlewareTest.java | 14 +- .../bot/builder/TurnContextTests.java | 15 +- .../bot/builder/adapters/TestAdapter.java | 129 ++- .../TeamsActivityHandlerBadRequestTests.java | 42 +- .../TeamsActivityHandlerHidingTests.java | 191 ++-- ...amsActivityHandlerNotImplementedTests.java | 313 +++---- .../teams/TeamsActivityHandlerTests.java | 798 ++++++---------- .../bot/builder/teams/TeamsInfoTests.java | 287 ++---- .../authentication/ChannelValidation.java | 36 +- .../authentication/EmulatorValidation.java | 73 +- .../EnterpriseChannelValidation.java | 33 +- .../GovernmentChannelValidation.java | 36 +- .../connector/authentication/RetryParams.java | 6 +- .../AllowedCallersClaimsValidationTests.java | 18 +- .../bot/connector/AttachmentsTest.java | 32 +- .../bot/connector/ConversationsTest.java | 461 +++++----- .../bot/connector/JwtTokenExtractorTests.java | 58 +- .../connector/JwtTokenValidationTests.java | 156 ++-- .../microsoft/bot/connector/RetryTests.java | 19 +- .../microsoft/bot/dialogs/DialogContext.java | 9 +- .../com/microsoft/bot/dialogs/Recognizer.java | 8 +- .../bot/dialogs/choices/ChoiceFactory.java | 21 +- .../dialogs/choices/ChoiceRecognizers.java | 41 +- .../microsoft/bot/dialogs/choices/Find.java | 34 +- .../bot/dialogs/prompts/ChoicePrompt.java | 15 +- .../bot/dialogs/prompts/ConfirmPrompt.java | 22 +- .../bot/dialogs/prompts/OAuthPrompt.java | 10 +- .../bot/dialogs/ObjectPathTests.java | 110 +-- .../dialogs/choices/ChoiceFactoryTests.java | 5 +- .../dialogs/choices/ChoicesChannelTests.java | 7 +- .../choices/ChoicesRecognizerTests.java | 15 +- .../dialogs/prompts/ActivityPromptTests.java | 3 +- .../dialogs/prompts/ChoicePromptTests.java | 199 ++-- .../dialogs/prompts/NumberPromptTests.java | 11 +- .../bot/dialogs/prompts/OAuthPromptTests.java | 91 +- .../integration/AdapterWithErrorHandler.java | 13 +- .../com/microsoft/bot/schema/Activity.java | 154 ++-- .../microsoft/bot/schema/AnimationCard.java | 10 +- .../com/microsoft/bot/schema/Attachment.java | 24 +- .../com/microsoft/bot/schema/AudioCard.java | 10 +- .../com/microsoft/bot/schema/CardAction.java | 23 +- .../microsoft/bot/schema/ChannelAccount.java | 23 +- .../bot/schema/ConversationAccount.java | 28 +- .../bot/schema/ConversationReference.java | 21 +- .../java/com/microsoft/bot/schema/Entity.java | 16 +- .../com/microsoft/bot/schema/HeroCard.java | 10 +- .../microsoft/bot/schema/MessageReaction.java | 9 +- .../com/microsoft/bot/schema/OAuthCard.java | 11 +- .../com/microsoft/bot/schema/ReceiptCard.java | 10 +- .../com/microsoft/bot/schema/SigninCard.java | 10 +- .../bot/schema/SuggestedActions.java | 21 +- .../microsoft/bot/schema/ThumbnailCard.java | 10 +- .../com/microsoft/bot/schema/VideoCard.java | 10 +- .../microsoft/bot/schema/ActivityTest.java | 172 ++-- .../bot/schema/AnimationCardTest.java | 15 +- .../microsoft/bot/schema/AudioCardTest.java | 14 +- .../schema/EntitySchemaValidationTest.java | 23 +- .../microsoft/bot/schema/HeroCardTest.java | 16 +- .../microsoft/bot/schema/OAuthCardTest.java | 14 +- .../microsoft/bot/schema/ReceiptCardTest.java | 12 +- .../microsoft/bot/schema/SigninCardTest.java | 12 +- .../bot/schema/ThumbnailCardTest.java | 20 +- .../microsoft/bot/schema/VideoCardTest.java | 18 +- .../teams/MessageActionsPayloadTest.java | 119 +-- .../sample/welcomeuser/WelcomeUserBot.java | 80 +- .../bot/sample/usingcards/Cards.java | 37 +- .../bots/AdaptiveCardsBot.java | 9 +- .../suggestedactions/SuggestedActionsBot.java | 53 +- .../microsoft/bot/sample/qnamaker/QnABot.java | 14 +- .../bot/sample/core/DateResolverDialog.java | 10 +- .../bot/sample/core/DialogAndWelcomeBot.java | 8 +- .../sample/core/FlightBookingRecognizer.java | 8 +- .../sample/attachments/AttachmentsBot.java | 33 +- .../sample/multilingual/MultiLingualBot.java | 39 +- .../TeamsMessagingExtensionsSearchBot.java | 86 +- .../TeamsMessagingExtensionsActionBot.java | 81 +- ...essagingExtensionsSearchAuthConfigBot.java | 226 ++--- ...msMessagingExtensionsActionPreviewBot.java | 74 +- .../teamstaskmodule/TeamsTaskModuleBot.java | 35 +- .../models/TaskModuleResponseFactory.java | 20 +- .../sample/teamsunfurl/LinkUnfurlingBot.java | 66 +- .../teamsfileupload/TeamsFileUploadBot.java | 42 +- .../TeamsConversationBot.java | 83 +- .../TeamsStartNewThreadBot.java | 9 +- .../sample/dialogrootbot/Bots/RootBot.java | 11 +- 118 files changed, 3357 insertions(+), 4911 deletions(-) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java index 0aa5b4ff9..7570fd646 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java @@ -75,12 +75,10 @@ private CompletableFuture initialStep(WaterfallStepContext ste return stepContext.prompt("DateTimePrompt", promptOptions); } - DateTimeResolution dateTimeResolution = new DateTimeResolution() {{ - setTimex(timex); - }}; - List dateTimeResolutions = new ArrayList() {{ - add(dateTimeResolution); - }}; + DateTimeResolution dateTimeResolution = new DateTimeResolution(); + dateTimeResolution.setTimex(timex); + List dateTimeResolutions = new ArrayList(); + dateTimeResolutions.add(dateTimeResolution); return stepContext.next(dateTimeResolutions); } diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java index 5f4fa6d98..807b6457e 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java @@ -85,10 +85,10 @@ private Attachment createAdaptiveCardAttachment() { String adaptiveCardJson = IOUtils .toString(inputStream, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + return attachment; } catch (IOException e) { e.printStackTrace(); diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java index 68ef39304..0e5df9b0b 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java @@ -42,12 +42,8 @@ public FlightBookingRecognizer(Configuration configuration) { // Set the recognizer options depending on which endpoint version you want to use. // More details can be found in // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3( - luisApplication) { - { - setIncludeInstanceData(true); - } - }; + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); + recognizerOptions.setIncludeInstanceData(true); this.recognizer = new LuisRecognizer(recognizerOptions); } diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java index bf520db6c..e22e5c251 100644 --- a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java @@ -485,11 +485,8 @@ private CompletableFuture recognizeInternal(TurnContext turnCo ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); if (utterance == null || utterance.isEmpty()) { - recognizerResult = new RecognizerResult() { - { - setText(utterance); - } - }; + recognizerResult = new RecognizerResult(); + recognizerResult.setText(utterance); } else { try { Request request = buildRequest(buildRequestBody(utterance)); @@ -541,11 +538,9 @@ private Map getIntents(JsonNode prediction) { Map.Entry intent = it.next(); double score = intent.getValue().get("score").asDouble(); String intentName = intent.getKey().replace(".", "_").replace(" ", "_"); - intents.put(intentName, new IntentScore() { - { - setScore(score); - } - }); + IntentScore intentScore = new IntentScore(); + intentScore.setScore(score); + intents.put(intentName, intentScore); } return intents; diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java index 6e4c2cf04..6538dcc09 100644 --- a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3Tests.java @@ -124,13 +124,9 @@ private void shouldParseLuisResponsesCorrectly_TurnContextPassed(String fileName LuisRecognizerOptionsV3 v3 = buildTestRecognizer(endpoint, testSettings); // Run test - Activity activity = new Activity() { - { - setText(testData.get("text").asText()); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(testData.get("text").asText()); + activity.setChannelId("EmptyContext"); doReturn(activity) .when(turnContext) .getActivity(); @@ -198,13 +194,9 @@ public void shouldBuildExternalEntities_DialogContextPassed_ExternalRecognizer() LuisRecognizerOptionsV3 v3 = buildTestRecognizer(endpoint, testSettings); v3.setExternalEntityRecognizer(recognizer); - Activity activity = new Activity() { - { - setText(testData.get("text").asText()); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(testData.get("text").asText()); + activity.setChannelId("EmptyContext"); doReturn(CompletableFuture.completedFuture(new ResourceResponse())) .when(turnContext) @@ -212,9 +204,11 @@ public void shouldBuildExternalEntities_DialogContextPassed_ExternalRecognizer() when(dC.getContext()).thenReturn(turnContext); - doReturn(CompletableFuture.supplyAsync(() -> new RecognizerResult(){{ - setEntities(testSettings.get("ExternalRecognizerResult")); - }})) + doReturn(CompletableFuture.supplyAsync(() -> { + RecognizerResult recognizerResult = new RecognizerResult(); + recognizerResult.setEntities(testSettings.get("ExternalRecognizerResult")); + return recognizerResult; + })) .when(recognizer) .recognize(any(DialogContext.class), any(Activity.class)); @@ -244,13 +238,9 @@ public void shouldBuildExternalEntities_DialogContextPassed_ExternalRecognizer() public static TurnContext createContext(String message) { - Activity activity = new Activity() { - { - setText(message); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(message); + activity.setChannelId("EmptyContext"); return new TurnContextImpl(new NotImplementedAdapter(), activity); } @@ -323,18 +313,18 @@ private LuisRecognizerOptionsV3 buildTestRecognizer (String endpoint, JsonNode t ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); ObjectReader readerDynamicList = mapper.readerFor(new TypeReference>() {}); ObjectReader readerExternalentities = mapper.readerFor(new TypeReference>() {}); - return new LuisRecognizerOptionsV3( + LuisRecognizerOptionsV3 recognizer = new LuisRecognizerOptionsV3( new LuisApplication( this.applicationId, this.subscriptionKey, - endpoint)) {{ - setIncludeInstanceData(testSettings.get("IncludeInstanceData").asBoolean()); - setIncludeAllIntents(testSettings.get("IncludeAllIntents").asBoolean()); - setVersion(testSettings.get("Version") == null ? null : testSettings.get("Version").asText()); - setDynamicLists(testSettings.get("DynamicLists") == null ? null : readerDynamicList.readValue(testSettings.get("DynamicLists"))); - setExternalEntities(testSettings.get("ExternalEntities") == null ? null : readerExternalentities.readValue(testSettings.get("ExternalEntities"))); - setDateTimeReference(testSettings.get("DateTimeReference") == null ? null : testSettings.get("DateTimeReference").asText()); - }}; + endpoint)); + recognizer.setIncludeInstanceData(testSettings.get("IncludeInstanceData").asBoolean()); + recognizer.setIncludeAllIntents(testSettings.get("IncludeAllIntents").asBoolean()); + recognizer.setVersion(testSettings.get("Version") == null ? null : testSettings.get("Version").asText()); + recognizer.setDynamicLists(testSettings.get("DynamicLists") == null ? null : readerDynamicList.readValue(testSettings.get("DynamicLists"))); + recognizer.setExternalEntities(testSettings.get("ExternalEntities") == null ? null : readerExternalentities.readValue(testSettings.get("ExternalEntities"))); + recognizer.setDateTimeReference(testSettings.get("DateTimeReference") == null ? null : testSettings.get("DateTimeReference").asText()); + return recognizer; } } diff --git a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java index 6d07c20fc..b5c10b735 100644 --- a/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java +++ b/libraries/bot-ai-luis-v3/src/test/java/com/microsoft/bot/ai/luis/LuisRecognizerTests.java @@ -46,44 +46,44 @@ public class LuisRecognizerTests { @Mock LuisApplication luisApplication; - private RecognizerResult mockedResult = new RecognizerResult(){{ - setIntents(new HashMap(){{ - put("Test", - new IntentScore(){{ - setScore(0.2); - }}); - put("Greeting", - new IntentScore(){{ - setScore(0.4); - }}); - }}); - setEntities(JsonNodeFactory.instance.objectNode()); - setProperties( + private RecognizerResult getMockedResult() { + RecognizerResult recognizerResult = new RecognizerResult(); + HashMap intents = new HashMap(); + IntentScore testScore = new IntentScore(); + testScore.setScore(0.2); + IntentScore greetingScore = new IntentScore(); + greetingScore.setScore(0.4); + intents.put("Test", testScore); + intents.put("Greeting", greetingScore); + recognizerResult.setIntents(intents); + recognizerResult.setEntities(JsonNodeFactory.instance.objectNode()); + recognizerResult.setProperties( "sentiment", JsonNodeFactory.instance.objectNode() .put( "label", "neutral")); - }}; + return recognizerResult; + }; @Test public void topIntentReturnsTopIntent() { String defaultIntent = LuisRecognizer - .topIntent(mockedResult); + .topIntent(getMockedResult()); assertEquals(defaultIntent, "Greeting"); } @Test public void topIntentReturnsDefaultIfMinScoreIsHigher() { String defaultIntent = LuisRecognizer - .topIntent(mockedResult, 0.5); + .topIntent(getMockedResult(), 0.5); assertEquals(defaultIntent, "None"); } @Test public void topIntentReturnsDefaultIfProvided() { String defaultIntent = LuisRecognizer - .topIntent(mockedResult, "Test2", 0.5); + .topIntent(getMockedResult(), "Test2", 0.5); assertEquals(defaultIntent, "Test2"); } @@ -101,7 +101,7 @@ public void topIntentThrowsIllegalArgumentIfResultIsNull() { @Test public void TopIntentReturnsTopIntentIfScoreEqualsMinScore() { - String defaultIntent = LuisRecognizer.topIntent(mockedResult, 0.4); + String defaultIntent = LuisRecognizer.topIntent(getMockedResult(), 0.4); assertEquals(defaultIntent, "Greeting"); } @@ -119,26 +119,23 @@ public void throwExceptionOnNullOptions() { public void recognizerResult() { setMockObjectsForTelemetry(); LuisRecognizer recognizer = new LuisRecognizer(options); - RecognizerResult expected = new RecognizerResult(){{ - setText("Random Message"); - setIntents(new HashMap(){{ - put("Test", - new IntentScore(){{ - setScore(0.2); - }}); - put("Greeting", - new IntentScore(){{ - setScore(0.4); - }}); - }}); - setEntities(JsonNodeFactory.instance.objectNode()); - setProperties( - "sentiment", - JsonNodeFactory.instance.objectNode() - .put( - "label", - "neutral")); - }}; + RecognizerResult expected = new RecognizerResult(); + expected.setText("Random Message"); + HashMap intents = new HashMap(); + IntentScore testScore = new IntentScore(); + testScore.setScore(0.2); + IntentScore greetingScore = new IntentScore(); + greetingScore.setScore(0.4); + intents.put("Test", testScore); + intents.put("Greeting", greetingScore); + expected.setIntents(intents); + expected.setEntities(JsonNodeFactory.instance.objectNode()); + expected.setProperties( + "sentiment", + JsonNodeFactory.instance.objectNode() + .put( + "label", + "neutral")); RecognizerResult actual = null; try { actual = recognizer.recognize(turnContext).get(); @@ -152,21 +149,21 @@ public void recognizerResult() { @Test public void recognizerResult_nullTelemetryClient() { + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Random Message"); + activity.setChannelId("EmptyContext"); + ChannelAccount channelAccount = new ChannelAccount(); + channelAccount.setId("Activity-from-ID"); + activity.setFrom(channelAccount); when(turnContext.getActivity()) - .thenReturn(new Activity() {{ - setText("Random Message"); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - setFrom(new ChannelAccount(){{ - setId("Activity-from-ID"); - }}); - }}); + .thenReturn(activity); when(luisApplication.getApplicationId()) .thenReturn("b31aeaf3-3511-495b-a07f-571fc873214b"); when(options.getApplication()) .thenReturn(luisApplication); + RecognizerResult mockedResult = getMockedResult(); mockedResult.setText("Random Message"); doReturn(CompletableFuture.supplyAsync(() -> mockedResult)) .when(options) @@ -174,26 +171,23 @@ public void recognizerResult_nullTelemetryClient() { any(TurnContext.class)); LuisRecognizer recognizer = new LuisRecognizer(options); - RecognizerResult expected = new RecognizerResult(){{ - setText("Random Message"); - setIntents(new HashMap(){{ - put("Test", - new IntentScore(){{ - setScore(0.2); - }}); - put("Greeting", - new IntentScore(){{ - setScore(0.4); - }}); - }}); - setEntities(JsonNodeFactory.instance.objectNode()); - setProperties( + RecognizerResult expected = new RecognizerResult(); + expected.setText("Random Message"); + HashMap intents = new HashMap(); + IntentScore testScore = new IntentScore(); + testScore.setScore(0.2); + IntentScore greetingScore = new IntentScore(); + greetingScore.setScore(0.4); + intents.put("Test", testScore); + intents.put("Greeting", greetingScore); + expected.setIntents(intents); + expected.setEntities(JsonNodeFactory.instance.objectNode()); + expected.setProperties( "sentiment", JsonNodeFactory.instance.objectNode() .put( "label", "neutral")); - }}; RecognizerResult actual = null; try { actual = recognizer.recognize(turnContext).get(); @@ -207,37 +201,32 @@ public void recognizerResult_nullTelemetryClient() { @Test public void recognizerResultDialogContext() { - RecognizerResult expected = new RecognizerResult(){{ - setText("Random Message"); - setIntents(new HashMap(){{ - put("Test", - new IntentScore(){{ - setScore(0.2); - }}); - put("Greeting", - new IntentScore(){{ - setScore(0.4); - }}); - }}); - setEntities(JsonNodeFactory.instance.objectNode()); - setProperties( + RecognizerResult expected = new RecognizerResult(); + expected.setText("Random Message"); + HashMap intents = new HashMap(); + IntentScore testScore = new IntentScore(); + testScore.setScore(0.2); + IntentScore greetingScore = new IntentScore(); + greetingScore.setScore(0.4); + intents.put("Test", testScore); + intents.put("Greeting", greetingScore); + expected.setIntents(intents); + expected.setEntities(JsonNodeFactory.instance.objectNode()); + expected.setProperties( "sentiment", JsonNodeFactory.instance.objectNode() .put( "label", "neutral")); - }}; RecognizerResult actual = null; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Random Message"); + activity.setChannelId("EmptyContext"); + ChannelAccount channelAccount = new ChannelAccount(); + channelAccount.setId("Activity-from-ID"); + activity.setFrom(channelAccount); when(turnContext.getActivity()) - .thenReturn(new Activity() {{ - setText("Random Message"); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - setFrom(new ChannelAccount(){{ - setId("Activity-from-ID"); - }}); - }}); - + .thenReturn(activity); when(luisApplication.getApplicationId()) .thenReturn("b31aeaf3-3511-495b-a07f-571fc873214b"); @@ -245,6 +234,7 @@ public void recognizerResultDialogContext() { when(options.getApplication()) .thenReturn(luisApplication); + RecognizerResult mockedResult = getMockedResult(); mockedResult.setText("Random Message"); when(dialogContext.getContext()) .thenReturn(turnContext); @@ -264,7 +254,6 @@ public void recognizerResultDialogContext() { } } - @Test public void recognizerResultConverted() { @@ -277,9 +266,8 @@ public void recognizerResultConverted() { e.printStackTrace(); } - TestRecognizerResultConvert expected = new TestRecognizerResultConvert(){{ - recognizerResultText = "Random Message"; - }}; + TestRecognizerResultConvert expected = new TestRecognizerResultConvert(); + expected.recognizerResultText = "Random Message"; assertEquals(expected.recognizerResultText, actual.recognizerResultText); } @@ -295,16 +283,15 @@ public void telemetryPropertiesAreFilledOnRecognizer() { } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } - Map expectedProperties = new HashMap (){{ - put("intentScore", "0.4"); - put("intent2", "Test"); - put("entities", "{}"); - put("intentScore2", "0.2"); - put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); - put("intent", "Greeting"); - put("fromId", "Activity-from-ID"); - put("sentimentLabel", "neutral"); - }}; + Map expectedProperties = new HashMap(); + expectedProperties.put("intentScore", "0.4"); + expectedProperties.put("intent2", "Test"); + expectedProperties.put("entities", "{}"); + expectedProperties.put("intentScore2", "0.2"); + expectedProperties.put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + expectedProperties.put("intent", "Greeting"); + expectedProperties.put("fromId", "Activity-from-ID"); + expectedProperties.put("sentimentLabel", "neutral"); verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, null); } @@ -322,17 +309,16 @@ public void telemetry_PiiLogged() { } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } - Map expectedProperties = new HashMap (){{ - put("intentScore", "0.4"); - put("intent2", "Test"); - put("entities", "{}"); - put("intentScore2", "0.2"); - put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); - put("intent", "Greeting"); - put("fromId", "Activity-from-ID"); - put("sentimentLabel", "neutral"); - put("question", "Random Message"); - }}; + Map expectedProperties = new HashMap(); + expectedProperties.put("intentScore", "0.4"); + expectedProperties.put("intent2", "Test"); + expectedProperties.put("entities", "{}"); + expectedProperties.put("intentScore2", "0.2"); + expectedProperties.put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + expectedProperties.put("intent", "Greeting"); + expectedProperties.put("fromId", "Activity-from-ID"); + expectedProperties.put("sentimentLabel", "neutral"); + expectedProperties.put("question", "Random Message"); verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, null); } @@ -343,32 +329,29 @@ public void telemetry_additionalProperties() { when(options.isLogPersonalInformation()).thenReturn(true); LuisRecognizer recognizer = new LuisRecognizer(options); - Map additionalProperties = new HashMap(){{ - put("test", "testvalue"); - put("foo", "foovalue"); - }}; - Map telemetryMetrics = new HashMap(){{ - put("test", 3.1416); - put("foo", 2.11); - }}; + Map additionalProperties = new HashMap(); + additionalProperties.put("test", "testvalue"); + additionalProperties.put("foo", "foovalue"); + Map telemetryMetrics = new HashMap(); + telemetryMetrics.put("test", 3.1416); + telemetryMetrics.put("foo", 2.11); try { recognizer.recognize(turnContext, additionalProperties, telemetryMetrics).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } - Map expectedProperties = new HashMap (){{ - put("intentScore", "0.4"); - put("intent2", "Test"); - put("entities", "{}"); - put("intentScore2", "0.2"); - put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); - put("intent", "Greeting"); - put("fromId", "Activity-from-ID"); - put("sentimentLabel", "neutral"); - put("question", "Random Message"); - put("test", "testvalue"); - put("foo", "foovalue"); - }}; + Map expectedProperties = new HashMap(); + expectedProperties.put("intentScore", "0.4"); + expectedProperties.put("intent2", "Test"); + expectedProperties.put("entities", "{}"); + expectedProperties.put("intentScore2", "0.2"); + expectedProperties.put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + expectedProperties.put("intent", "Greeting"); + expectedProperties.put("fromId", "Activity-from-ID"); + expectedProperties.put("sentimentLabel", "neutral"); + expectedProperties.put("question", "Random Message"); + expectedProperties.put("test", "testvalue"); + expectedProperties.put("foo", "foovalue"); verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, telemetryMetrics); } @@ -379,45 +362,42 @@ public void telemetry_additionalPropertiesOverrideProperty() { when(options.isLogPersonalInformation()).thenReturn(true); LuisRecognizer recognizer = new LuisRecognizer(options); - Map additionalProperties = new HashMap(){{ - put("intentScore", "1.15"); - put("foo", "foovalue"); - }}; - Map telemetryMetrics = new HashMap(){{ - put("test", 3.1416); - put("foo", 2.11); - }}; + Map additionalProperties = new HashMap(); + additionalProperties.put("intentScore", "1.15"); + additionalProperties.put("foo", "foovalue"); + Map telemetryMetrics = new HashMap(); + telemetryMetrics.put("test", 3.1416); + telemetryMetrics.put("foo", 2.11); try { recognizer.recognize(turnContext, additionalProperties, telemetryMetrics).get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } - Map expectedProperties = new HashMap (){{ - put("intentScore", "1.15"); - put("intent2", "Test"); - put("entities", "{}"); - put("intentScore2", "0.2"); - put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); - put("intent", "Greeting"); - put("fromId", "Activity-from-ID"); - put("sentimentLabel", "neutral"); - put("question", "Random Message"); - put("foo", "foovalue"); - }}; + Map expectedProperties = new HashMap(); + expectedProperties.put("intentScore", "1.15"); + expectedProperties.put("intent2", "Test"); + expectedProperties.put("entities", "{}"); + expectedProperties.put("intentScore2", "0.2"); + expectedProperties.put("applicationId", "b31aeaf3-3511-495b-a07f-571fc873214b"); + expectedProperties.put("intent", "Greeting"); + expectedProperties.put("fromId", "Activity-from-ID"); + expectedProperties.put("sentimentLabel", "neutral"); + expectedProperties.put("question", "Random Message"); + expectedProperties.put("foo", "foovalue"); verify(telemetryClient, atLeastOnce()).trackEvent("LuisResult", expectedProperties, telemetryMetrics); } private void setMockObjectsForTelemetry() { + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Random Message"); + activity.setType(ActivityTypes.MESSAGE); + activity.setChannelId("EmptyContext"); + ChannelAccount channelAccount = new ChannelAccount(); + channelAccount.setId("Activity-from-ID"); + activity.setFrom(channelAccount); when(turnContext.getActivity()) - .thenReturn(new Activity() {{ - setText("Random Message"); - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - setFrom(new ChannelAccount(){{ - setId("Activity-from-ID"); - }}); - }}); + .thenReturn(activity); when(luisApplication.getApplicationId()) .thenReturn("b31aeaf3-3511-495b-a07f-571fc873214b"); @@ -426,6 +406,7 @@ private void setMockObjectsForTelemetry() { when(options.getApplication()) .thenReturn(luisApplication); + RecognizerResult mockedResult = getMockedResult(); mockedResult.setText("Random Message"); doReturn(CompletableFuture.supplyAsync(() -> mockedResult)) .when(options) diff --git a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/QnAMakerRecognizer.java b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/QnAMakerRecognizer.java index 1867d8b11..1414445f6 100644 --- a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/QnAMakerRecognizer.java +++ b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/QnAMakerRecognizer.java @@ -380,17 +380,15 @@ public CompletableFuture recognize( filters.addAll(Arrays.asList(externalMetadata)); } - QnAMakerOptions options = new QnAMakerOptions() { - { - setContext(context); - setThreshold(threshold); - setStrictFilters(filters.toArray(new Metadata[filters.size()])); - setTop(top); - setQnAId(qnAId); - setIsTest(isTest); - setStrictFiltersJoinOperator(strictFiltersJoinOperator); - } - }; + QnAMakerOptions options = new QnAMakerOptions(); + options.setContext(context); + options.setScoreThreshold(threshold); + options.setStrictFilters(filters.toArray(new Metadata[filters.size()])); + options.setTop(top); + options.setQnAId(qnAId); + options.setIsTest(isTest); + options.setStrictFiltersJoinOperator(strictFiltersJoinOperator); + // Calling QnAMaker to get response. return this.getQnAMakerClient(dialogContext).thenCompose(qnaClient -> { return qnaClient.getAnswers(dialogContext.getContext(), options, null, null).thenApply(answers -> { @@ -403,19 +401,17 @@ public CompletableFuture recognize( } Float internalTopAnswer = topAnswer.getScore(); if (topAnswer.getAnswer().trim().toUpperCase().startsWith(intentPrefix.toUpperCase())) { + IntentScore intentScore = new IntentScore(); + intentScore.setScore(internalTopAnswer); recognizerResult.getIntents() .put(topAnswer.getAnswer().trim().substring( intentPrefix.length()).trim(), - new IntentScore() {{ - setScore(internalTopAnswer); - }} + intentScore ); } else { - recognizerResult.getIntents().put(this.qnAMatchIntent, new IntentScore() { - { - setScore(internalTopAnswer); - } - }); + IntentScore intentScore = new IntentScore(); + intentScore.setScore(internalTopAnswer); + recognizerResult.getIntents().put(this.qnAMatchIntent, intentScore); } ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); ObjectNode entitiesNode = mapper.createObjectNode(); @@ -434,11 +430,9 @@ public CompletableFuture recognize( recognizerResult.setEntities(entitiesNode); recognizerResult.getProperties().put("answers", mapper.valueToTree(answers)); } else { - recognizerResult.getIntents().put("None", new IntentScore() { - { - setScore(1.0f); - } - }); + IntentScore intentScore = new IntentScore(); + intentScore.setScore(1.0f); + recognizerResult.getIntents().put("None", intentScore); } this.trackRecognizerResult( @@ -469,13 +463,10 @@ protected CompletableFuture getQnAMakerClient(DialogContext dc) String hn = this.hostName; String kbId = this.knowledgeBaseId; Boolean logPersonalInfo = this.logPersonalInformation; - QnAMakerEndpoint endpoint = new QnAMakerEndpoint() { - { - setEndpointKey(epKey); - setHost(hn); - setKnowledgeBaseId(kbId); - } - }; + QnAMakerEndpoint endpoint = new QnAMakerEndpoint(); + endpoint.setEndpointKey(epKey); + endpoint.setHost(hn); + endpoint.setKnowledgeBaseId(kbId); return CompletableFuture .completedFuture(new QnAMaker(endpoint, new QnAMakerOptions(), this.getTelemetryClient(), logPersonalInfo)); @@ -505,44 +496,40 @@ protected Map fillRecognizerResultTelemetryProperties( ); } - Map properties = new HashMap() { - { - put( - "TopIntent", - !recognizerResult.getIntents().isEmpty() - ? (String) recognizerResult.getIntents().keySet().toArray()[0] - : null - ); - put( - "TopIntentScore", - !recognizerResult.getIntents() - .isEmpty() - ? Double.toString( - ((IntentScore) recognizerResult.getIntents().values().toArray()[0]).getScore() - ) - : null - ); - put( - "Intents", - !recognizerResult.getIntents().isEmpty() - ? Serialization.toStringSilent(recognizerResult.getIntents()) - : null - ); - put( - "Entities", - recognizerResult.getEntities() != null - ? Serialization.toStringSilent(recognizerResult.getEntities()) - : null - ); - put( - "AdditionalProperties", - !recognizerResult.getProperties().isEmpty() - ? Serialization.toStringSilent(recognizerResult.getProperties()) - : null - ); - } - }; - + Map properties = new HashMap(); + properties.put( + "TopIntent", + !recognizerResult.getIntents().isEmpty() + ? (String) recognizerResult.getIntents().keySet().toArray()[0] + : null + ); + properties.put( + "TopIntentScore", + !recognizerResult.getIntents() + .isEmpty() + ? Double.toString( + ((IntentScore) recognizerResult.getIntents().values().toArray()[0]).getScore() + ) + : null + ); + properties.put( + "Intents", + !recognizerResult.getIntents().isEmpty() + ? Serialization.toStringSilent(recognizerResult.getIntents()) + : null + ); + properties.put( + "Entities", + recognizerResult.getEntities() != null + ? Serialization.toStringSilent(recognizerResult.getEntities()) + : null + ); + properties.put( + "AdditionalProperties", + !recognizerResult.getProperties().isEmpty() + ? Serialization.toStringSilent(recognizerResult.getProperties()) + : null + ); if (logPersonalInformation && !Strings.isNullOrEmpty(recognizerResult.getText())) { properties.put("Text", recognizerResult.getText()); properties.put("AlteredText", recognizerResult.getAlteredText()); diff --git a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java index 52c09b3c0..298718004 100644 --- a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java +++ b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/GenerateAnswerUtils.java @@ -250,19 +250,18 @@ private CompletableFuture queryQnaService( JacksonAdapter jacksonAdapter = new JacksonAdapter(); String jsonRequest = null; - jsonRequest = jacksonAdapter.serialize(new JSONObject() { - { - put("question", messageActivity.getText()); - put("top", withOptions.getTop()); - put("strictFilters", withOptions.getStrictFilters()); - put("scoreThreshold", withOptions.getScoreThreshold()); - put("context", withOptions.getContext()); - put("qnaId", withOptions.getQnAId()); - put("isTest", withOptions.getIsTest()); - put("rankerType", withOptions.getRankerType()); - put("StrictFiltersCompoundOperationType", withOptions.getStrictFiltersJoinOperator()); - } - }); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("question", messageActivity.getText()); + jsonObject.put("top", withOptions.getTop()); + jsonObject.put("strictFilters", withOptions.getStrictFilters()); + jsonObject.put("scoreThreshold", withOptions.getScoreThreshold()); + jsonObject.put("context", withOptions.getContext()); + jsonObject.put("qnaId", withOptions.getQnAId()); + jsonObject.put("isTest", withOptions.getIsTest()); + jsonObject.put("rankerType", withOptions.getRankerType()); + jsonObject.put("StrictFiltersCompoundOperationType", withOptions.getStrictFiltersJoinOperator()); + + jsonRequest = jacksonAdapter.serialize(jsonObject); HttpRequestUtils httpRequestHelper = new HttpRequestUtils(); return httpRequestHelper.executeHttpRequest(requestUrl, jsonRequest, this.endpoint).thenCompose(response -> { diff --git a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/QnACardBuilder.java b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/QnACardBuilder.java index 5f339ca02..1cc68c09e 100644 --- a/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/QnACardBuilder.java +++ b/libraries/bot-ai-qna/src/main/java/com/microsoft/bot/ai/qna/utils/QnACardBuilder.java @@ -49,29 +49,23 @@ public static Activity getSuggestionsCard(List suggestionsList, String c // Add all suggestions for (String suggestion : suggestionsList) { - buttonList.add(new CardAction() { - { - setValue(suggestion); - setType(ActionTypes.IM_BACK); - setTitle(suggestion); - } - }); + CardAction cardAction = new CardAction(); + cardAction.setValue(suggestion); + cardAction.setType(ActionTypes.IM_BACK); + cardAction.setTitle(suggestion); + + buttonList.add(cardAction); } // Add No match text - buttonList.add(new CardAction() { - { - setValue(cardNoMatchText); - setType(ActionTypes.IM_BACK); - setTitle(cardNoMatchText); - } - }); - - HeroCard plCard = new HeroCard() { - { - setButtons(buttonList); - } - }; + CardAction cardAction = new CardAction(); + cardAction.setValue(cardNoMatchText); + cardAction.setType(ActionTypes.IM_BACK); + cardAction.setTitle(cardNoMatchText); + buttonList.add(cardAction); + + HeroCard plCard = new HeroCard(); + plCard.setButtons(buttonList); // Create the attachment. Attachment attachment = plCard.toAttachment(); @@ -103,20 +97,15 @@ public static Activity getQnAPromptsCard(QueryResult result, String cardNoMatchT // Add all prompt for (QnAMakerPrompt prompt : result.getContext().getPrompts()) { - buttonList.add(new CardAction() { - { - setValue(prompt.getDisplayText()); - setType(ActionTypes.IM_BACK); - setTitle(prompt.getDisplayText()); - } - }); + CardAction card = new CardAction(); + card.setValue(prompt.getDisplayText()); + card.setType(ActionTypes.IM_BACK); + card.setTitle(prompt.getDisplayText()); + buttonList.add(card); } - HeroCard plCard = new HeroCard() { - { - setButtons(buttonList); - } - }; + HeroCard plCard = new HeroCard(); + plCard.setButtons(buttonList); // Create the attachment. Attachment attachment = plCard.toAttachment(); diff --git a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerRecognizerTests.java b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerRecognizerTests.java index 4e03a4b1d..3adb6ef60 100644 --- a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerRecognizerTests.java +++ b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerRecognizerTests.java @@ -36,13 +36,11 @@ public class QnAMakerRecognizerTests { @Test public void logPiiIsFalseByDefault() { - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(hostname); - setEndpointKey(endpointKey); - setKnowledgeBaseId(knowledgeBaseId); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(hostname); + recognizer.setEndpointKey(endpointKey); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + Boolean logPersonalInfo = recognizer.getLogPersonalInformation(); // Should be false by default, when not specified by user. Assert.assertFalse(logPersonalInfo); @@ -53,13 +51,11 @@ public void noTextNoAnswer() { Activity activity = Activity.createMessageActivity(); TurnContext context = new TurnContextImpl(new TestAdapter(), activity); DialogContext dc = new DialogContext(new DialogSet(), context, new DialogState()); - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(hostname); - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(hostname); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + recognizer.setEndpointKey(endpointKey); + RecognizerResult result = recognizer.recognize(dc, activity).join(); Assert.assertEquals(result.getEntities(), null); Assert.assertEquals(result.getProperties().get("answers"), null); @@ -81,13 +77,11 @@ public void noAnswer() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer,response, url).port()); } String finalEndpoint = endpoint; - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(finalEndpoint); - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(finalEndpoint); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + recognizer.setEndpointKey(endpointKey); + Activity activity = Activity.createMessageActivity(); activity.setText("test"); TurnContext context = new TurnContextImpl(new TestAdapter(), activity); @@ -123,13 +117,11 @@ public void returnAnswers() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer,response, url).port()); } String finalEndpoint = endpoint; - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(finalEndpoint); - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(finalEndpoint); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + recognizer.setEndpointKey(endpointKey); + Activity activity = Activity.createMessageActivity(); activity.setText("test"); TurnContext context = new TurnContextImpl(new TestAdapter(), activity); @@ -163,13 +155,11 @@ public void topNAnswers() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer,response, url).port()); } String finalEndpoint = endpoint; - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(finalEndpoint); - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(finalEndpoint); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + recognizer.setEndpointKey(endpointKey); + Activity activity = Activity.createMessageActivity(); activity.setText("test"); TurnContext context = new TurnContextImpl(new TestAdapter(), activity); @@ -203,13 +193,11 @@ public void returnAnswersWithIntents() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer,response, url).port()); } String finalEndpoint = endpoint; - QnAMakerRecognizer recognizer = new QnAMakerRecognizer() { - { - setHostName(finalEndpoint); - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - } - }; + QnAMakerRecognizer recognizer = new QnAMakerRecognizer(); + recognizer.setHostName(finalEndpoint); + recognizer.setKnowledgeBaseId(knowledgeBaseId); + recognizer.setEndpointKey(endpointKey); + Activity activity = Activity.createMessageActivity(); activity.setText("test"); TurnContext context = new TurnContextImpl(new TestAdapter(), activity); diff --git a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java index 0dc66d4cb..46298f17f 100644 --- a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java +++ b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTests.java @@ -127,12 +127,8 @@ public void qnaMakerTraceActivity() { } delay(500); conversationId[0] = turnContext.getActivity().getConversation().getId(); - Activity typingActivity = new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }; + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo()); turnContext.sendActivity(typingActivity).join(); delay(500); turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); @@ -182,15 +178,12 @@ public void qnaMakerTraceActivityEmptyText() { // No text TestAdapter adapter = new TestAdapter( TestAdapter.createConversationReference("QnaMaker_TraceActivity_EmptyText", "User1", "Bot")); - Activity activity = new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText(new String()); - setConversation(new ConversationAccount()); - setRecipient(new ChannelAccount()); - setFrom(new ChannelAccount()); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(new String()); + activity.setConversation(new ConversationAccount()); + activity.setRecipient(new ChannelAccount()); + activity.setFrom(new ChannelAccount()); + TurnContext context = new TurnContextImpl(adapter, activity); Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join()); } catch (Exception e) { @@ -214,15 +207,12 @@ public void qnaMakerTraceActivityNullText() { // No text TestAdapter adapter = new TestAdapter( TestAdapter.createConversationReference("QnaMaker_TraceActivity_NullText", "User1", "Bot")); - Activity activity = new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText(null); - setConversation(new ConversationAccount()); - setRecipient(new ChannelAccount()); - setFrom(new ChannelAccount()); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(null); + activity.setConversation(new ConversationAccount()); + activity.setRecipient(new ChannelAccount()); + activity.setFrom(new ChannelAccount()); + TurnContext context = new TurnContextImpl(adapter, activity); Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join()); } catch (Exception e) { @@ -265,15 +255,11 @@ public void qnaMakerTraceActivityBadMessage() { // No text TestAdapter adapter = new TestAdapter( TestAdapter.createConversationReference("QnaMaker_TraceActivity_BadMessage", "User1", "Bot")); - Activity activity = new Activity() { - { - setType(ActivityTypes.TRACE); - setText("My Text"); - setConversation(new ConversationAccount()); - setRecipient(new ChannelAccount()); - setFrom(new ChannelAccount()); - } - }; + Activity activity = new Activity(ActivityTypes.TRACE); + activity.setText("My Text"); + activity.setConversation(new ConversationAccount()); + activity.setRecipient(new ChannelAccount()); + activity.setFrom(new ChannelAccount()); TurnContext context = new TurnContextImpl(adapter, activity); Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(context, null).join()); @@ -336,11 +322,9 @@ public void qnaMakerReturnsAnswerRaw() { MockWebServer mockWebServer = new MockWebServer(); try { QnAMaker qna = this.qnaReturnsAnswer(mockWebServer); - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); + QueryResults results = qna.getAnswersRaw(getContext("how do I clean the stove?"), options, null, null).join(); Assert.assertNotNull(results.getAnswers()); Assert.assertTrue(results.getActiveLearningEnabled()); @@ -371,18 +355,13 @@ public void qnaMakerLowScoreVariation() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setTop(5); - } - }; + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnaMakerEndpoint.setEndpointKey(endpointKey); + qnaMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setTop(5); QnAMaker qna = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions); QueryResult[] results = qna.getAnswers(getContext("Q11"), null).join(); Assert.assertNotNull(results); @@ -429,31 +408,23 @@ public void qnaMakerCallTrain() { response, url).port()); String finalEndpoint = endpoint; - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnaMakerEndpoint.setEndpointKey(endpointKey); + qnaMakerEndpoint.setHost(finalEndpoint); + QnAMaker qna = new QnAMaker(qnaMakerEndpoint, null); FeedbackRecords feedbackRecords = new FeedbackRecords(); - FeedbackRecord feedback1 = new FeedbackRecord() { - { - setQnaId(1); - setUserId("test"); - setUserQuestion("How are you?"); - } - }; + FeedbackRecord feedback1 = new FeedbackRecord(); + feedback1.setQnaId(1); + feedback1.setUserId("test"); + feedback1.setUserQuestion("How are you?"); - FeedbackRecord feedback2 = new FeedbackRecord() { - { - setQnaId(2); - setUserId("test"); - setUserQuestion("What up??"); - } - }; + FeedbackRecord feedback2 = new FeedbackRecord(); + feedback2.setQnaId(2); + feedback2.setUserId("test"); + feedback2.setUserQuestion("What up??"); feedbackRecords.setRecords(new FeedbackRecord[] { feedback1, feedback2 }); qna.callTrain(feedbackRecords); @@ -502,24 +473,18 @@ public void qnaMakerReturnsAnswerWithFiltering() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setStrictFilters(new Metadata[] { new Metadata() { - { - setName("topic"); - setValue("value"); - } - } }); - setTop(1); - } - }; + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnaMakerEndpoint.setEndpointKey(endpointKey); + qnaMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + Metadata metadata = new Metadata(); + metadata.setName("topic"); + metadata.setValue("value"); + Metadata[] filters = new Metadata[] { metadata }; + qnaMakerOptions.setStrictFilters(filters); + qnaMakerOptions.setTop(1); QnAMaker qna = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions); ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); @@ -566,25 +531,20 @@ public void qnaMakerSetScoreThresholdWhenThresholdIsZero() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setScoreThreshold(0.0f); - } - }; + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnaMakerEndpoint.setEndpointKey(endpointKey); + qnaMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setScoreThreshold(0.0f); + QnAMaker qnaWithZeroValueThreshold = new QnAMaker(qnaMakerEndpoint, qnaMakerOptions); - QueryResult[] results = qnaWithZeroValueThreshold.getAnswers(getContext("how do I clean the stove?"), new QnAMakerOptions() { - { - setTop(1); - } - }).join(); + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); + + QueryResult[] results = qnaWithZeroValueThreshold.getAnswers(getContext("how do I clean the stove?"), options).join(); Assert.assertNotNull(results); Assert.assertTrue(results.length == 1); } catch (Exception e) { @@ -611,19 +571,15 @@ public void qnaMakerTestThreshold() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setTop(1); - setScoreThreshold(0.99F); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setTop(1); + qnaMakerOptions.setScoreThreshold(0.99F); + QnAMaker qna = new QnAMaker(qnAMakerEndpoint, qnaMakerOptions); QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null).join(); @@ -642,37 +598,29 @@ public void qnaMakerTestThreshold() { @Test public void qnaMakerTestScoreThresholdTooLargeOutOfRange() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(hostname); - } - }; - QnAMakerOptions tooLargeThreshold = new QnAMakerOptions() { - { - setTop(1); - setScoreThreshold(1.1f); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(hostname); + + QnAMakerOptions tooLargeThreshold = new QnAMakerOptions(); + tooLargeThreshold.setTop(1); + tooLargeThreshold.setScoreThreshold(1.1f); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, tooLargeThreshold)); } @Test public void qnaMakerTestScoreThresholdTooSmallOutOfRange() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(hostname); - } - }; - QnAMakerOptions tooSmallThreshold = new QnAMakerOptions() { - { - setTop(1); - setScoreThreshold(-9000.0f); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(hostname); + + QnAMakerOptions tooSmallThreshold = new QnAMakerOptions(); + tooSmallThreshold.setTop(1); + tooSmallThreshold.setScoreThreshold(-9000.0f); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, tooSmallThreshold)); } @@ -689,25 +637,18 @@ public void qnaMakerReturnsAnswerWithContext() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnARequestContext context = new QnARequestContext() { - { - setPreviousQnAId(5); - setPreviousUserQuery("how do I clean the stove?"); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - setContext(context); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnARequestContext context = new QnARequestContext(); + context.setPreviousQnAId(5); + context.setPreviousUserQuery("how do I clean the stove?"); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); + options.setContext(context); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options); @@ -740,18 +681,13 @@ public void qnaMakerReturnAnswersWithoutContext() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(3); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(3); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options); @@ -783,19 +719,14 @@ public void qnaMakerReturnsHighScoreWhenIdPassed() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - setQnAId(55); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); + options.setQnAId(55); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options); QueryResult[] results = qna.getAnswers(getContext("Where can I buy?"), options).join(); @@ -816,55 +747,45 @@ public void qnaMakerReturnsHighScoreWhenIdPassed() { @Test public void qnaMakerTestTopOutOfRange() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(hostname); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(-1); - setScoreThreshold(0.5f); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(hostname); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(-1); + options.setScoreThreshold(0.5f); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, options)); } @Test public void qnaMakerTestEndpointEmptyKbId() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(new String()); - setEndpointKey(endpointKey); - setHost(hostname); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(new String()); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(hostname); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, null)); } @Test public void qnaMakerTestEndpointEmptyEndpointKey() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(new String()); - setHost(hostname); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(new String()); + qnAMakerEndpoint.setHost(hostname); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, null)); } @Test public void qnaMakerTestEndpointEmptyHost() { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(new String()); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(new String()); + Assert.assertThrows(IllegalArgumentException.class, () -> new QnAMaker(qnAMakerEndpoint, null)); } @@ -907,13 +828,10 @@ public void qnaMakerV2LegacyEndpointShouldThrow() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String host = String.format("{%s}/v2.0", endpoint); - QnAMakerEndpoint v2LegacyEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(host); - } - }; + QnAMakerEndpoint v2LegacyEndpoint = new QnAMakerEndpoint(); + v2LegacyEndpoint.setKnowledgeBaseId(knowledgeBaseId); + v2LegacyEndpoint.setEndpointKey(endpointKey); + v2LegacyEndpoint.setHost(host); Assert.assertThrows(UnsupportedOperationException.class, () -> new QnAMaker(v2LegacyEndpoint,null)); @@ -941,13 +859,10 @@ public void qnaMakerV3LeagacyEndpointShouldThrow() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String host = String.format("{%s}/v3.0", endpoint); - QnAMakerEndpoint v3LegacyEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(host); - } - }; + QnAMakerEndpoint v3LegacyEndpoint = new QnAMakerEndpoint(); + v3LegacyEndpoint.setKnowledgeBaseId(knowledgeBaseId); + v3LegacyEndpoint.setEndpointKey(endpointKey); + v3LegacyEndpoint.setHost(host); Assert.assertThrows(UnsupportedOperationException.class, () -> new QnAMaker(v3LegacyEndpoint,null)); @@ -975,18 +890,13 @@ public void qnaMakerReturnsAnswerWithMetadataBoost() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options); @@ -1018,19 +928,14 @@ public void qnaMakerTestThresholdInQueryOption() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions queryOptionsWithScoreThreshold = new QnAMakerOptions() { - { - setScoreThreshold(0.5f); - setTop(2); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions queryOptionsWithScoreThreshold = new QnAMakerOptions(); + queryOptionsWithScoreThreshold.setScoreThreshold(0.5f); + queryOptionsWithScoreThreshold.setTop(2); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, queryOptionsWithScoreThreshold); @@ -1063,13 +968,11 @@ public void qnaMakerTestUnsuccessfulResponse() { try { String url = this.getRequestUrl(); String finalEndpoint = String.format("%s:%s", hostname, mockWebServer.url(url).port()); - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + QnAMaker qna = new QnAMaker(qnAMakerEndpoint, null); Assert.assertThrows(CompletionException.class, () -> qna.getAnswers(getContext("how do I clean the stove?"), null).join()); } catch (Exception e) { @@ -1096,19 +999,14 @@ public void qnaMakerIsTestTrue() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setTop(1); - setIsTest(true); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setTop(1); + qnaMakerOptions.setIsTest(true); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, qnaMakerOptions); @@ -1139,19 +1037,14 @@ public void qnaMakerRankerTypeQuestionOnly() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setTop(1); - setRankerType("QuestionOnly"); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setTop(1); + qnaMakerOptions.setRankerType("QuestionOnly"); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, qnaMakerOptions); @@ -1183,58 +1076,41 @@ public void qnaMakerTestOptionsHydration() { } String finalEndpoint = endpoint; - QnAMakerOptions noFiltersOptions = new QnAMakerOptions() { - { - setTop(30); - } - }; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - Metadata strictFilterMovie = new Metadata() { - { - setName("movie"); - setValue("disney"); - } - }; - Metadata strictFilterHome = new Metadata() { - { - setName("home"); - setValue("floating"); - } - }; - Metadata strictFilterDog = new Metadata() { - { - setName("dog"); - setValue("samoyed"); - } - }; + QnAMakerOptions noFiltersOptions = new QnAMakerOptions(); + noFiltersOptions.setTop(30); + + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + Metadata strictFilterMovie = new Metadata(); + strictFilterMovie.setName("movie"); + strictFilterMovie.setValue("disney"); + + Metadata strictFilterHome = new Metadata(); + strictFilterHome.setName("home"); + strictFilterHome.setValue("floating"); + + Metadata strictFilterDog = new Metadata(); + strictFilterDog.setName("dog"); + strictFilterDog.setValue("samoyed"); + Metadata[] oneStrictFilters = new Metadata[] {strictFilterMovie}; Metadata[] twoStrictFilters = new Metadata[] {strictFilterMovie, strictFilterHome}; Metadata[] allChangedRequestOptionsFilters = new Metadata[] {strictFilterDog}; - QnAMakerOptions oneFilteredOption = new QnAMakerOptions() { - { - setTop(30); - setStrictFilters(oneStrictFilters); - } - }; - QnAMakerOptions twoStrictFiltersOptions = new QnAMakerOptions() { - { - setTop(30); - setStrictFilters(twoStrictFilters); - } - }; - QnAMakerOptions allChangedRequestOptions = new QnAMakerOptions() { - { - setTop(2000); - setScoreThreshold(0.4f); - setStrictFilters(allChangedRequestOptionsFilters); - } - }; + QnAMakerOptions oneFilteredOption = new QnAMakerOptions(); + oneFilteredOption.setTop(30); + oneFilteredOption.setStrictFilters(oneStrictFilters); + + QnAMakerOptions twoStrictFiltersOptions = new QnAMakerOptions(); + twoStrictFiltersOptions.setTop(30); + twoStrictFiltersOptions.setStrictFilters(twoStrictFilters); + + QnAMakerOptions allChangedRequestOptions = new QnAMakerOptions(); + allChangedRequestOptions.setTop(2000); + allChangedRequestOptions.setScoreThreshold(0.4f); + allChangedRequestOptions.setStrictFilters(allChangedRequestOptionsFilters); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, noFiltersOptions); @@ -1318,33 +1194,24 @@ public void qnaMakerStrictFiltersCompoundOperationType() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - Metadata strictFilterMovie = new Metadata() { - { - setName("movie"); - setValue("disney"); - } - }; - Metadata strictFilterProduction = new Metadata() { - { - setName("production"); - setValue("Walden"); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + Metadata strictFilterMovie = new Metadata(); + strictFilterMovie.setName("movie"); + strictFilterMovie.setValue("disney"); + + Metadata strictFilterProduction = new Metadata(); + strictFilterProduction.setName("production"); + strictFilterProduction.setValue("Walden"); + Metadata[] strictFilters = new Metadata[] {strictFilterMovie, strictFilterProduction}; - QnAMakerOptions oneFilteredOption = new QnAMakerOptions() { - { - setTop(30); - setStrictFilters(strictFilters); - setStrictFiltersJoinOperator(JoinOperator.OR); - } - }; + QnAMakerOptions oneFilteredOption = new QnAMakerOptions(); + oneFilteredOption.setTop(30); + oneFilteredOption.setStrictFilters(strictFilters); + oneFilteredOption.setStrictFiltersJoinOperator(JoinOperator.OR); QnAMaker qna = new QnAMaker(qnAMakerEndpoint, oneFilteredOption); @@ -1382,19 +1249,13 @@ public void telemetryNullTelemetryClient() { } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); // Act (Null Telemetry client) // This will default to the NullTelemetryClient which no-ops all calls. @@ -1429,18 +1290,13 @@ public void telemetryReturnsAnswer() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); @@ -1497,18 +1353,13 @@ public void telemetryReturnsAnswerWhenNoAnswerFoundInKB() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); @@ -1565,18 +1416,13 @@ public void telemetryPii() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); @@ -1635,25 +1481,19 @@ public void telemetryOverride() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); // Act - Override the QnaMaker object to log custom stuff and honor parms passed in. - Map telemetryProperties = new HashMap() {{ - put("Id", "MyID"); - }}; + Map telemetryProperties = new HashMap(); + telemetryProperties.put("Id", "MyID"); QnAMaker qna = new OverrideTelemetry(qnAMakerEndpoint, options, telemetryClient, false); QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null, telemetryProperties, null).join(); @@ -1709,33 +1549,23 @@ public void telemetryAdditionalPropsMetrics() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); // Act - Pass in properties during QnA invocation QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options, telemetryClient, false); - Map telemetryProperties = new HashMap() { - { - put("MyImportantProperty", "myImportantValue"); - } - }; - Map telemetryMetrics = new HashMap() { - { - put("MyImportantMetric", 3.14159); - } - }; + Map telemetryProperties = new HashMap(); + telemetryProperties.put("MyImportantProperty", "myImportantValue"); + + Map telemetryMetrics = new HashMap(); + telemetryMetrics.put("MyImportantMetric", 3.14159); QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null, telemetryProperties, telemetryMetrics).join(); // Assert - added properties were added. @@ -1796,35 +1626,25 @@ public void telemetryAdditionalPropsOverride() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); // Act - Pass in properties during QnA invocation that override default properties // NOTE: We are invoking this with PII turned OFF, and passing a PII property (originalQuestion). QnAMaker qna = new QnAMaker(qnAMakerEndpoint, options, telemetryClient, false); - Map telemetryProperties = new HashMap() { - { - put("knowledgeBaseId", "myImportantValue"); - put("originalQuestion", "myImportantValue2"); - } - }; - Map telemetryMetrics = new HashMap() { - { - put("score", 3.14159); - } - }; + Map telemetryProperties = new HashMap(); + telemetryProperties.put("knowledgeBaseId", "myImportantValue"); + telemetryProperties.put("originalQuestion", "myImportantValue2"); + + Map telemetryMetrics = new HashMap(); + telemetryMetrics.put("score", 3.14159); QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null, telemetryProperties, telemetryMetrics).join(); // Assert - added properties were added. @@ -1879,18 +1699,13 @@ public void telemetryFillPropsOverride() { endpoint = String.format("%s:%s", hostname, initializeMockServer(mockWebServer, response, url).port()); } String finalEndpoint = endpoint; - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions options = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnAMakerEndpoint.setEndpointKey(endpointKey); + qnAMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class); @@ -1903,17 +1718,12 @@ public void telemetryFillPropsOverride() { // Logically, the GetAnswersAync should win. But ultimately OnQnaResultsAsync decides since it is the last // code to touch the properties before logging (since it actually logs the event). QnAMaker qna = new OverrideFillTelemetry(qnAMakerEndpoint, options, telemetryClient, false); - Map telemetryProperties = new HashMap() { - { - put("knowledgeBaseId", "myImportantValue"); - put("matchedQuestion", "myImportantValue2"); - } - }; - Map telemetryMetrics = new HashMap() { - { - put("score", 3.14159); - } - }; + Map telemetryProperties = new HashMap(); + telemetryProperties.put("knowledgeBaseId", "myImportantValue"); + telemetryProperties.put("matchedQuestion", "myImportantValue2"); + + Map telemetryMetrics = new HashMap(); + telemetryMetrics.put("score", 3.14159); QueryResult[] results = qna.getAnswers(getContext("how do I clean the stove?"), null, telemetryProperties, telemetryMetrics).join(); // Assert - added properties were added. @@ -1957,15 +1767,11 @@ public void telemetryFillPropsOverride() { private static TurnContext getContext(String utterance) { TestAdapter b = new TestAdapter(); - Activity a = new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText(utterance); - setConversation(new ConversationAccount()); - setRecipient(new ChannelAccount()); - setFrom(new ChannelAccount()); - } - }; + Activity a = new Activity(ActivityTypes.MESSAGE); + a.setText(utterance); + a.setConversation(new ConversationAccount()); + a.setRecipient(new ChannelAccount()); + a.setFrom(new ChannelAccount()); return new TurnContextImpl(b, a); } @@ -2034,18 +1840,14 @@ private QnAMaker qnaReturnsAnswer(MockWebServer mockWebServer) { } String finalEndpoint = endpoint; // Mock Qna - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint() { - { - setKnowledgeBaseId(knowledgeBaseId); - setEndpointKey(endpointKey); - setHost(finalEndpoint); - } - }; - QnAMakerOptions qnaMakerOptions = new QnAMakerOptions() { - { - setTop(1); - } - }; + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(knowledgeBaseId); + qnaMakerEndpoint.setEndpointKey(endpointKey); + qnaMakerEndpoint.setHost(finalEndpoint); + + QnAMakerOptions qnaMakerOptions = new QnAMakerOptions(); + qnaMakerOptions.setTop(1); + return new QnAMaker(qnaMakerEndpoint, qnaMakerOptions); } catch (Exception e) { return null; @@ -2144,11 +1946,9 @@ protected CompletableFuture onQnaResults(QueryResult[] queryResults, TurnC telemetryClient.trackEvent(QnATelemetryConstants.QNA_MSG_EVENT, eventData.getLeft(), eventData.getRight()); // Create second event. - Map secondEventProperties = new HashMap(){ - { - put("MyImportantProperty2", "myImportantValue2"); - } - }; + Map secondEventProperties = new HashMap(); + secondEventProperties.put("MyImportantProperty2", "myImportantValue2"); + telemetryClient.trackEvent("MySecondEvent", secondEventProperties); }); } diff --git a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTraceInfoTests.java b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTraceInfoTests.java index 2e9200c60..fbc57019e 100644 --- a/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTraceInfoTests.java +++ b/libraries/bot-ai-qna/src/test/java/com/microsoft/bot/ai/qna/QnAMakerTraceInfoTests.java @@ -17,22 +17,17 @@ public class QnAMakerTraceInfoTests { @Test public void qnaMakerTraceInfoSerialization() throws IOException { - QueryResult[] queryResults = new QueryResult[] { new QueryResult() { - { - setQuestions(new String[] { "What's your name?" }); - setAnswer("My name is Mike"); - setScore(0.9f); - } - } }; - - QnAMakerTraceInfo qnaMakerTraceInfo = new QnAMakerTraceInfo() { - { - setQueryResults(queryResults); - setKnowledgeBaseId(UUID.randomUUID().toString()); - setScoreThreshold(0.5f); - setTop(1); - } - }; + QueryResult queryResult = new QueryResult(); + queryResult.setQuestions(new String[] { "What's your name?" }); + queryResult.setAnswer("My name is Mike"); + queryResult.setScore(0.9f); + QueryResult[] queryResults = new QueryResult[] { queryResult }; + + QnAMakerTraceInfo qnaMakerTraceInfo = new QnAMakerTraceInfo(); + qnaMakerTraceInfo.setQueryResults(queryResults); + qnaMakerTraceInfo.setKnowledgeBaseId(UUID.randomUUID().toString()); + qnaMakerTraceInfo.setScoreThreshold(0.5f); + qnaMakerTraceInfo.setTop(1); JacksonAdapter jacksonAdapter = new JacksonAdapter(); String serialized = jacksonAdapter.serialize(qnaMakerTraceInfo); diff --git a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java index 25a145113..ba4b96df7 100644 --- a/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java +++ b/libraries/bot-azure/src/main/java/com/microsoft/bot/azure/CosmosDbPartitionedStorage.java @@ -206,20 +206,17 @@ public CompletableFuture write(Map changes) { // metadata. node.remove("eTag"); - DocumentStoreItem documentChange = new DocumentStoreItem() { - { - setId( - CosmosDbKeyEscape.escapeKey( - change.getKey(), - cosmosDbStorageOptions.getKeySuffix(), - cosmosDbStorageOptions.getCompatibilityMode() - ) - ); - setReadId(change.getKey()); - setDocument(node.toString()); - setType(change.getValue().getClass().getTypeName()); - } - }; + DocumentStoreItem documentChange = new DocumentStoreItem(); + documentChange.setId( + CosmosDbKeyEscape.escapeKey( + change.getKey(), + cosmosDbStorageOptions.getKeySuffix(), + cosmosDbStorageOptions.getCompatibilityMode() + ) + ); + documentChange.setReadId(change.getKey()); + documentChange.setDocument(node.toString()); + documentChange.setType(change.getValue().getClass().getTypeName()); Document document = new Document(objectMapper.writeValueAsString(documentChange)); @@ -349,11 +346,8 @@ private CompletableFuture getCollection() { partitionKeyDefinition.setPaths(Collections.singleton(DocumentStoreItem.PARTITION_KEY_PATH)); collectionDefinition.setPartitionKey(partitionKeyDefinition); - RequestOptions options = new RequestOptions() { - { - setOfferThroughput(cosmosDbStorageOptions.getContainerThroughput()); - } - }; + RequestOptions options = new RequestOptions(); + options.setOfferThroughput(cosmosDbStorageOptions.getContainerThroughput()); collectionCache = client .createCollection(getDatabase().getSelfLink(), collectionDefinition, options) diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java index f33c6bfad..86e91e4f8 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/CosmosDbPartitionStorageTests.java @@ -91,12 +91,12 @@ public static void allTestCleanup() throws DocumentClientException { @Before public void testInit() { if (emulatorIsRunning) { - storage = new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey(CosmosAuthKey); - setContainerId(CosmosCollectionName); - setCosmosDbEndpoint(CosmosServiceEndpoint); - setDatabaseId(CosmosDatabaseName); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey(CosmosAuthKey); + options.setContainerId(CosmosCollectionName); + options.setCosmosDbEndpoint(CosmosServiceEndpoint); + options.setDatabaseId(CosmosDatabaseName); + storage = new CosmosDbPartitionedStorage(options); } } @@ -115,75 +115,75 @@ public void constructorShouldThrowOnInvalidOptions() { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey("test"); - setContainerId("testId"); - setDatabaseId("testDb"); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey("test"); + options.setContainerId("testId"); + options.setDatabaseId("testDb"); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for missing end point"); } catch (IllegalArgumentException e) { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey(null); - setContainerId("testId"); - setDatabaseId("testDb"); - setCosmosDbEndpoint("testEndpoint"); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey(null); + options.setContainerId("testId"); + options.setDatabaseId("testDb"); + options.setCosmosDbEndpoint("testEndpoint"); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for missing auth key"); } catch (IllegalArgumentException e) { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey("testAuthKey"); - setContainerId("testId"); - setDatabaseId(null); - setCosmosDbEndpoint("testEndpoint"); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey("testAuthKey"); + options.setContainerId("testId"); + options.setDatabaseId(null); + options.setCosmosDbEndpoint("testEndpoint"); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for missing db id"); } catch (IllegalArgumentException e) { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey("testAuthKey"); - setContainerId(null); - setDatabaseId("testDb"); - setCosmosDbEndpoint("testEndpoint"); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey("testAuthKey"); + options.setContainerId(null); + options.setDatabaseId("testDb"); + options.setCosmosDbEndpoint("testEndpoint"); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for missing collection id"); } catch (IllegalArgumentException e) { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey("testAuthKey"); - setContainerId("testId"); - setDatabaseId("testDb"); - setCosmosDbEndpoint("testEndpoint"); - setKeySuffix("?#*test"); - setCompatibilityMode(false); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey("testAuthKey"); + options.setContainerId("testId"); + options.setDatabaseId("testDb"); + options.setCosmosDbEndpoint("testEndpoint"); + options.setKeySuffix("?#*test"); + options.setCompatibilityMode(false); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for invalid Row Key characters in KeySuffix"); } catch (IllegalArgumentException e) { } try { - new CosmosDbPartitionedStorage(new CosmosDbPartitionedStorageOptions() {{ - setAuthKey("testAuthKey"); - setContainerId("testId"); - setDatabaseId("testDb"); - setCosmosDbEndpoint("testEndpoint"); - setKeySuffix("thisisatest"); - setCompatibilityMode(true); - }}); + CosmosDbPartitionedStorageOptions options = new CosmosDbPartitionedStorageOptions(); + options.setAuthKey("testAuthKey"); + options.setContainerId("testId"); + options.setDatabaseId("testDb"); + options.setCosmosDbEndpoint("testEndpoint"); + options.setKeySuffix("thisisatest"); + options.setCompatibilityMode(true); + new CosmosDbPartitionedStorage(options); Assert.fail("should have thrown for CompatibilityMode 'true' while using a KeySuffix"); } catch (IllegalArgumentException e) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index e3aa09a72..1d079fea5 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -1060,13 +1060,10 @@ public CompletableFuture createConversation( // run pipeline CompletableFuture result = new CompletableFuture<>(); try (TurnContextImpl context = new TurnContextImpl(this, eventActivity)) { - HashMap claims = new HashMap() { - { - put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - } - }; + HashMap claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, credentials.getAppId()); + claims.put(AuthenticationConstants.APPID_CLAIM, credentials.getAppId()); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity claimsIdentity = new ClaimsIdentity("anonymous", claims); context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); @@ -1531,24 +1528,19 @@ public CompletableFuture getOAuthSignInLink( try { Activity activity = context.getActivity(); String appId = getBotAppId(context); - - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setRelatesTo(activity.getRelatesTo()); - setMsAppId(appId); - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setActivityId(activity.getId()); + conversationReference.setBot(activity.getRecipient()); + conversationReference.setChannelId(activity.getChannelId()); + conversationReference.setConversation(activity.getConversation()); + conversationReference.setServiceUrl(activity.getServiceUrl()); + conversationReference.setUser(activity.getFrom()); + + TokenExchangeState tokenExchangeState = new TokenExchangeState(); + tokenExchangeState.setConnectionName(connectionName); + tokenExchangeState.setConversation(conversationReference); + tokenExchangeState.setRelatesTo(activity.getRelatesTo()); + tokenExchangeState.setMsAppId(appId); String serializedState = Serialization.toString(tokenExchangeState); String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); @@ -1598,24 +1590,19 @@ public CompletableFuture getOAuthSignInLink( Activity activity = context.getActivity(); String appId = getBotAppId(context); - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setLocale(activity.getLocale()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setRelatesTo(activity.getRelatesTo()); - setMsAppId(appId); - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setActivityId(activity.getId()); + conversationReference.setBot(activity.getRecipient()); + conversationReference.setChannelId(activity.getChannelId()); + conversationReference.setConversation(activity.getConversation()); + conversationReference.setLocale(activity.getLocale()); + conversationReference.setServiceUrl(activity.getServiceUrl()); + conversationReference.setUser(activity.getFrom()); + TokenExchangeState tokenExchangeState = new TokenExchangeState(); + tokenExchangeState.setConnectionName(connectionName); + tokenExchangeState.setConversation(conversationReference); + tokenExchangeState.setRelatesTo(activity.getRelatesTo()); + tokenExchangeState.setMsAppId(appId); String serializedState = Serialization.toString(tokenExchangeState); String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); @@ -1827,24 +1814,19 @@ public CompletableFuture getSignInResource( Activity activity = context.getActivity(); String appId = getBotAppId(context); - TokenExchangeState tokenExchangeState = new TokenExchangeState() { - { - setConnectionName(connectionName); - setConversation(new ConversationReference() { - { - setActivityId(activity.getId()); - setBot(activity.getRecipient()); - setChannelId(activity.getChannelId()); - setConversation(activity.getConversation()); - setLocale(activity.getLocale()); - setServiceUrl(activity.getServiceUrl()); - setUser(activity.getFrom()); - } - }); - setRelatesTo(activity.getRelatesTo()); - setMsAppId(appId); - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setActivityId(activity.getId()); + conversationReference.setBot(activity.getRecipient()); + conversationReference.setChannelId(activity.getChannelId()); + conversationReference.setConversation(activity.getConversation()); + conversationReference.setLocale(activity.getLocale()); + conversationReference.setServiceUrl(activity.getServiceUrl()); + conversationReference.setUser(activity.getFrom()); + TokenExchangeState tokenExchangeState = new TokenExchangeState(); + tokenExchangeState.setConnectionName(connectionName); + tokenExchangeState.setConversation(conversationReference); + tokenExchangeState.setRelatesTo(activity.getRelatesTo()); + tokenExchangeState.setMsAppId(appId); String serializedState = Serialization.toString(tokenExchangeState); String state = Base64.getEncoder().encodeToString(serializedState.getBytes(StandardCharsets.UTF_8)); diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java index e3f7fe8a7..a48e84195 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotState.java @@ -147,11 +147,8 @@ public CompletableFuture saveChanges(TurnContext turnContext, boolean forc CachedBotState cachedState = turnContext.getTurnState().get(contextServiceKey); if (force || cachedState != null && cachedState.isChanged()) { String storageKey = getStorageKey(turnContext); - Map changes = new HashMap() { - { - put(storageKey, cachedState.state); - } - }; + Map changes = new HashMap(); + changes.put(storageKey, cachedState.state); return storage.write(changes).thenApply(val -> { cachedState.setHash(cachedState.computeHash(cachedState.state)); diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MessageFactory.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MessageFactory.java index df782ede8..fef50ffc0 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MessageFactory.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/MessageFactory.java @@ -135,14 +135,10 @@ public static Activity suggestedActions( List cardActions = new ArrayList<>(); for (String action : actions) { - CardAction cardAction = new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue(action); - setTitle(action); - } - }; - + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.IM_BACK); + cardAction.setValue(action); + cardAction.setTitle(action); cardActions.add(cardAction); } @@ -357,13 +353,10 @@ public static Activity contentUrl( throw new IllegalArgumentException("contentType cannot be null or empty"); } - Attachment attachment = new Attachment() { - { - setContentType(contentType); - setContentUrl(url); - setName(StringUtils.isEmpty(name) ? null : name); - } - }; + Attachment attachment = new Attachment(); + attachment.setContentType(contentType); + attachment.setContentUrl(url); + attachment.setName(StringUtils.isEmpty(name) ? null : name); return attachmentActivity( AttachmentLayoutTypes.LIST, Collections.singletonList(attachment), text, ssml, inputHint diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java index 500bc12a2..e123282ab 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ShowTypingMiddleware.java @@ -119,11 +119,8 @@ private static CompletableFuture sendTypingActivity( ) { // create a TypingActivity, associate it with the conversation and send // immediately - Activity typingActivity = new Activity(ActivityTypes.TYPING) { - { - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }; + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo()); // sending the Activity directly on the Adapter avoids other Middleware and // avoids setting the Responded diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java index 27f8d1e42..1bf2966ba 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java @@ -102,12 +102,9 @@ public CompletableFuture onTurn(TurnContext context, NextDelegate next) { context.onDeleteActivity( (deleteContext, deleteReference, deleteNext) -> deleteNext.get() .thenCompose(nextResult -> { - Activity deleteActivity = new Activity(ActivityTypes.MESSAGE_DELETE) { - { - setId(deleteReference.getActivityId()); - applyConversationReference(deleteReference, false); - } - }; + Activity deleteActivity = new Activity(ActivityTypes.MESSAGE_DELETE); + deleteActivity.setId(deleteReference.getActivityId()); + deleteActivity.applyConversationReference(deleteReference, false); return onDeleteActivity(deleteActivity); }) @@ -196,18 +193,15 @@ protected CompletableFuture> fillReceiveEventProperties( Map additionalProperties ) { - Map properties = new HashMap() { - { - put(TelemetryConstants.FROMIDPROPERTY, activity.getFrom().getId()); - put( - TelemetryConstants.CONVERSATIONNAMEPROPERTY, - activity.getConversation().getName() - ); - put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); - put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); - put(TelemetryConstants.RECIPIENTNAMEPROPERTY, activity.getRecipient().getName()); - } - }; + Map properties = new HashMap(); + properties.put(TelemetryConstants.FROMIDPROPERTY, activity.getFrom().getId()); + properties.put( + TelemetryConstants.CONVERSATIONNAMEPROPERTY, + activity.getConversation().getName() + ); + properties.put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); + properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); + properties.put(TelemetryConstants.RECIPIENTNAMEPROPERTY, activity.getRecipient().getName()); // Use the LogPersonalInformation flag to toggle logging PII data, text and user // name are common examples @@ -250,17 +244,14 @@ protected CompletableFuture> fillSendEventProperties( Map additionalProperties ) { - Map properties = new HashMap() { - { - put(TelemetryConstants.REPLYACTIVITYIDPROPERTY, activity.getReplyToId()); - put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); - put( + Map properties = new HashMap(); + properties.put(TelemetryConstants.REPLYACTIVITYIDPROPERTY, activity.getReplyToId()); + properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); + properties.put( TelemetryConstants.CONVERSATIONNAMEPROPERTY, activity.getConversation().getName() - ); - put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); - } - }; + ); + properties.put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); // Use the LogPersonalInformation flag to toggle logging PII data, text and user // name are common examples @@ -303,17 +294,14 @@ protected CompletableFuture> fillUpdateEventProperties( Map additionalProperties ) { - Map properties = new HashMap() { - { - put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); - put(TelemetryConstants.CONVERSATIONIDPROPERTY, activity.getConversation().getId()); - put( - TelemetryConstants.CONVERSATIONNAMEPROPERTY, - activity.getConversation().getName() - ); - put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); - } - }; + Map properties = new HashMap(); + properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); + properties.put(TelemetryConstants.CONVERSATIONIDPROPERTY, activity.getConversation().getId()); + properties.put( + TelemetryConstants.CONVERSATIONNAMEPROPERTY, + activity.getConversation().getName() + ); + properties.put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); // Use the LogPersonalInformation flag to toggle logging PII data, text is a // common example @@ -344,16 +332,13 @@ protected CompletableFuture> fillDeleteEventProperties( Map additionalProperties ) { - Map properties = new HashMap() { - { - put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); - put(TelemetryConstants.CONVERSATIONIDPROPERTY, activity.getConversation().getId()); - put( - TelemetryConstants.CONVERSATIONNAMEPROPERTY, - activity.getConversation().getName() - ); - } - }; + Map properties = new HashMap(); + properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); + properties.put(TelemetryConstants.CONVERSATIONIDPROPERTY, activity.getConversation().getId()); + properties.put( + TelemetryConstants.CONVERSATIONNAMEPROPERTY, + activity.getConversation().getName() + ); // Additional Properties can override "stock" properties. if (additionalProperties != null) { diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java index bf77ddb2c..8a0d020c2 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TranscriptLoggerMiddleware.java @@ -113,12 +113,9 @@ public CompletableFuture onTurn(TurnContext context, NextDelegate next) { return nextDel.get().thenApply(nextDelResult -> { // add MessageDelete activity // log as MessageDelete activity - Activity deleteActivity = new Activity(ActivityTypes.MESSAGE_DELETE) { - { - setId(reference.getActivityId()); - applyConversationReference(reference, false); - } - }; + Activity deleteActivity = new Activity(ActivityTypes.MESSAGE_DELETE); + deleteActivity.setId(reference.getActivityId()); + deleteActivity.applyConversationReference(reference, false); logActivity(deleteActivity, false); diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java index 3f3b3024c..1a513a793 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TurnContextImpl.java @@ -312,11 +312,8 @@ public CompletableFuture sendActivity( return Async.completeExceptionally(new IllegalArgumentException("textReplyToSend")); } - Activity activityToSend = new Activity(ActivityTypes.MESSAGE) { - { - setText(textReplyToSend); - } - }; + Activity activityToSend = new Activity(ActivityTypes.MESSAGE); + activityToSend.setText(textReplyToSend); if (StringUtils.isNotEmpty(speak)) { activityToSend.setSpeak(speak); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index 66401f54b..e12802fe3 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -39,12 +39,8 @@ public void TestOnInstallationUpdate() { @Test public void TestInstallationUpdateAdd() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INSTALLATION_UPDATE); - setAction("add"); - } - }; + Activity activity = new Activity(ActivityTypes.INSTALLATION_UPDATE); + activity.setAction("add"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -58,12 +54,8 @@ public void TestInstallationUpdateAdd() { @Test public void TestInstallationUpdateAddUpgrade() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INSTALLATION_UPDATE); - setAction("add-upgrade"); - } - }; + Activity activity = new Activity(ActivityTypes.INSTALLATION_UPDATE); + activity.setAction("add-upgrade"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -77,12 +69,8 @@ public void TestInstallationUpdateAddUpgrade() { @Test public void TestInstallationUpdateRemove() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INSTALLATION_UPDATE); - setAction("remove"); - } - }; + Activity activity = new Activity(ActivityTypes.INSTALLATION_UPDATE); + activity.setAction("remove"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -96,12 +84,8 @@ public void TestInstallationUpdateRemove() { @Test public void TestInstallationUpdateRemoveUpgrade() { - Activity activity = new Activity() { - { - setType(ActivityTypes.INSTALLATION_UPDATE); - setAction("remove-upgrade"); - } - }; + Activity activity = new Activity(ActivityTypes.INSTALLATION_UPDATE); + activity.setAction("remove-upgrade"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -127,17 +111,11 @@ public void TestOnTypingActivity() { @Test public void TestMemberAdded1() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -150,18 +128,13 @@ public void TestMemberAdded1() { @Test public void TestMemberAdded2() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + activity.setType(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -175,19 +148,13 @@ public void TestMemberAdded2() { @Test public void TestMemberAdded3() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + members.add(new ChannelAccount("c")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -201,17 +168,11 @@ public void TestMemberAdded3() { @Test public void TestMemberRemoved1() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -224,18 +185,12 @@ public void TestMemberRemoved1() { @Test public void TestMemberRemoved2() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -249,19 +204,13 @@ public void TestMemberRemoved2() { @Test public void TestMemberRemoved3() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -275,17 +224,11 @@ public void TestMemberRemoved3() { @Test public void TestMemberAddedJustTheBot() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -298,17 +241,11 @@ public void TestMemberAddedJustTheBot() { @Test public void TestMemberRemovedJustTheBot() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -326,21 +263,13 @@ public void TestMessageReaction() { // sends separate activities each with a single add and a single remove. // Arrange - Activity activity = new Activity() { - { - setType(ActivityTypes.MESSAGE_REACTION); - setReactionsAdded(new ArrayList() { - { - add(new MessageReaction("sad")); - } - }); - setReactionsRemoved(new ArrayList() { - { - add(new MessageReaction("angry")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE_REACTION); + ArrayList reactionsAdded = new ArrayList(); + reactionsAdded.add(new MessageReaction("sad")); + activity.setReactionsAdded(reactionsAdded); + ArrayList reactionsRemoved = new ArrayList(); + reactionsRemoved.add(new MessageReaction("angry")); + activity.setReactionsRemoved(reactionsRemoved); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -355,12 +284,8 @@ public void TestMessageReaction() { @Test public void TestTokenResponseEventAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - setName("tokens/response"); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setName("tokens/response"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -374,12 +299,8 @@ public void TestTokenResponseEventAsync() { @Test public void TestEventAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - setName("some.random.event"); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setName("some.random.event"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -393,11 +314,7 @@ public void TestEventAsync() { @Test public void TestEventNullNameAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -447,11 +364,7 @@ public void TestCommandResultActivityType() { @Test public void TestUnrecognizedActivityType() { - Activity activity = new Activity() { - { - setType("shall.not.pass"); - } - }; + Activity activity = new Activity("shall.not.pass"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/AutoSaveStateMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/AutoSaveStateMiddlewareTests.java index ab6ce581e..72176400b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/AutoSaveStateMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/AutoSaveStateMiddlewareTests.java @@ -75,15 +75,13 @@ public void AutoSaveStateMiddleware_DualReadWrite() { "get convCount" ).assertReply(String.format("%d", CONVERSATION_INITIAL_COUNT + 3)).startTest().join(); - adapter = new TestAdapter(new ConversationReference() { - { - setChannelId("test"); - setServiceUrl("https://test.com"); - setUser(new ChannelAccount("user1", "User1")); - setBot(new ChannelAccount("bot", "Bot")); - setConversation(new ConversationAccount(false, "convo2", "Conversation2")); - } - }).use(new AutoSaveStateMiddleware(userState, convState)); + ConversationReference conversation = new ConversationReference(); + conversation.setChannelId("test"); + conversation.setServiceUrl("https://test.com"); + conversation.setUser(new ChannelAccount("user1", "User1")); + conversation.setBot(new ChannelAccount("bot", "Bot")); + conversation.setConversation(new ConversationAccount(false, "convo2", "Conversation2")); + adapter = new TestAdapter(conversation).use(new AutoSaveStateMiddleware(userState, convState)); new TestFlow(adapter, botLogic).send("get userCount").assertReply( String.format("%d", USER_INITIAL_COUNT + 4) @@ -104,81 +102,74 @@ public void AutoSaveStateMiddleware_Chain() { ConversationState convState = new ConversationState(storage); StatePropertyAccessor convProperty = convState.createProperty("convCount"); - AutoSaveStateMiddleware bss = new AutoSaveStateMiddleware() { - { - add(userState); - add(convState); - } - }; - - TestAdapter adapter = new TestAdapter().use(bss); - - final int USER_INITIAL_COUNT = 100; - final int CONVERSATION_INITIAL_COUNT = 10; - - BotCallbackHandler botLogic = (turnContext -> { - Integer userCount = userProperty.get(turnContext, () -> USER_INITIAL_COUNT).join(); - Integer convCount = convProperty.get( - turnContext, - () -> CONVERSATION_INITIAL_COUNT - ).join(); - - if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { - if (StringUtils.equals(turnContext.getActivity().getText(), "get userCount")) { - turnContext.sendActivity( - turnContext.getActivity().createReply(userCount.toString()) - ).join(); - } else if ( - StringUtils.equals(turnContext.getActivity().getText(), "get convCount") - ) { - turnContext.sendActivity( - turnContext.getActivity().createReply(convCount.toString()) - ).join(); + AutoSaveStateMiddleware bss = new AutoSaveStateMiddleware(); + { + bss.add(userState); + bss.add(convState); + + TestAdapter adapter = new TestAdapter().use(bss); + + final int USER_INITIAL_COUNT = 100; + final int CONVERSATION_INITIAL_COUNT = 10; + + BotCallbackHandler botLogic = (turnContext -> { + Integer userCount = userProperty.get(turnContext, () -> USER_INITIAL_COUNT).join(); + Integer convCount = convProperty.get( + turnContext, + () -> CONVERSATION_INITIAL_COUNT + ).join(); + + if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { + if (StringUtils.equals(turnContext.getActivity().getText(), "get userCount")) { + turnContext.sendActivity( + turnContext.getActivity().createReply(userCount.toString()) + ).join(); + } else if ( + StringUtils.equals(turnContext.getActivity().getText(), "get convCount") + ) { + turnContext.sendActivity( + turnContext.getActivity().createReply(convCount.toString()) + ).join(); + } } - } - - // increment userCount and set property using accessor. To be saved later by - // AutoSaveStateMiddleware - userCount++; - userProperty.set(turnContext, userCount).join(); - - // increment convCount and set property using accessor. To be saved later by - // AutoSaveStateMiddleware - convCount++; - convProperty.set(turnContext, convCount).join(); - return CompletableFuture.completedFuture(null); - }); - - new TestFlow(adapter, botLogic).send("test1").send("get userCount").assertReply( - String.format("%d", USER_INITIAL_COUNT + 1) - ).send("get userCount").assertReply(String.format("%d", USER_INITIAL_COUNT + 2)).send( - "get convCount" - ).assertReply(String.format("%d", CONVERSATION_INITIAL_COUNT + 3)).startTest().join(); - - // new adapter on new conversation - AutoSaveStateMiddleware bss2 = new AutoSaveStateMiddleware() { - { - add(userState); - add(convState); - } - }; - - adapter = new TestAdapter(new ConversationReference() { - { - setChannelId(Channels.TEST); - setServiceUrl("https://test.com"); - setUser(new ChannelAccount("user1", "User1")); - setBot(new ChannelAccount("bot", "Bot")); - setConversation(new ConversationAccount(false, "convo2", "Conversation2")); - } - }).use(bss2); - - new TestFlow(adapter, botLogic).send("get userCount").assertReply( - String.format("%d", USER_INITIAL_COUNT + 4) - ).send("get convCount").assertReply( - String.format("%d", CONVERSATION_INITIAL_COUNT + 1) - ).startTest().join(); + // increment userCount and set property using accessor. To be saved later by + // AutoSaveStateMiddleware + userCount++; + userProperty.set(turnContext, userCount).join(); + + // increment convCount and set property using accessor. To be saved later by + // AutoSaveStateMiddleware + convCount++; + convProperty.set(turnContext, convCount).join(); + + return CompletableFuture.completedFuture(null); + }); + + new TestFlow(adapter, botLogic).send("test1").send("get userCount").assertReply( + String.format("%d", USER_INITIAL_COUNT + 1) + ).send("get userCount").assertReply(String.format("%d", USER_INITIAL_COUNT + 2)).send( + "get convCount" + ).assertReply(String.format("%d", CONVERSATION_INITIAL_COUNT + 3)).startTest().join(); + + // new adapter on new conversation + AutoSaveStateMiddleware bss2 = new AutoSaveStateMiddleware(); + bss2.add(userState); + bss2.add(convState); + + ConversationReference conversation = new ConversationReference(); + conversation.setChannelId(Channels.TEST); + conversation.setServiceUrl("https://test.com"); + conversation.setUser(new ChannelAccount("user1", "User1")); + conversation.setBot(new ChannelAccount("bot", "Bot")); + conversation.setConversation(new ConversationAccount(false, "convo2", "Conversation2")); + adapter = new TestAdapter(conversation).use(bss2); + + new TestFlow(adapter, botLogic).send("get userCount").assertReply( + String.format("%d", USER_INITIAL_COUNT + 4) + ).send("get convCount").assertReply( + String.format("%d", CONVERSATION_INITIAL_COUNT + 1) + ).startTest().join(); + } } - } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java index faf7084d4..40ba1c971 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotAdapterTests.java @@ -73,36 +73,27 @@ public void ContinueConversation_DirectMsgAsync() { boolean[] callbackInvoked = new boolean[] { false }; TestAdapter adapter = new TestAdapter(); - ConversationReference cr = new ConversationReference() { - { - setActivityId("activityId"); - setBot(new ChannelAccount() { - { - setId("channelId"); - setName("testChannelAccount"); - setRole(RoleTypes.BOT); - } - }); - setChannelId("testChannel"); - setServiceUrl("testUrl"); - setConversation(new ConversationAccount() { - { - setConversationType(""); - setId("testConversationId"); - setIsGroup(false); - setName("testConversationName"); - setRole(RoleTypes.USER); - } - }); - setUser(new ChannelAccount() { - { - setId("channelId"); - setName("testChannelAccount"); - setRole(RoleTypes.BOT); - } - }); - } - }; + ConversationReference cr = new ConversationReference(); + cr.setActivityId("activityId"); + ChannelAccount botAccount = new ChannelAccount(); + botAccount.setId("channelId"); + botAccount.setName("testChannelAccount"); + botAccount.setRole(RoleTypes.BOT); + cr.setBot(botAccount); + cr.setChannelId("testChannel"); + cr.setServiceUrl("testUrl"); + ConversationAccount conversation = new ConversationAccount(); + conversation.setConversationType(""); + conversation.setId("testConversationId"); + conversation.setIsGroup(false); + conversation.setName("testConversationName"); + conversation.setRole(RoleTypes.USER); + cr.setConversation(conversation); + ChannelAccount userAccount = new ChannelAccount(); + userAccount.setId("channelId"); + userAccount.setName("testChannelAccount"); + userAccount.setRole(RoleTypes.BOT); + cr.setUser(userAccount); BotCallbackHandler callback = (turnContext) -> { callbackInvoked[0] = true; diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java index d8772559f..05c007b79 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/BotFrameworkAdapterTests.java @@ -85,13 +85,11 @@ protected CompletableFuture getOrCreateConnectorClient( AppCredentials usingAppCredentials ) { Conversations conv = mock(Conversations.class); + ConversationResourceResponse response = new ConversationResourceResponse(); + response.setActivityId(ActivityIdValue); + response.setId(ConversationIdValue); when(conv.createConversation(any())).thenReturn( - CompletableFuture.completedFuture(new ConversationResourceResponse() { - { - setActivityId(ActivityIdValue); - setId(ConversationIdValue); - } - }) + CompletableFuture.completedFuture(response) ); ConnectorClient client = mock(ConnectorClient.class); @@ -111,28 +109,18 @@ protected CompletableFuture getOrCreateConnectorClient( .set("id", JsonNodeFactory.instance.textNode(TenantIdValue)) ); - Activity activity = new Activity("Test") { - { - setChannelId(Channels.MSTEAMS); - setServiceUrl("https://fake.service.url"); - setChannelData(channelData); - setConversation(new ConversationAccount() { - { - setTenantId(TenantIdValue); - } - }); - } - }; + Activity activity = new Activity("Test"); + activity.setChannelId(Channels.MSTEAMS); + activity.setServiceUrl("https://fake.service.url"); + activity.setChannelData(channelData); + ConversationAccount conversation = new ConversationAccount(); + conversation.setTenantId(TenantIdValue); + activity.setConversation(conversation); - ConversationParameters parameters = new ConversationParameters() { - { - setActivity(new Activity() { - { - setChannelData(activity.getChannelData()); - } - }); - } - }; + Activity activityConversation = new Activity(ActivityTypes.MESSAGE); + activityConversation.setChannelData(activity.getChannelData()); + ConversationParameters parameters = new ConversationParameters(); + parameters.setActivity(activityConversation); ConversationReference reference = activity.getConversationReference(); MicrosoftAppCredentials credentials = new MicrosoftAppCredentials(null, null); @@ -178,18 +166,14 @@ private Activity processActivity( channelData.set("tenant", tenantId); Activity[] activity = new Activity[] {null}; - sut.processActivity(mockClaims, new Activity("test") { - { - setChannelId(channelId); - setServiceUrl("https://smba.trafficmanager.net/amer/"); - setChannelData(channelData); - setConversation(new ConversationAccount() { - { - setTenantId(conversationTenantId); - } - }); - } - }, (context) -> { + Activity activityTest = new Activity("test"); + activityTest.setChannelId(channelId); + activityTest.setServiceUrl("https://smba.trafficmanager.net/amer/"); + activityTest.setChannelData(channelData); + ConversationAccount conversation = new ConversationAccount(); + conversation.setTenantId(conversationTenantId); + activityTest.setConversation(conversation); + sut.processActivity(mockClaims, activityTest, (context) -> { activity[0] = context.getActivity(); return CompletableFuture.completedFuture(null); }).join(); @@ -202,14 +186,11 @@ public void OutgoingActivityIdNotSent() { CredentialProvider mockCredentials = mock(CredentialProvider.class); BotFrameworkAdapter adapter = new BotFrameworkAdapter(mockCredentials); - Activity incoming_activity = new Activity("test") { - { - setId("testid"); - setChannelId(Channels.DIRECTLINE); - setServiceUrl("https://fake.service.url"); - setConversation(new ConversationAccount("cid")); - } - }; + Activity incoming_activity = new Activity("test"); + incoming_activity.setId("testid"); + incoming_activity.setChannelId(Channels.DIRECTLINE); + incoming_activity.setServiceUrl("https://fake.service.url"); + incoming_activity.setConversation(new ConversationAccount("cid")); Activity reply = MessageFactory.text("test"); reply.setId("TestReplyId"); @@ -272,20 +253,14 @@ private void processActivityCreatesCorrectCredsAndClient( int expectedAppCredentialsCount, int expectedClientCredentialsCount ) { - HashMap claims = new HashMap() { - { - put(AuthenticationConstants.AUDIENCE_CLAIM, botAppId); - put(AuthenticationConstants.APPID_CLAIM, botAppId); - put(AuthenticationConstants.VERSION_CLAIM, "1.0"); - } - }; + HashMap claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, botAppId); + claims.put(AuthenticationConstants.APPID_CLAIM, botAppId); + claims.put(AuthenticationConstants.VERSION_CLAIM, "1.0"); ClaimsIdentity identity = new ClaimsIdentity("anonymous", claims); - CredentialProvider credentialProvider = new SimpleCredentialProvider() { - { - setAppId(botAppId); - } - }; + SimpleCredentialProvider credentialProvider = new SimpleCredentialProvider(); + credentialProvider.setAppId(botAppId); String serviceUrl = "https://smba.trafficmanager.net/amer/"; BotFrameworkAdapter sut = new BotFrameworkAdapter( @@ -316,12 +291,10 @@ private void processActivityCreatesCorrectCredsAndClient( return CompletableFuture.completedFuture(null); }; - sut.processActivity(identity, new Activity("test") { - { - setChannelId(Channels.EMULATOR); - setServiceUrl(serviceUrl); - } - }, callback).join(); + Activity activityTest = new Activity("test"); + activityTest.setChannelId(Channels.EMULATOR); + activityTest.setServiceUrl(serviceUrl); + sut.processActivity(identity, activityTest, callback).join(); } @Test @@ -423,13 +396,11 @@ public void DeliveryModeExpectReplies() { return CompletableFuture.completedFuture(null); }; - Activity inboundActivity = new Activity() {{ - setType(ActivityTypes.MESSAGE); - setChannelId(Channels.EMULATOR); - setServiceUrl("http://tempuri.org/whatever"); - setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); - setText("hello world"); - }}; + Activity inboundActivity = new Activity(ActivityTypes.MESSAGE); + inboundActivity.setChannelId(Channels.EMULATOR); + inboundActivity.setServiceUrl("http://tempuri.org/whatever"); + inboundActivity.setDeliveryMode(DeliveryModes.EXPECT_REPLIES.toString()); + inboundActivity.setText("hello world"); InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); @@ -456,14 +427,12 @@ public void DeliveryModeNormal() { return CompletableFuture.completedFuture(null); }; - Activity inboundActivity = new Activity() {{ - setType(ActivityTypes.MESSAGE); - setChannelId(Channels.EMULATOR); - setServiceUrl("http://tempuri.org/whatever"); - setDeliveryMode(DeliveryModes.NORMAL.toString()); - setText("hello world"); - setConversation(new ConversationAccount("conversationId")); - }}; + Activity inboundActivity = new Activity(ActivityTypes.MESSAGE); + inboundActivity.setChannelId(Channels.EMULATOR); + inboundActivity.setServiceUrl("http://tempuri.org/whatever"); + inboundActivity.setDeliveryMode(DeliveryModes.NORMAL.toString()); + inboundActivity.setText("hello world"); + inboundActivity.setConversation(new ConversationAccount("conversationId")); InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); @@ -486,13 +455,11 @@ public void DeliveryModeNull() { return CompletableFuture.completedFuture(null); }; - Activity inboundActivity = new Activity() {{ - setType(ActivityTypes.MESSAGE); - setChannelId(Channels.EMULATOR); - setServiceUrl("http://tempuri.org/whatever"); - setText("hello world"); - setConversation(new ConversationAccount("conversationId")); - }}; + Activity inboundActivity = new Activity(ActivityTypes.MESSAGE); + inboundActivity.setChannelId(Channels.EMULATOR); + inboundActivity.setServiceUrl("http://tempuri.org/whatever"); + inboundActivity.setText("hello world"); + inboundActivity.setConversation(new ConversationAccount("conversationId")); InvokeResponse invokeResponse = adapter.processActivity((String) null, inboundActivity, callback).join(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java index a9ad40382..947ff831f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/InspectionTests.java @@ -183,19 +183,11 @@ public void ScenarioWithInspectionMiddlewareOpenAttachWithMention() throws IOExc String attachCommand = "" + recipientId + " " + inspectionOpenResultActivity.getValue(); Activity attachActivity = MessageFactory.text(attachCommand); - attachActivity.getEntities().add(new Entity() { - { - setType("mention"); - getProperties().put( - "text", - JsonNodeFactory.instance.textNode("" + recipientId + "") - ); - getProperties().put( - "mentioned", - JsonNodeFactory.instance.objectNode().put("id", "bot") - ); - } - }); + Entity entity = new Entity(); + entity.setType("mention"); + entity.getProperties().put("text", JsonNodeFactory.instance.textNode("" + recipientId + "")); + entity.getProperties().put("mentioned", JsonNodeFactory.instance.objectNode().put("id", "bot")); + attachActivity.getEntities().add(entity); applicationAdapter.processActivity( attachActivity, diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java index 24027f868..aa21614cb 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MemoryConversations.java @@ -48,11 +48,9 @@ public CompletableFuture sendToConversation( Activity activity ) { sentActivities.add(activity); - return CompletableFuture.completedFuture(new ResourceResponse() { - { - setId(activity.getId()); - } - }); + ResourceResponse response = new ResourceResponse(); + response.setId(activity.getId()); + return CompletableFuture.completedFuture(response); } @Override @@ -69,11 +67,9 @@ public CompletableFuture replyToActivity( Activity activity ) { sentActivities.add(activity); - return CompletableFuture.completedFuture(new ResourceResponse() { - { - setId(activity.getId()); - } - }); + ResourceResponse response = new ResourceResponse(); + response.setId(activity.getId()); + return CompletableFuture.completedFuture(response); } @Override diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java index 75501d86d..5998af11f 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/MessageFactoryTests.java @@ -137,13 +137,10 @@ public void SuggestedActionCardAction() { String cardActionValue = UUID.randomUUID().toString(); String cardActionTitle = UUID.randomUUID().toString(); - CardAction ca = new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue(cardActionValue); - setTitle(cardActionTitle); - } - }; + CardAction ca = new CardAction(); + ca.setType(ActionTypes.IM_BACK); + ca.setValue(cardActionValue); + ca.setTitle(cardActionTitle); List cardActions = Collections.singletonList(ca); @@ -178,24 +175,18 @@ public void SuggestedActionCardActionUnordered() { String cardValue1 = UUID.randomUUID().toString(); String cardTitle1 = UUID.randomUUID().toString(); - CardAction cardAction1 = new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue(cardValue1); - setTitle(cardTitle1); - } - }; + CardAction cardAction1 = new CardAction(); + cardAction1.setType(ActionTypes.IM_BACK); + cardAction1.setValue(cardValue1); + cardAction1.setTitle(cardTitle1); String cardValue2 = UUID.randomUUID().toString(); String cardTitle2 = UUID.randomUUID().toString(); - CardAction cardAction2 = new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue(cardValue2); - setTitle(cardTitle2); - } - }; + CardAction cardAction2 = new CardAction(); + cardAction2.setType(ActionTypes.IM_BACK); + cardAction2.setValue(cardValue2); + cardAction2.setTitle(cardTitle2); List cardActions = Arrays.asList(cardAction1, cardAction2); Set values = new HashSet<>(Arrays.asList(cardValue1, cardValue2)); @@ -238,11 +229,8 @@ public void AttachmentSingle() { InputHints inputHint = InputHints.EXPECTING_INPUT; String attachmentName = UUID.randomUUID().toString(); - Attachment a = new Attachment() { - { - setName(attachmentName); - } - }; + Attachment a = new Attachment(); + a.setName(attachmentName); Activity message = MessageFactory.attachment(a, text, ssml, inputHint); @@ -283,18 +271,12 @@ public void CarouselTwoAttachments() { InputHints inputHint = InputHints.EXPECTING_INPUT; String attachmentName = UUID.randomUUID().toString(); - Attachment attachment1 = new Attachment() { - { - setName(attachmentName); - } - }; + Attachment attachment1 = new Attachment(); + attachment1.setName(attachmentName); String attachmentName2 = UUID.randomUUID().toString(); - Attachment attachment2 = new Attachment() { - { - setName(attachmentName2); - } - }; + Attachment attachment2 = new Attachment(); + attachment2.setName(attachmentName2); List multipleAttachments = Arrays.asList(attachment1, attachment2); Activity message = MessageFactory.carousel(multipleAttachments, text, ssml, inputHint); @@ -324,18 +306,12 @@ public void CarouselUnorderedAttachments() { InputHints inputHint = InputHints.EXPECTING_INPUT; String attachmentName1 = UUID.randomUUID().toString(); - Attachment attachment1 = new Attachment() { - { - setName(attachmentName1); - } - }; + Attachment attachment1 = new Attachment(); + attachment1.setName(attachmentName1); String attachmentName2 = UUID.randomUUID().toString(); - Attachment attachment2 = new Attachment() { - { - setName(attachmentName2); - } - }; + Attachment attachment2 = new Attachment(); + attachment2.setName(attachmentName2); Set multipleAttachments = new HashSet<>( Arrays.asList(attachment1, attachment2) @@ -368,18 +344,12 @@ public void AttachmentMultiple() { InputHints inputHint = InputHints.EXPECTING_INPUT; String attachmentName = UUID.randomUUID().toString(); - Attachment a = new Attachment() { - { - setName(attachmentName); - } - }; + Attachment a = new Attachment(); + a.setName(attachmentName); String attachmentName2 = UUID.randomUUID().toString(); - Attachment a2 = new Attachment() { - { - setName(attachmentName2); - } - }; + Attachment a2 = new Attachment(); + a2.setName(attachmentName2); List multipleAttachments = Arrays.asList(a, a2); Activity message = MessageFactory.attachment(multipleAttachments, text, ssml, inputHint); @@ -409,18 +379,12 @@ public void AttachmentMultipleUnordered() { InputHints inputHint = InputHints.EXPECTING_INPUT; String attachmentName1 = UUID.randomUUID().toString(); - Attachment attachment1 = new Attachment() { - { - setName(attachmentName1); - } - }; + Attachment attachment1 = new Attachment(); + attachment1.setName(attachmentName1); String attachmentName2 = UUID.randomUUID().toString(); - Attachment attachment2 = new Attachment() { - { - setName(attachmentName2); - } - }; + Attachment attachment2 = new Attachment(); + attachment2.setName(attachmentName2); Set multipleAttachments = new HashSet<>( Arrays.asList(attachment1, attachment2) @@ -481,14 +445,12 @@ public void ValidateIMBackWithText() { BotCallbackHandler replyWithimBackBack = turnContext -> { if (StringUtils.equals(turnContext.getActivity().getText(), "test")) { + CardAction card = new CardAction(); + card.setType(ActionTypes.IM_BACK); + card.setText("red"); + card.setTitle("redTitle"); Activity activity = MessageFactory.suggestedCardActions( - Collections.singletonList(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setText("red"); - setTitle("redTitle"); - } - }), + Collections.singletonList(card), "Select color" ); @@ -534,14 +496,12 @@ public void ValidateIMBackWithNoTest() { BotCallbackHandler replyWithimBackBack = turnContext -> { if (StringUtils.equals(turnContext.getActivity().getText(), "test")) { + CardAction card = new CardAction(); + card.setType(ActionTypes.IM_BACK); + card.setText("red"); + card.setTitle("redTitle"); Activity activity = MessageFactory.suggestedCardActions( - Collections.singletonList(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setText("red"); - setTitle("redTitle"); - } - }), + Collections.singletonList(card), null ); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java index 142e921fc..51048dad2 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/StorageBaseTests.java @@ -19,12 +19,9 @@ protected void readUnknownTest(Storage storage) { } protected void createObjectTest(Storage storage) { - Map storeItems = new HashMap() { - { - put("createPoco", new PocoItem("1")); - put("createPocoStoreItem", new PocoStoreItem("1")); - } - }; + Map storeItems = new HashMap(); + storeItems.put("createPoco", new PocoItem("1")); + storeItems.put("createPocoStoreItem", new PocoStoreItem("1")); storage.write(storeItems).join(); @@ -55,12 +52,8 @@ protected void createObjectTest(Storage storage) { protected void handleCrazyKeys(Storage storage) { String key = "!@#$%^&*()~/\\><,.?';\"`~"; PocoStoreItem storeItem = new PocoStoreItem("1"); - Map dict = new HashMap() { - { - put(key, storeItem); - } - }; - + Map dict = new HashMap(); + dict.put(key, storeItem); storage.write(dict).join(); Map storeItems = storage.read(new String[] { key }).join(); @@ -71,12 +64,9 @@ protected void handleCrazyKeys(Storage storage) { } protected void updateObjectTest(Storage storage) { - Map dict = new HashMap() { - { - put("pocoItem", new PocoItem("1", 1)); - put("pocoStoreItem", new PocoStoreItem("1", 1)); - } - }; + Map dict = new HashMap(); + dict.put("pocoItem", new PocoItem("1", 1)); + dict.put("pocoStoreItem", new PocoStoreItem("1", 1)); storage.write(dict).join(); Map loadedStoreItems = storage.read( @@ -127,24 +117,18 @@ protected void updateObjectTest(Storage storage) { try { updatePocoItem.setCount(123); - - storage.write(new HashMap() { - { - put("pocoItem", updatePocoItem); - } - }).join(); + HashMap pocoList = new HashMap(); + pocoList.put("pocoItem", updatePocoItem); + storage.write(pocoList).join(); } catch (Throwable t) { Assert.fail("Should not throw exception on write with pocoItem"); } try { updatePocoStoreItem.setCount(123); - - storage.write(new HashMap() { - { - put("pocoStoreItem", updatePocoStoreItem); - } - }).join(); + HashMap pocoList = new HashMap(); + pocoList.put("pocoStoreItem", updatePocoStoreItem); + storage.write(pocoList).join(); Assert.fail( "Should have thrown exception on write with store item because of old etag" @@ -169,13 +153,10 @@ protected void updateObjectTest(Storage storage) { reloadedPocoItem2.setCount(100); reloadedPocoStoreItem2.setCount(100); reloadedPocoStoreItem2.setETag("*"); - - storage.write(new HashMap() { - { - put("pocoItem", reloadedPocoItem2); - put("pocoStoreItem", reloadedPocoStoreItem2); - } - }).join(); + HashMap pocoList = new HashMap(); + pocoList.put("pocoItem", reloadedPocoItem2); + pocoList.put("pocoStoreItem", reloadedPocoStoreItem2); + storage.write(pocoList).join(); Map reloadedStoreItems3 = storage.read( new String[] { "pocoItem", "pocoStoreItem" } @@ -195,11 +176,9 @@ protected void updateObjectTest(Storage storage) { reloadedStoreItem4.setETag(""); - storage.write(new HashMap() { - { - put("pocoStoreItem", reloadedStoreItem4); - } - }).join(); + HashMap pocoList2 = new HashMap(); + pocoList2.put("pocoStoreItem", reloadedStoreItem4); + storage.write(pocoList2).join(); Assert.fail( "Should have thrown exception on write with storeitem because of empty etag" @@ -216,11 +195,8 @@ protected void updateObjectTest(Storage storage) { } protected void deleteObjectTest(Storage storage) { - Map dict = new HashMap() { - { - put("delete1", new PocoStoreItem("1", 1)); - } - }; + Map dict = new HashMap(); + dict.put("delete1", new PocoStoreItem("1", 1)); storage.write(dict).join(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java index 8326fe099..7ff33f851 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java @@ -54,12 +54,9 @@ public void Telemetry_LogActivities() { String[] conversationId = new String[] { null }; new TestFlow(adapter, (turnContext -> { conversationId[0] = turnContext.getActivity().getConversation().getId(); - turnContext.sendActivity(new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }).join(); + Activity activity = new Activity(ActivityTypes.TYPING); + activity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + turnContext.sendActivity(activity).join(); turnContext.sendActivity("echo:" + turnContext.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); })).send("foo").assertReply(activity -> { @@ -127,12 +124,9 @@ public void Telemetry_NoPII() { String[] conversationId = new String[] { null }; new TestFlow(adapter, (turnContext -> { conversationId[0] = turnContext.getActivity().getConversation().getId(); - turnContext.sendActivity(new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }).join(); + Activity activity = new Activity(ActivityTypes.TYPING); + activity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + turnContext.sendActivity(activity).join(); turnContext.sendActivity("echo:" + turnContext.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); })).send("foo").assertReply(activity -> { @@ -278,12 +272,9 @@ public void Telemetry_OverrideReceive() { String[] conversationId = new String[] { null }; new TestFlow(adapter, (turnContext -> { conversationId[0] = turnContext.getActivity().getConversation().getId(); - turnContext.sendActivity(new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }).join(); + Activity activity = new Activity(ActivityTypes.TYPING); + activity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + turnContext.sendActivity(activity).join(); turnContext.sendActivity("echo:" + turnContext.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); })).send("foo").assertReply(activity -> { @@ -349,12 +340,9 @@ public void Telemetry_OverrideSend() { String[] conversationId = new String[] { null }; new TestFlow(adapter, (turnContext -> { conversationId[0] = turnContext.getActivity().getConversation().getId(); - turnContext.sendActivity(new Activity() { - { - setType(ActivityTypes.TYPING); - setRelatesTo(turnContext.getActivity().getRelatesTo()); - } - }).join(); + Activity activity = new Activity(ActivityTypes.TYPING); + activity.setRelatesTo(turnContext.getActivity().getRelatesTo()); + turnContext.sendActivity(activity).join(); turnContext.sendActivity("echo:" + turnContext.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); })).send("foo").assertReply(activity -> { @@ -547,25 +535,23 @@ public void Telemetry_LogTeamsProperties() throws JsonProcessingException { new TelemetryLoggerMiddleware(mockTelemetryClient, true) ); - TeamInfo teamInfo = new TeamInfo() {{ - setId("teamId"); - setName("teamName"); - }}; + TeamInfo teamInfo = new TeamInfo(); + teamInfo.setId("teamId"); + teamInfo.setName("teamName"); - TeamsChannelData channelData = new TeamsChannelData() {{ - setTeam(teamInfo); - setTenant(new TenantInfo() {{ - setId("tenantId"); - }}); - }}; + TeamsChannelData channelData = new TeamsChannelData(); + channelData.setTeam(teamInfo); + TenantInfo tenant = new TenantInfo(); + tenant.setId("tenantId"); + channelData.setTenant(tenant); Activity activity = MessageFactory.text("test"); activity.setChannelData(channelData); - activity.setFrom(new ChannelAccount() {{ - setId("userId"); - setName("userName"); - setAadObjectId("aadId"); - }}); + ChannelAccount from = new ChannelAccount(); + from.setId("userId"); + from.setName("userName"); + from.setAadObjectId("aadId"); + activity.setFrom(from); new TestFlow(adapter).send(activity).startTest().join(); @@ -593,12 +579,9 @@ public OverrideReceiveLogger( @Override protected CompletableFuture onReceiveActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); getTelemetryClient().trackEvent( TelemetryLoggerConstants.BOTMSGRECEIVEEVENT, @@ -622,12 +605,9 @@ public OverrideSendLogger( @Override protected CompletableFuture onSendActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); getTelemetryClient().trackEvent( TelemetryLoggerConstants.BOTMSGSENDEVENT, @@ -651,12 +631,9 @@ public OverrideUpdateDeleteLogger( @Override protected CompletableFuture onUpdateActivity(Activity activity) { - Map properties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map properties = new HashMap(); + properties.put("foo", "bar"); + properties.put("ImportantProperty", "ImportantValue"); getTelemetryClient().trackEvent(TelemetryLoggerConstants.BOTMSGUPDATEEVENT, properties); return CompletableFuture.completedFuture(null); @@ -664,12 +641,9 @@ protected CompletableFuture onUpdateActivity(Activity activity) { @Override protected CompletableFuture onDeleteActivity(Activity activity) { - Map properties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map properties = new HashMap(); + properties.put("foo", "bar"); + properties.put("ImportantProperty", "ImportantValue"); getTelemetryClient().trackEvent(TelemetryLoggerConstants.BOTMSGDELETEEVENT, properties); return CompletableFuture.completedFuture(null); @@ -686,12 +660,9 @@ public OverrideFillLogger( @Override protected CompletableFuture onReceiveActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); return fillReceiveEventProperties(activity, customProperties).thenApply( allProperties -> { @@ -706,12 +677,9 @@ protected CompletableFuture onReceiveActivity(Activity activity) { @Override protected CompletableFuture onSendActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); return fillSendEventProperties(activity, customProperties).thenApply(allProperties -> { getTelemetryClient().trackEvent( @@ -724,12 +692,9 @@ protected CompletableFuture onSendActivity(Activity activity) { @Override protected CompletableFuture onUpdateActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); return fillUpdateEventProperties(activity, customProperties).thenApply( allProperties -> { @@ -744,12 +709,9 @@ protected CompletableFuture onUpdateActivity(Activity activity) { @Override protected CompletableFuture onDeleteActivity(Activity activity) { - Map customProperties = new HashMap() { - { - put("foo", "bar"); - put("ImportantProperty", "ImportantValue"); - } - }; + Map customProperties = new HashMap(); + customProperties.put("foo", "bar"); + customProperties.put("ImportantProperty", "ImportantValue"); return fillDeleteEventProperties(activity, customProperties).thenApply( allProperties -> { diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java index 58dfb3fd6..12fae4aaa 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestAdapterTests.java @@ -116,16 +116,13 @@ public void TestAdapter_SaySimple() { @Test public void TestAdapter_Say() { TestAdapter adapter = new TestAdapter(); + Activity messageActivity = new Activity(ActivityTypes.MESSAGE); + messageActivity.setText("echo:foo"); new TestFlow(adapter, this::myBotLogic).test( "foo", "echo:foo", "say with string works" - ).test("foo", new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText("echo:foo"); - } - }, "say with activity works").test("foo", activity -> { + ).test("foo", messageActivity, "say with activity works").test("foo", activity -> { Assert.assertEquals("echo:foo", activity.getText()); }, "say with validator works").startTest().join(); } @@ -133,15 +130,12 @@ public void TestAdapter_Say() { @Test public void TestAdapter_SendReply() { TestAdapter adapter = new TestAdapter(); + Activity messageActivity = new Activity(ActivityTypes.MESSAGE); + messageActivity.setText("echo:foo"); new TestFlow(adapter, this::myBotLogic).send("foo").assertReply( "echo:foo", "say with string works" - ).send("foo").assertReply(new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText("echo:foo"); - } - }, "say with activity works").send("foo").assertReply(activity -> { + ).send("foo").assertReply(messageActivity, "say with activity works").send("foo").assertReply(activity -> { Assert.assertEquals("echo:foo", activity.getText()); }, "say with validator works").startTest().join(); } @@ -186,16 +180,11 @@ public void TestAdapter_TestFlow() { @Test public void TestAdapter_GetUserTokenAsyncReturnsNull() { TestAdapter adapter = new TestAdapter(); - Activity activity = new Activity() { - { - setChannelId("directline"); - setFrom(new ChannelAccount() { - { - setId("testuser"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("directline"); + ChannelAccount from = new ChannelAccount(); + from.setId("testuser"); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); TokenResponse token = adapter.getUserToken(turnContext, "myconnection", null).join(); @@ -205,16 +194,11 @@ public void TestAdapter_GetUserTokenAsyncReturnsNull() { @Test public void TestAdapter_GetUserTokenAsyncReturnsNullWithCode() { TestAdapter adapter = new TestAdapter(); - Activity activity = new Activity() { - { - setChannelId("directline"); - setFrom(new ChannelAccount() { - { - setId("testuser"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("directline"); + ChannelAccount from = new ChannelAccount(); + from.setId("testuser"); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); TokenResponse token = adapter.getUserToken(turnContext, "myconnection", "abc123").join(); @@ -228,16 +212,11 @@ public void TestAdapter_GetUserTokenAsyncReturnsToken() { String channelId = "directline"; String userId = "testUser"; String token = "abc123"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken(connectionName, channelId, userId, token, null); @@ -260,16 +239,11 @@ public void TestAdapter_GetUserTokenAsyncReturnsTokenWithMagicCode() { String userId = "testUser"; String token = "abc123"; String magicCode = "888999"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken(connectionName, channelId, userId, token, magicCode); @@ -293,16 +267,11 @@ public void TestAdapter_GetSignInLink() { String connectionName = "myConnection"; String channelId = "directline"; String userId = "testUser"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); String link = adapter.getOAuthSignInLink(turnContext, connectionName, userId, null).join(); @@ -316,16 +285,11 @@ public void TestAdapter_GetSignInLinkWithNoUserId() { String connectionName = "myConnection"; String channelId = "directline"; String userId = "testUser"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); String link = adapter.getOAuthSignInLink(turnContext, connectionName).join(); @@ -339,16 +303,11 @@ public void TestAdapter_SignOutNoop() { String connectionName = "myConnection"; String channelId = "directline"; String userId = "testUser"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.signOutUser(turnContext, null, null).join(); @@ -364,16 +323,11 @@ public void TestAdapter_SignOut() { String channelId = "directline"; String userId = "testUser"; String token = "abc123"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken(connectionName, channelId, userId, token, null); @@ -399,16 +353,11 @@ public void TestAdapter_SignOutAll() { String channelId = "directline"; String userId = "testUser"; String token = "abc123"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken("ABC", channelId, userId, token, null); @@ -438,16 +387,11 @@ public void TestAdapter_GetTokenStatus() { String channelId = "directline"; String userId = "testUser"; String token = "abc123"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken("ABC", channelId, userId, token, null); @@ -465,16 +409,11 @@ public void TestAdapter_GetTokenStatusWithFilter() { String channelId = "directline"; String userId = "testUser"; String token = "abc123"; - Activity activity = new Activity() { - { - setChannelId(channelId); - setFrom(new ChannelAccount() { - { - setId(userId); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId(channelId); + ChannelAccount from = new ChannelAccount(); + from.setId(userId); + activity.setFrom(from); TurnContext turnContext = new TurnContextImpl(adapter, activity); adapter.addUserToken("ABC", channelId, userId, token, null); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestMessage.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestMessage.java index af2d63ae2..8d3f8a2d0 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestMessage.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestMessage.java @@ -14,22 +14,17 @@ public static Activity Message() { } public static Activity Message(String id) { - Activity a = new Activity(ActivityTypes.MESSAGE) { - { - setId(id); - setText("test"); - setFrom(new ChannelAccount("user", "User Name")); - setRecipient(new ChannelAccount("bot", "Bot Name")); - setConversation(new ConversationAccount() { - { - setId("convo"); - setName("Convo Name"); - } - }); - setChannelId("UnitTest"); - setServiceUrl("https://example.org"); - } - }; + Activity a = new Activity(ActivityTypes.MESSAGE); + a.setId(id); + a.setText("test"); + a.setFrom(new ChannelAccount("user", "User Name")); + a.setRecipient(new ChannelAccount("bot", "Bot Name")); + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setId("convo"); + conversationAccount.setName("Convo Name"); + a.setConversation(conversationAccount); + a.setChannelId("UnitTest"); + a.setServiceUrl("https://example.org"); return a; } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestUtilities.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestUtilities.java index 7516b876a..87fdd7d54 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestUtilities.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TestUtilities.java @@ -12,22 +12,14 @@ public final class TestUtilities { public static TurnContext createEmptyContext() { TestAdapter adapter = new TestAdapter(); - Activity activity = new Activity() { - { - setType(ActivityTypes.MESSAGE); - setChannelId("EmptyContext"); - setConversation(new ConversationAccount() { - { - setId("test"); - } - }); - setFrom(new ChannelAccount() { - { - setId("empty@empty.context.org"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("EmptyContext"); + ConversationAccount conversation = new ConversationAccount(); + conversation.setId("test"); + activity.setConversation(conversation); + ChannelAccount from = new ChannelAccount(); + from.setId("empty@empty.context.org"); + activity.setFrom(from); return new TurnContextImpl(adapter, activity); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java index 7a1b83598..be530436d 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java @@ -32,11 +32,8 @@ public CompletableFuture next() { return null; } }; - Activity typingActivity = new Activity(ActivityTypes.TYPING) { - { - setRelatesTo(context.getActivity().getRelatesTo()); - } - }; + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(context.getActivity().getRelatesTo()); try { context.sendActivity(typingActivity).join(); @@ -58,11 +55,8 @@ public final void Transcript_LogActivities() { new TestFlow(adapter, (context) -> { delay(500); conversationId[0] = context.getActivity().getConversation().getId(); - Activity typingActivity = new Activity(ActivityTypes.TYPING) { - { - setRelatesTo(context.getActivity().getRelatesTo()); - } - }; + Activity typingActivity = new Activity(ActivityTypes.TYPING); + typingActivity.setRelatesTo(context.getActivity().getRelatesTo()); context.sendActivity(typingActivity).join(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java index 8fb64edf5..b16fabf11 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TurnContextTests.java @@ -319,11 +319,9 @@ public void UpdateActivityWithMessageFactory() { foundActivity[0] = true; }); - TurnContext c = new TurnContextImpl(a, new Activity(ActivityTypes.MESSAGE) { - { - setConversation(new ConversationAccount(CONVERSATION_ID)); - } - }); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setConversation(new ConversationAccount(CONVERSATION_ID)); + TurnContext c = new TurnContextImpl(a, activity); Activity message = MessageFactory.text("test text"); message.setId(ACTIVITY_ID); @@ -435,11 +433,8 @@ public void DeleteConversationReferenceToAdapter() { TurnContext c = new TurnContextImpl(a, TestMessage.Message()); - ConversationReference reference = new ConversationReference() { - { - setActivityId("12345"); - } - }; + ConversationReference reference = new ConversationReference(); + reference.setActivityId("12345"); c.deleteActivity(reference).join(); Assert.assertTrue(activityDeleted[0]); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java index 94674825f..7b9045caf 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/adapters/TestAdapter.java @@ -97,25 +97,19 @@ public TestAdapter(String channelId, boolean sendTraceActivity) { ConversationReference conversationReference = new ConversationReference(); conversationReference.setChannelId(channelId); conversationReference.setServiceUrl("https://test.com"); - conversationReference.setUser(new ChannelAccount() { - { - setId("user1"); - setName("User1"); - } - }); - conversationReference.setBot(new ChannelAccount() { - { - setId("bot"); - setName("Bot"); - } - }); - conversationReference.setConversation(new ConversationAccount() { - { - setIsGroup(false); - setConversationType("convo1"); - setId("Conversation1"); - } - }); + ChannelAccount userAccount = new ChannelAccount(); + userAccount.setId("user1"); + userAccount.setName("User1"); + conversationReference.setUser(userAccount); + ChannelAccount botAccount = new ChannelAccount(); + botAccount.setId("bot"); + botAccount.setName("Bot"); + conversationReference.setBot(botAccount); + ConversationAccount conversation = new ConversationAccount(); + conversation.setIsGroup(false); + conversation.setConversationType("convo1"); + conversation.setId("Conversation1"); + conversationReference.setConversation(conversation); conversationReference.setLocale(this.getLocale()); setConversationReference(conversationReference); @@ -128,25 +122,19 @@ public TestAdapter(ConversationReference reference) { ConversationReference conversationReference = new ConversationReference(); conversationReference.setChannelId(Channels.TEST); conversationReference.setServiceUrl("https://test.com"); - conversationReference.setUser(new ChannelAccount() { - { - setId("user1"); - setName("User1"); - } - }); - conversationReference.setBot(new ChannelAccount() { - { - setId("bot"); - setName("Bot"); - } - }); - conversationReference.setConversation(new ConversationAccount() { - { - setIsGroup(false); - setConversationType("convo1"); - setId("Conversation1"); - } - }); + ChannelAccount userAccount = new ChannelAccount(); + userAccount.setId("user1"); + userAccount.setName("User1"); + conversationReference.setUser(userAccount); + ChannelAccount botAccount = new ChannelAccount(); + botAccount.setId("bot"); + botAccount.setName("Bot"); + conversationReference.setBot(botAccount); + ConversationAccount conversation = new ConversationAccount(); + conversation.setIsGroup(false); + conversation.setConversationType("convo1"); + conversation.setId("Conversation1"); + conversationReference.setConversation(conversation); conversationReference.setLocale(this.getLocale()); setConversationReference(conversationReference); } @@ -348,17 +336,14 @@ public Activity makeActivity() { public Activity makeActivity(String withText) { Integer next = nextId++; String locale = !getLocale().isEmpty() ? getLocale() : "en-us"; - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setLocale(locale); - setFrom(conversationReference().getUser()); - setRecipient(conversationReference().getBot()); - setConversation(conversationReference().getConversation()); - setServiceUrl(conversationReference().getServiceUrl()); - setId(next.toString()); - setText(withText); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setLocale(locale); + activity.setFrom(conversationReference().getUser()); + activity.setRecipient(conversationReference().getBot()); + activity.setConversation(conversationReference().getConversation()); + activity.setServiceUrl(conversationReference().getServiceUrl()); + activity.setId(next.toString()); + activity.setText(withText); activity.setLocale(getLocale() != null ? getLocale() : "en-us"); return activity; @@ -384,13 +369,11 @@ public void addUserToken(String connectionName, String channelId, String userId, if (withMagicCode == null) { userTokens.put(userKey, token); } else { - magicCodes.add(new TokenMagicCode() { - { - key = userKey; - magicCode = withMagicCode; - userToken = token; - } - }); + TokenMagicCode tokenMagicCode = new TokenMagicCode(); + tokenMagicCode.key = userKey; + tokenMagicCode.magicCode = withMagicCode; + tokenMagicCode.userToken = token; + magicCodes.add(tokenMagicCode); } } @@ -484,12 +467,10 @@ public CompletableFuture getUserToken(TurnContext turnContext, Ap } if (userTokens.containsKey(key)) { - return CompletableFuture.completedFuture(new TokenResponse() { - { - setConnectionName(connectionName); - setToken(userTokens.get(key)); - } - }); + TokenResponse tokenResponse = new TokenResponse(); + tokenResponse.setConnectionName(connectionName); + tokenResponse.setToken(userTokens.get(key)); + return CompletableFuture.completedFuture(tokenResponse); } return CompletableFuture.completedFuture(null); @@ -564,12 +545,12 @@ public CompletableFuture> getTokenStatus(TurnContext turnConte .filter(x -> StringUtils.equals(x.channelId, turnContext.getActivity().getChannelId()) && StringUtils.equals(x.userId, turnContext.getActivity().getFrom().getId()) && (includeFilter == null || Arrays.binarySearch(filter, x.connectionName) != -1)) - .map(r -> new TokenStatus() { - { - setConnectionName(r.connectionName); - setHasToken(true); - setServiceProviderDisplayName(r.connectionName); - } + .map(r -> { + TokenStatus tokenStatus = new TokenStatus(); + tokenStatus.setConnectionName(r.connectionName); + tokenStatus.setHasToken(true); + tokenStatus.setServiceProviderDisplayName(r.connectionName); + return tokenStatus; }).collect(Collectors.toList()); if (records.size() > 0) { @@ -659,13 +640,11 @@ public CompletableFuture exchangeToken(TurnContext turnContext, A new RuntimeException("Exception occurred during exchanging tokens") ); } - return CompletableFuture.completedFuture(new TokenResponse() { - { - setChannelId(key.getChannelId()); - setConnectionName(key.getConnectionName()); - setToken(token); - } - }); + TokenResponse tokenResponse = new TokenResponse(); + tokenResponse.setChannelId(key.getChannelId()); + tokenResponse.setConnectionName(key.getConnectionName()); + tokenResponse.setToken(token); + return CompletableFuture.completedFuture(tokenResponse); } else { return CompletableFuture.completedFuture(null); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerBadRequestTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerBadRequestTests.java index 3ad9af47f..ce68b3c25 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerBadRequestTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerBadRequestTests.java @@ -21,23 +21,16 @@ public class TeamsActivityHandlerBadRequestTests { @Test public void TestFileConsentBadAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("this.is.a.bad.action"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse fileConsentCard = new FileConsentCardResponse(); + fileConsentCard.setAction("this.is.a.bad.action"); + fileConsentCard.setUploadInfo(fileInfo); + activity.setValue(fileConsentCard); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -60,16 +53,11 @@ public void TestFileConsentBadAction() { @Test public void TestMessagingExtensionSubmitActionPreviewBadAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("this.is.a.bad.action"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("this.is.a.bad.action"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java index c51ec6ea3..8a6dec90d 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerHidingTests.java @@ -42,17 +42,11 @@ public void TestMessageActivity() { @Test public void TestMemberAdded1() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -65,18 +59,12 @@ public void TestMemberAdded1() { @Test public void TestMemberAdded2() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -90,19 +78,13 @@ public void TestMemberAdded2() { @Test public void TestMemberAdded3() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + members.add(new ChannelAccount("c")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -116,17 +98,11 @@ public void TestMemberAdded3() { @Test public void TestMemberRemoved1() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -139,18 +115,12 @@ public void TestMemberRemoved1() { @Test public void TestMemberRemoved2() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -164,19 +134,13 @@ public void TestMemberRemoved2() { @Test public void TestMemberRemoved3() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("a")); - add(new ChannelAccount("b")); - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + members.add(new ChannelAccount("b")); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -190,17 +154,11 @@ public void TestMemberRemoved3() { @Test public void TestMemberAddedJustTheBot() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("b")); - } - }); - setRecipient(new ChannelAccount("b")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("b")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -213,17 +171,11 @@ public void TestMemberAddedJustTheBot() { @Test public void TestMemberRemovedJustTheBot() { - Activity activity = new Activity() { - { - setType(ActivityTypes.CONVERSATION_UPDATE); - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("c")); - } - }); - setRecipient(new ChannelAccount("c")); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("c")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("c")); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -241,21 +193,13 @@ public void TestMessageReaction() { // sends separate activities each with a single add and a single remove. // Arrange - Activity activity = new Activity() { - { - setType(ActivityTypes.MESSAGE_REACTION); - setReactionsAdded(new ArrayList() { - { - add(new MessageReaction("sad")); - } - }); - setReactionsRemoved(new ArrayList() { - { - add(new MessageReaction("angry")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE_REACTION); + ArrayList reactionsAdded = new ArrayList(); + reactionsAdded.add(new MessageReaction("sad")); + activity.setReactionsAdded(reactionsAdded); + ArrayList reactionsRemoved = new ArrayList(); + reactionsRemoved.add(new MessageReaction("angry")); + activity.setReactionsRemoved(reactionsRemoved); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -270,12 +214,8 @@ public void TestMessageReaction() { @Test public void TestTokenResponseEventAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - setName("tokens/response"); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setName("tokens/response"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -289,12 +229,8 @@ public void TestTokenResponseEventAsync() { @Test public void TestEventAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - setName("some.random.event"); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); + activity.setName("some.random.event"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -308,11 +244,7 @@ public void TestEventAsync() { @Test public void TestEventNullNameAsync() { - Activity activity = new Activity() { - { - setType(ActivityTypes.EVENT); - } - }; + Activity activity = new Activity(ActivityTypes.EVENT); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -326,12 +258,7 @@ public void TestEventNullNameAsync() { @Test public void TestUnrecognizedActivityType() { - Activity activity = new Activity() { - { - setType("shall.not.pass"); - } - }; - + Activity activity = new Activity("shall.not.pass"); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); TestActivityHandler bot = new TestActivityHandler(); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerNotImplementedTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerNotImplementedTests.java index 3277e2222..aea8885e4 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerNotImplementedTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerNotImplementedTests.java @@ -30,317 +30,220 @@ public class TeamsActivityHandlerNotImplementedTests { @Test public void TestInvoke() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("gibberish"); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("gibberish"); assertNotImplemented(activity); } @Test public void TestFileConsentAccept() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("accept"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("accept"); + response.setUploadInfo(fileInfo); + activity.setValue(response); assertNotImplemented(activity); } @Test public void TestFileConsentDecline() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("decline"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("decline"); + response.setUploadInfo(fileInfo); + activity.setValue(response); assertNotImplemented(activity); } @Test public void TestActionableMessageExecuteAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("actionableMessage/executeAction"); - setValue(new O365ConnectorCardActionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("actionableMessage/executeAction"); + activity.setValue(new O365ConnectorCardActionQuery()); assertNotImplemented(activity); } @Test public void TestComposeExtensionQueryLink() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/queryLink"); - setValue(new AppBasedLinkQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/queryLink"); + activity.setValue(new AppBasedLinkQuery()); assertNotImplemented(activity); } @Test public void TestComposeExtensionQuery() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/query"); - setValue(new MessagingExtensionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/query"); + activity.setValue(new MessagingExtensionQuery()); assertNotImplemented(activity); } @Test public void TestMessagingExtensionSelectItem() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/selectItem"); - setValue(new O365ConnectorCardActionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/selectItem"); + activity.setValue(new O365ConnectorCardActionQuery()); assertNotImplemented(activity); } @Test public void TestMessagingExtensionSubmitAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + activity.setValue(new MessagingExtensionQuery()); assertNotImplemented(activity); } @Test public void TestMessagingExtensionSubmitActionPreviewActionEdit() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("edit"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("edit"); + activity.setValue(action); assertNotImplemented(activity); } @Test public void TestMessagingExtensionSubmitActionPreviewActionSend() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("send"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("send"); + activity.setValue(action); assertNotImplemented(activity); } @Test public void TestMessagingExtensionFetchTask() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/fetchTask"); - setValue(new MessagingExtensionAction() { - { - setCommandId("testCommand"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/fetchTask"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setCommandId("testCommand"); + activity.setValue(action); assertNotImplemented(activity); } @Test public void TestMessagingExtensionConfigurationQuerySettingsUrl() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/querySettingsUrl"); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/querySettingsUrl"); assertNotImplemented(activity); } @Test public void TestMessagingExtensionConfigurationSetting() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/setting"); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/setting"); assertNotImplemented(activity); } @Test public void TestTaskModuleFetch() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("task/fetch"); - setValue(new TaskModuleRequest() { - { - setData(new HashMap() { - { - put("key", "value"); - put("type", "task / fetch"); - } - }); - setContext(new TaskModuleRequestContext() { - { - setTheme("default"); - } - }); - } - }); - } - }; - + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("task/fetch"); + HashMap data = new HashMap(); + data.put("key", "value"); + data.put("type", "task / fetch"); + TaskModuleRequestContext context = new TaskModuleRequestContext(); + context.setTheme("default"); + TaskModuleRequest request = new TaskModuleRequest(); + request.setData(data); + request.setContext(context); + activity.setValue(request); assertNotImplemented(activity); } @Test public void TestTaskModuleSubmit() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("task/submit"); - setValue(new TaskModuleRequest() { - { - setData(new HashMap() { - { - put("key", "value"); - put("type", "task / fetch"); - } - }); - setContext(new TaskModuleRequestContext() { - { - setTheme("default"); - } - }); - } - }); - } - }; - + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("task/submit"); + HashMap data = new HashMap(); + data.put("key", "value"); + data.put("type", "task / fetch"); + TaskModuleRequestContext context = new TaskModuleRequestContext(); + context.setTheme("default"); + TaskModuleRequest request = new TaskModuleRequest(); + request.setData(data); + request.setContext(context); + activity.setValue(request); assertNotImplemented(activity); } @Test public void TestFileConsentAcceptImplemented() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("accept"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("accept"); + response.setUploadInfo(fileInfo); + activity.setValue(response); assertImplemented(activity, new TestActivityHandlerFileConsent()); } @Test public void TestFileConsentDeclineImplemented() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("decline"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("decline"); + response.setUploadInfo(fileInfo); + activity.setValue(response); assertImplemented(activity, new TestActivityHandlerFileConsent()); } @Test public void TestMessagingExtensionSubmitActionPreviewActionEditImplemented() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("edit"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("edit"); + activity.setValue(action); assertImplemented(activity, new TestActivityHandlerPrevieAction()); } @Test public void TestMessagingExtensionSubmitActionPreviewActionSendImplemented() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("send"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("send"); + activity.setValue(action); assertImplemented(activity, new TestActivityHandlerPrevieAction()); } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java index 0dddcac0f..3d6ee347b 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsActivityHandlerTests.java @@ -59,23 +59,16 @@ public void TestConversationUpdateBotTeamsMemberAdded() { MicrosoftAppCredentials.empty() ); - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("botid-1")); - } - }); - setRecipient(new ChannelAccount("botid-1")); - setChannelData(new TeamsChannelData() { - { - setEventType("teamMemberAdded"); - setTeam(new TeamInfo("team-id")); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("botid-1")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("botid-1")); + TeamsChannelData channelData = new TeamsChannelData(); + channelData.setEventType("teamMemberAdded"); + channelData.setTeam(new TeamInfo("team-id")); + activity.setChannelData(channelData); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -96,23 +89,16 @@ public void TestConversationUpdateTeamsMemberAdded() { MicrosoftAppCredentials.empty() ); - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("id-1")); - } - }); - setRecipient(new ChannelAccount("b")); - setChannelData(new TeamsChannelData() { - { - setEventType("teamMemberAdded"); - setTeam(new TeamInfo("team-id")); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("id-1")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); + TeamsChannelData channelData = new TeamsChannelData(); + channelData.setEventType("teamMemberAdded"); + channelData.setTeam(new TeamInfo("team-id")); + activity.setChannelData(channelData); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -133,18 +119,13 @@ public void TestConversationUpdateTeamsMemberAddedNoTeam() { MicrosoftAppCredentials.empty() ); - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersAdded(new ArrayList() { - { - add(new ChannelAccount("id-1")); - } - }); - setRecipient(new ChannelAccount("b")); - setConversation(new ConversationAccount("conversation-id")); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("id-1")); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); + activity.setConversation(new ConversationAccount("conversation-id")); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -165,35 +146,26 @@ public void TestConversationUpdateTeamsMemberAddedFullDetailsInEvent() { MicrosoftAppCredentials.empty() ); - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersAdded(new ArrayList() { - { - add(new TeamsChannelAccount() { - { - setId("id-1"); - setName("name-1"); - setAadObjectId("aadobject-1"); - setEmail("test@microsoft.com"); - setGivenName("given-1"); - setSurname("surname-1"); - setUserPrincipalName("t@microsoft.com"); - setTenantId("testTenantId"); - setUserRole("guest"); - } - }); - } - }); - setRecipient(new ChannelAccount("b")); - setChannelData(new TeamsChannelData() { - { - setEventType("teamMemberAdded"); - setTeam(new TeamInfo("team-id")); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + TeamsChannelAccount teams = new TeamsChannelAccount(); + teams.setId("id-1"); + teams.setName("name-1"); + teams.setAadObjectId("aadobject-1"); + teams.setEmail("test@microsoft.com"); + teams.setGivenName("given-1"); + teams.setSurname("surname-1"); + teams.setUserPrincipalName("t@microsoft.com"); + teams.setTenantId("testTenantId"); + teams.setUserRole("guest"); + members.add(teams); + activity.setMembersAdded(members); + activity.setRecipient(new ChannelAccount("b")); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamMemberAdded"); + data.setTeam(new TeamInfo("team-id")); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); // serialize to json and back to verify we can get back to the // correct Activity. i.e., In this case, mainly the TeamsChannelAccount. @@ -218,22 +190,15 @@ public void TestConversationUpdateTeamsMemberAddedFullDetailsInEvent() { @Test public void TestConversationUpdateTeamsMemberRemoved() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersRemoved(new ArrayList() { - { - add(new ChannelAccount("a")); - } - }); - setRecipient(new ChannelAccount("b")); - setChannelData(new TeamsChannelData() { - { - setEventType("teamMemberRemoved"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + ArrayList members = new ArrayList(); + members.add(new ChannelAccount("a")); + activity.setMembersRemoved(members); + activity.setRecipient(new ChannelAccount("b")); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamMemberRemoved"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -247,16 +212,11 @@ public void TestConversationUpdateTeamsMemberRemoved() { @Test public void TestConversationUpdateTeamsChannelCreated() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("channelCreated"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("channelCreated"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -270,16 +230,11 @@ public void TestConversationUpdateTeamsChannelCreated() { @Test public void TestConversationUpdateTeamsChannelDeleted() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("channelDeleted"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("channelDeleted"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -293,16 +248,11 @@ public void TestConversationUpdateTeamsChannelDeleted() { @Test public void TestConversationUpdateTeamsChannelRenamed() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("channelRenamed"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("channelRenamed"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -316,16 +266,11 @@ public void TestConversationUpdateTeamsChannelRenamed() { @Test public void TestConversationUpdateTeamsChannelRestored() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("channelRestored"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("channelRestored"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -339,16 +284,11 @@ public void TestConversationUpdateTeamsChannelRestored() { @Test public void TestConversationUpdateTeamsTeamRenamed() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamRenamed"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamRenamed"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -362,16 +302,11 @@ public void TestConversationUpdateTeamsTeamRenamed() { @Test public void TestConversationUpdateTeamsTeamArchived() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamArchived"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamArchived"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -385,16 +320,11 @@ public void TestConversationUpdateTeamsTeamArchived() { @Test public void TestConversationUpdateTeamsTeamDeleted() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamDeleted"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamDeleted"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -408,16 +338,11 @@ public void TestConversationUpdateTeamsTeamDeleted() { @Test public void TestConversationUpdateTeamsTeamHardDeleted() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamHardDeleted"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamHardDeleted"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -431,16 +356,11 @@ public void TestConversationUpdateTeamsTeamHardDeleted() { @Test public void TestConversationUpdateTeamsTeamRestored() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamRestored"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamRestored"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -454,16 +374,11 @@ public void TestConversationUpdateTeamsTeamRestored() { @Test public void TestConversationUpdateTeamsTeamUnarchived() { - Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setChannelData(new TeamsChannelData() { - { - setEventType("teamUnarchived"); - } - }); - setChannelId(Channels.MSTEAMS); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + TeamsChannelData data = new TeamsChannelData(); + data.setEventType("teamUnarchived"); + activity.setChannelData(data); + activity.setChannelId(Channels.MSTEAMS); TurnContext turnContext = new TurnContextImpl(new NotImplementedAdapter(), activity); @@ -477,23 +392,16 @@ public void TestConversationUpdateTeamsTeamUnarchived() { @Test public void TestFileConsentAccept() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("accept"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("accept"); + response.setUploadInfo(fileInfo); + activity.setValue(response); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -520,23 +428,16 @@ public void TestFileConsentAccept() { @Test public void TestFileConsentDecline() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("fileConsent/invoke"); - setValue(new FileConsentCardResponse() { - { - setAction("decline"); - setUploadInfo(new FileUploadInfo() { - { - setUniqueId("uniqueId"); - setFileType("fileType"); - setUploadUrl("uploadUrl"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("fileConsent/invoke"); + FileUploadInfo fileInfo = new FileUploadInfo(); + fileInfo.setUniqueId("uniqueId"); + fileInfo.setFileType("fileType"); + fileInfo.setUploadUrl("uploadUrl"); + FileConsentCardResponse response = new FileConsentCardResponse(); + response.setAction("decline"); + response.setUploadInfo(fileInfo); + activity.setValue(response); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -563,12 +464,9 @@ public void TestFileConsentDecline() { @Test public void TestActionableMessageExecuteAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("actionableMessage/executeAction"); - setValue(new O365ConnectorCardActionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("actionableMessage/executeAction"); + activity.setValue(new O365ConnectorCardActionQuery()); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -594,12 +492,9 @@ public void TestActionableMessageExecuteAction() { @Test public void TestComposeExtensionQueryLink() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/queryLink"); - setValue(new AppBasedLinkQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/queryLink"); + activity.setValue(new AppBasedLinkQuery()); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -625,12 +520,9 @@ public void TestComposeExtensionQueryLink() { @Test public void TestComposeExtensionQuery() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/query"); - setValue(new MessagingExtensionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/query"); + activity.setValue(new MessagingExtensionQuery()); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -656,12 +548,9 @@ public void TestComposeExtensionQuery() { @Test public void TestMessagingExtensionSelectItemAsync() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/selectItem"); - setValue(new MessagingExtensionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/selectItem"); + activity.setValue(new MessagingExtensionQuery()); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -687,12 +576,9 @@ public void TestMessagingExtensionSelectItemAsync() { @Test public void TestMessagingExtensionSubmitAction() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionQuery()); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + activity.setValue(new MessagingExtensionQuery()); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -719,16 +605,11 @@ public void TestMessagingExtensionSubmitAction() { @Test public void TestMessagingExtensionSubmitActionPreviewActionEdit() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("edit"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("edit"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -755,16 +636,11 @@ public void TestMessagingExtensionSubmitActionPreviewActionEdit() { @Test public void TestMessagingExtensionSubmitActionPreviewActionSend() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/submitAction"); - setValue(new MessagingExtensionAction() { - { - setBotMessagePreviewAction("send"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/submitAction"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setBotMessagePreviewAction("send"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -791,16 +667,11 @@ public void TestMessagingExtensionSubmitActionPreviewActionSend() { @Test public void TestMessagingExtensionFetchTask() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/fetchTask"); - setValue(new MessagingExtensionAction() { - { - setCommandId("testCommand"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/fetchTask"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setCommandId("testCommand"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -826,16 +697,11 @@ public void TestMessagingExtensionFetchTask() { @Test public void TestMessagingExtensionConfigurationQuerySettingUrl() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/querySettingUrl"); - setValue(new MessagingExtensionAction() { - { - setCommandId("testCommand"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/querySettingUrl"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setCommandId("testCommand"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -864,16 +730,11 @@ public void TestMessagingExtensionConfigurationQuerySettingUrl() { @Test public void TestMessagingExtensionConfigurationSetting() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("composeExtension/setting"); - setValue(new MessagingExtensionAction() { - { - setCommandId("testCommand"); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("composeExtension/setting"); + MessagingExtensionAction action = new MessagingExtensionAction(); + action.setCommandId("testCommand"); + activity.setValue(action); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -899,26 +760,17 @@ public void TestMessagingExtensionConfigurationSetting() { @Test public void TestTaskModuleFetch() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("task/fetch"); - setValue(new TaskModuleRequest() { - { - setData(new HashMap() { - { - put("key", "value"); - put("type", "task / fetch"); - } - }); - setContext(new TaskModuleRequestContext() { - { - setTheme("default"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("task/fetch"); + TaskModuleRequestContext context = new TaskModuleRequestContext(); + context.setTheme("default"); + HashMap data = new HashMap(); + data.put("key", "value"); + data.put("type", "task / fetch"); + TaskModuleRequest request = new TaskModuleRequest(); + request.setData(data); + request.setContext(context); + activity.setValue(request); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -944,26 +796,17 @@ public void TestTaskModuleFetch() { @Test public void TestTaskModuleSubmit() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("task/submit"); - setValue(new TaskModuleRequest() { - { - setData(new HashMap() { - { - put("key", "value"); - put("type", "task / fetch"); - } - }); - setContext(new TaskModuleRequestContext() { - { - setTheme("default"); - } - }); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("task/submit"); + TaskModuleRequestContext context = new TaskModuleRequestContext(); + context.setTheme("default"); + HashMap data = new HashMap(); + data.put("key", "value"); + data.put("type", "task / fetch"); + TaskModuleRequest request = new TaskModuleRequest(); + request.setData(data); + request.setContext(context); + activity.setValue(request); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -989,11 +832,8 @@ public void TestTaskModuleSubmit() { @Test public void TestSigninVerifyState() { - Activity activity = new Activity(ActivityTypes.INVOKE) { - { - setName("signin/verifyState"); - } - }; + Activity activity = new Activity(ActivityTypes.INVOKE); + activity.setName("signin/verifyState"); AtomicReference> activitiesToSend = new AtomicReference<>(); @@ -1374,205 +1214,99 @@ protected CompletableFuture onTeamsTeamUnarchived( private static ConnectorClient getConnectorClient(String baseUri, AppCredentials credentials) { Conversations mockConversations = Mockito.mock(Conversations.class); + ConversationResourceResponse response = new ConversationResourceResponse(); + response.setId("team-id"); + response.setServiceUrl("https://serviceUrl/"); + response.setActivityId("activityId123"); + // createConversation Mockito.when( mockConversations.createConversation(Mockito.any(ConversationParameters.class)) - ).thenReturn(CompletableFuture.completedFuture(new ConversationResourceResponse() { - { - setId("team-id"); - setServiceUrl("https://serviceUrl/"); - setActivityId("activityId123"); - } - })); - + ).thenReturn(CompletableFuture.completedFuture(response)); + + + ArrayList channelAccount1 = new ArrayList(); + ChannelAccount account1 = new ChannelAccount(); + account1.setId("id-1"); + account1.setName("name-1"); + account1.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-1")); + account1.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-1")); + account1.setProperties("surname", JsonNodeFactory.instance.textNode("surname-1")); + account1.setProperties("email", JsonNodeFactory.instance.textNode("email-1")); + account1.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-1")); + account1.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-1")); + channelAccount1.add(account1); + + ChannelAccount account2 = new ChannelAccount(); + account2.setId("id-2"); + account2.setName("name-2"); + account2.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-2")); + account2.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-2")); + account2.setProperties("surname",JsonNodeFactory.instance.textNode("surname-2")); + account2.setProperties("email", JsonNodeFactory.instance.textNode("email-2")); + account2.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-2")); + account2.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-2")); + channelAccount1.add(account2); // getConversationMembers (Team) Mockito.when(mockConversations.getConversationMembers("team-id")).thenReturn( - CompletableFuture.completedFuture(new ArrayList() { - { - add(new ChannelAccount() { - { - setId("id-1"); - setName("name-1"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-1") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-1") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-1") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-1")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-1") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-1") - ); - } - }); - add(new ChannelAccount() { - { - setId("id-2"); - setName("name-2"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-2") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-2") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-2") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-2")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-2") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-2") - ); - } - }); - } - }) + CompletableFuture.completedFuture(channelAccount1) ); + + ArrayList channelAccount2 = new ArrayList(); + ChannelAccount channelAccount3 = new ChannelAccount(); + channelAccount3.setId("id-3"); + channelAccount3.setName("name-3"); + channelAccount3.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-3")); + channelAccount3.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-3")); + channelAccount3.setProperties("surname", JsonNodeFactory.instance.textNode("surname-3")); + channelAccount3.setProperties("email", JsonNodeFactory.instance.textNode("email-3")); + channelAccount3.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-3")); + channelAccount3.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-3")); + channelAccount2.add(channelAccount3); + ChannelAccount channelAccount4 = new ChannelAccount(); + channelAccount4.setId("id-4"); + channelAccount4.setName("name-4"); + channelAccount4.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-4")); + channelAccount4.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-4")); + channelAccount4.setProperties("surname", JsonNodeFactory.instance.textNode("surname-4")); + channelAccount4.setProperties("email", JsonNodeFactory.instance.textNode("email-4")); + channelAccount4.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-4")); + channelAccount4.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-4")); + channelAccount2.add(channelAccount4); // getConversationMembers (Group chat) Mockito.when(mockConversations.getConversationMembers("conversation-id")).thenReturn( - CompletableFuture.completedFuture(new ArrayList() { - { - add(new ChannelAccount() { - { - setId("id-3"); - setName("name-3"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-3") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-3") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-3") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-3")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-3") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-3") - ); - } - }); - add(new ChannelAccount() { - { - setId("id-4"); - setName("name-4"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-4") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-4") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-4") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-4")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-4") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-4") - ); - } - }); - } - }) + CompletableFuture.completedFuture(channelAccount2) ); + + ChannelAccount channelAccount5 = new ChannelAccount(); + channelAccount5.setId("id-1"); + channelAccount5.setName("name-1"); + channelAccount5.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-1")); + channelAccount5.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-1")); + channelAccount5.setProperties("surname", JsonNodeFactory.instance.textNode("surname-1")); + channelAccount5.setProperties("email", JsonNodeFactory.instance.textNode("email-1")); + channelAccount5.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-1")); + channelAccount5.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-1")); // getConversationMember (Team) Mockito.when(mockConversations.getConversationMember("id-1", "team-id")).thenReturn( - CompletableFuture.completedFuture( - new ChannelAccount() { - { - setId("id-1"); - setName("name-1"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-1") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-1") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-1") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-1")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-1") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-1") - ); - } - } - ) + CompletableFuture.completedFuture(channelAccount5) ); + + ChannelAccount channelAccount6 = new ChannelAccount(); + channelAccount6.setId("id-1"); + channelAccount6.setName("name-1"); + channelAccount6.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-1")); + channelAccount6.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-1")); + channelAccount6.setProperties("surname", JsonNodeFactory.instance.textNode("surname-1")); + channelAccount6.setProperties("email", JsonNodeFactory.instance.textNode("email-1")); + channelAccount6.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-1")); + channelAccount6.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-1")); // getConversationMember (Group chat) Mockito.when(mockConversations.getConversationMember("id-1", "conversation-id")).thenReturn( - CompletableFuture.completedFuture( - new ChannelAccount() { - { - setId("id-1"); - setName("name-1"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-1") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-1") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-1") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-1")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-1") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-1") - ); - } - } - ) + CompletableFuture.completedFuture(channelAccount6) ); ConnectorClient mockConnectorClient = Mockito.mock(ConnectorClient.class); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsInfoTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsInfoTests.java index e0b7da57a..cd47f251e 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsInfoTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/teams/TeamsInfoTests.java @@ -54,17 +54,12 @@ public void TestSendMessageToTeamsChannel() { ); ConnectorClient connectorClient = getConnectorClient(baseUri, credentials); - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setText("Test-SendMessageToTeamsChannelAsync"); - setChannelId(Channels.MSTEAMS); - setChannelData(new TeamsChannelData() { - { - setTeam(new TeamInfo("team-id")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Test-SendMessageToTeamsChannelAsync"); + activity.setChannelId(Channels.MSTEAMS); + TeamsChannelData data = new TeamsChannelData(); + data.setTeam(new TeamInfo("team-id")); + activity.setChannelData(data); TurnContext turnContext = new TurnContextImpl( new TestBotFrameworkAdapter( @@ -89,17 +84,12 @@ public void TestGetTeamDetails() { MicrosoftAppCredentials credentials = MicrosoftAppCredentials.empty(); ConnectorClient connectorClient = getConnectorClient(baseUri, credentials); - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setText("Test-GetTeamDetailsAsync"); - setChannelId(Channels.MSTEAMS); - setChannelData(new TeamsChannelData() { - { - setTeam(new TeamInfo("team-id")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Test-GetTeamDetailsAsync"); + activity.setChannelId(Channels.MSTEAMS); + TeamsChannelData data = new TeamsChannelData(); + data.setTeam(new TeamInfo("team-id")); + activity.setChannelData(data); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -119,17 +109,12 @@ public void TestTeamGetMembers() { MicrosoftAppCredentials credentials = MicrosoftAppCredentials.empty(); ConnectorClient connectorClient = getConnectorClient(baseUri, credentials); - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setText("Test-Team-GetMembersAsync"); - setChannelId(Channels.MSTEAMS); - setChannelData(new TeamsChannelData() { - { - setTeam(new TeamInfo("team-id")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Test-Team-GetMembersAsync"); + activity.setChannelId(Channels.MSTEAMS); + TeamsChannelData data = new TeamsChannelData(); + data.setTeam(new TeamInfo("team-id")); + activity.setChannelData(data); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -149,13 +134,10 @@ public void TestGroupChatGetMembers() { MicrosoftAppCredentials credentials = MicrosoftAppCredentials.empty(); ConnectorClient connectorClient = getConnectorClient(baseUri, credentials); - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setText("Test-GroupChat-GetMembersAsync"); - setChannelId(Channels.MSTEAMS); - setConversation(new ConversationAccount("conversation-id")); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Test-GroupChat-GetMembersAsync"); + activity.setChannelId(Channels.MSTEAMS); + activity.setConversation(new ConversationAccount("conversation-id")); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -175,17 +157,12 @@ public void TestGetChannels() { MicrosoftAppCredentials credentials = MicrosoftAppCredentials.empty(); ConnectorClient connectorClient = getConnectorClient(baseUri, credentials); - Activity activity = new Activity(ActivityTypes.MESSAGE) { - { - setText("Test-GetChannelsAsync"); - setChannelId(Channels.MSTEAMS); - setChannelData(new TeamsChannelData() { - { - setTeam(new TeamInfo("team-id")); - } - }); - } - }; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText("Test-GetChannelsAsync"); + activity.setChannelId(Channels.MSTEAMS); + TeamsChannelData data = new TeamsChannelData(); + data.setTeam(new TeamInfo("team-id")); + activity.setChannelData(data); TurnContext turnContext = new TurnContextImpl(new SimpleAdapter(), activity); turnContext.getTurnState().add(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY, connectorClient); @@ -336,139 +313,68 @@ private CompletableFuture callGetChannels(TurnContext turnContext) { private static ConnectorClient getConnectorClient(String baseUri, AppCredentials credentials) { Conversations mockConversations = Mockito.mock(Conversations.class); + ConversationResourceResponse response = new ConversationResourceResponse(); + response.setId("team-id"); + response.setServiceUrl("https://serviceUrl/"); + response.setActivityId("activityId123"); + // createConversation Mockito.when( mockConversations.createConversation(Mockito.any(ConversationParameters.class)) - ).thenReturn(CompletableFuture.completedFuture(new ConversationResourceResponse() { - { - setId("team-id"); - setServiceUrl("https://serviceUrl/"); - setActivityId("activityId123"); - } - })); - + ).thenReturn(CompletableFuture.completedFuture(response)); + + + ArrayList channelAccounts1 = new ArrayList(); + ChannelAccount channelAccount1 = new ChannelAccount(); + channelAccount1.setId("id-1"); + channelAccount1.setName("name-1"); + channelAccount1.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-1")); + channelAccount1.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-1")); + channelAccount1.setProperties("surname", JsonNodeFactory.instance.textNode("surname-1")); + channelAccount1.setProperties("email", JsonNodeFactory.instance.textNode("email-1")); + channelAccount1.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-1")); + channelAccount1.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-1")); + channelAccounts1.add(channelAccount1); + ChannelAccount channelAccount2 = new ChannelAccount(); + channelAccount2.setId("id-2"); + channelAccount2.setName("name-2"); + channelAccount2.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-2")); + channelAccount2.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-2")); + channelAccount2.setProperties("surname", JsonNodeFactory.instance.textNode("surname-2")); + channelAccount2.setProperties("email", JsonNodeFactory.instance.textNode("email-2")); + channelAccount2.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-2")); + channelAccount2.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-2")); + channelAccounts1.add(channelAccount2); // getConversationMembers (Team) Mockito.when(mockConversations.getConversationMembers("team-id")).thenReturn( - CompletableFuture.completedFuture(new ArrayList() { - { - add(new ChannelAccount() { - { - setId("id-1"); - setName("name-1"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-1") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-1") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-1") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-1")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-1") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-1") - ); - } - }); - add(new ChannelAccount() { - { - setId("id-2"); - setName("name-2"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-2") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-2") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-2") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-2")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-2") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-2") - ); - } - }); - } - }) + CompletableFuture.completedFuture(channelAccounts1) ); + + ArrayList channelAccounts2 = new ArrayList(); + ChannelAccount channelAccount3 = new ChannelAccount(); + channelAccount3.setId("id-3"); + channelAccount3.setName("name-3"); + channelAccount3.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-3")); + channelAccount3.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-3")); + channelAccount3.setProperties("surname", JsonNodeFactory.instance.textNode("surname-3")); + channelAccount3.setProperties("email", JsonNodeFactory.instance.textNode("email-3")); + channelAccount3.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-3")); + channelAccount3.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-3")); + channelAccounts2.add(channelAccount3); + ChannelAccount channelAccount4 = new ChannelAccount(); + channelAccount4.setId("id-4"); + channelAccount4.setName("name-4"); + channelAccount4.setProperties("objectId", JsonNodeFactory.instance.textNode("objectId-4")); + channelAccount4.setProperties("givenName", JsonNodeFactory.instance.textNode("givenName-4")); + channelAccount4.setProperties("surname", JsonNodeFactory.instance.textNode("surname-4")); + channelAccount4.setProperties("email", JsonNodeFactory.instance.textNode("email-4")); + channelAccount4.setProperties("userPrincipalName", JsonNodeFactory.instance.textNode("userPrincipalName-4")); + channelAccount4.setProperties("tenantId", JsonNodeFactory.instance.textNode("tenantId-4")); + channelAccounts2.add(channelAccount4); // getConversationMembers (Group chat) Mockito.when(mockConversations.getConversationMembers("conversation-id")).thenReturn( - CompletableFuture.completedFuture(new ArrayList() { - { - add(new ChannelAccount() { - { - setId("id-3"); - setName("name-3"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-3") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-3") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-3") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-3")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-3") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-3") - ); - } - }); - add(new ChannelAccount() { - { - setId("id-4"); - setName("name-4"); - setProperties( - "objectId", - JsonNodeFactory.instance.textNode("objectId-4") - ); - setProperties( - "givenName", - JsonNodeFactory.instance.textNode("givenName-4") - ); - setProperties( - "surname", - JsonNodeFactory.instance.textNode("surname-4") - ); - setProperties("email", JsonNodeFactory.instance.textNode("email-4")); - setProperties( - "userPrincipalName", - JsonNodeFactory.instance.textNode("userPrincipalName-4") - ); - setProperties( - "tenantId", - JsonNodeFactory.instance.textNode("tenantId-4") - ); - } - }); - } - }) + CompletableFuture.completedFuture(channelAccounts2) ); ConnectorClient mockConnectorClient = Mockito.mock(ConnectorClient.class); @@ -485,30 +391,25 @@ private static TeamsConnectorClient getTeamsConnectorClient( ) { TeamsOperations mockOperations = Mockito.mock(TeamsOperations.class); + + ConversationList list = new ConversationList(); + ArrayList conversations = new ArrayList(); + conversations.add(new ChannelInfo("channel-id-1")); + conversations.add(new ChannelInfo("channel-id-2", "channel-name-2")); + conversations.add(new ChannelInfo("channel-id-3", "channel-name-3")); + list.setConversations(conversations); // fetchChannelList Mockito.when(mockOperations.fetchChannelList(Mockito.anyString())).thenReturn( - CompletableFuture.completedFuture(new ConversationList() { - { - setConversations(new ArrayList() { - { - add(new ChannelInfo("channel-id-1")); - add(new ChannelInfo("channel-id-2", "channel-name-2")); - add(new ChannelInfo("channel-id-3", "channel-name-3")); - } - }); - } - }) + CompletableFuture.completedFuture(list) ); + TeamDetails details = new TeamDetails(); + details.setId("team-id"); + details.setName("team-name"); + details.setAadGroupId("team-aadgroupid"); // fetchTeamDetails Mockito.when(mockOperations.fetchTeamDetails(Mockito.anyString())).thenReturn( - CompletableFuture.completedFuture(new TeamDetails() { - { - setId("team-id"); - setName("team-name"); - setAadGroupId("team-aadgroupid"); - } - }) + CompletableFuture.completedFuture(details) ); TeamsConnectorClient mockConnectorClient = Mockito.mock(TeamsConnectorClient.class); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ChannelValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ChannelValidation.java index 2446ae67c..25b516bcd 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ChannelValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/ChannelValidation.java @@ -21,24 +21,24 @@ private ChannelValidation() { } /** - * TO BOT FROM CHANNEL: Token validation parameters when connecting to a bot. + * TO BOT FROM CHANNEL. + * @return Token validation parameters when connecting to a bot. */ - public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = - new TokenValidationParameters() { - { - this.validateIssuer = true; - this.validIssuers = new ArrayList() { - { - add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); - } - }; - this.validateAudience = false; - this.validateLifetime = true; - this.clockSkew = - Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); - this.requireSignedTokens = true; - } - }; + public static TokenValidationParameters getTokenValidationParameters() { + TokenValidationParameters tokenValidationParameters = new TokenValidationParameters(); + tokenValidationParameters.validateIssuer = true; + + ArrayList validIssuers = new ArrayList(); + validIssuers.add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); + tokenValidationParameters.validIssuers = validIssuers; + + tokenValidationParameters.validateAudience = false; + tokenValidationParameters.validateLifetime = true; + tokenValidationParameters.clockSkew = Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); + tokenValidationParameters.requireSignedTokens = true; + + return tokenValidationParameters; + } /** * Gets the OpenID metadata URL. @@ -104,7 +104,7 @@ public static CompletableFuture authenticateToken( AuthenticationConfiguration authConfig ) { JwtTokenExtractor tokenExtractor = new JwtTokenExtractor( - TOKENVALIDATIONPARAMETERS, + getTokenValidationParameters(), getOpenIdMetaDataUrl(), AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS ); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java index a9890eeba..02da3a176 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EmulatorValidation.java @@ -20,47 +20,36 @@ private EmulatorValidation() { } /** - * TO BOT FROM EMULATOR: Token validation parameters when connecting to a - * channel. + * TO BOT FROM EMULATOR. + * @return Token validation parameters when connecting to a channel. */ - public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = - new TokenValidationParameters() { - { - this.validateIssuer = true; - this.validIssuers = new ArrayList() { - { - // Auth v3.1, 1.0 - add("https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/"); - - // Auth v3.1, 2.0 - add( - "https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0" - ); - - // Auth v3.2, 1.0 - add("https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/"); - - // Auth v3.2, 2.0 - add( - "https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0" - ); - - // Auth for US Gov, 1.0 - add("https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/"); - - // Auth for US Gov, 2.0 - add( - "https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0" - ); - } - }; - this.validateAudience = false; - this.validateLifetime = true; - this.clockSkew = - Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); - this.requireSignedTokens = true; - } - }; + public static TokenValidationParameters getTokenValidationParameters() { + TokenValidationParameters tokenValidationParameters = new TokenValidationParameters(); + tokenValidationParameters.validateIssuer = true; + + ArrayList validIssuers = new ArrayList(); + // Auth v3.1, 1.0 + validIssuers.add("https://sts.windows.net/d6d49420-f39b-4df7-a1dc-d59a935871db/"); + // Auth v3.1, 2.0 + validIssuers.add("https://login.microsoftonline.com/d6d49420-f39b-4df7-a1dc-d59a935871db/v2.0"); + // Auth v3.2, 1.0 + validIssuers.add("https://sts.windows.net/f8cdef31-a31e-4b4a-93e4-5f571e91255a/"); + // Auth v3.2, 2.0 + validIssuers.add("https://login.microsoftonline.com/f8cdef31-a31e-4b4a-93e4-5f571e91255a/v2.0"); + // Auth for US Gov, 1.0 + validIssuers.add("https://sts.windows.net/cab8a31a-1906-4287-a0d8-4eef66b95f6e/"); + // Auth for US Gov, 2.0 + validIssuers.add("https://login.microsoftonline.us/cab8a31a-1906-4287-a0d8-4eef66b95f6e/v2.0"); + tokenValidationParameters.validIssuers = validIssuers; + + tokenValidationParameters.validateAudience = false; + tokenValidationParameters.validateLifetime = true; + tokenValidationParameters.clockSkew = Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); + + tokenValidationParameters.requireSignedTokens = true; + + return tokenValidationParameters; + } /** * Determines if a given Auth header is from the Bot Framework Emulator. @@ -103,7 +92,7 @@ public static Boolean isTokenFromEmulator(String authHeader) { // Is the token issues by a source we consider to be the emulator? // Not a Valid Issuer. This is NOT a Bot Framework Emulator Token. - return TOKENVALIDATIONPARAMETERS.validIssuers.contains(decodedJWT.getIssuer()); + return getTokenValidationParameters().validIssuers.contains(decodedJWT.getIssuer()); } catch (Throwable t) { return false; } @@ -169,7 +158,7 @@ public static CompletableFuture authenticateToken( : AuthenticationConstants.TO_BOT_FROM_EMULATOR_OPENID_METADATA_URL; JwtTokenExtractor tokenExtractor = new JwtTokenExtractor( - TOKENVALIDATIONPARAMETERS, + getTokenValidationParameters(), openIdMetadataUrl, AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS ); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java index 084f9c43b..70d6f9a51 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/EnterpriseChannelValidation.java @@ -14,22 +14,21 @@ * Enterprise channel auth validation. */ public final class EnterpriseChannelValidation { - private static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = - new TokenValidationParameters() { - { - this.validateIssuer = true; - this.validIssuers = new ArrayList() { - { - add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); - } - }; - this.validateAudience = false; - this.validateLifetime = true; - this.clockSkew = - Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); - this.requireSignedTokens = true; - } - }; + private static TokenValidationParameters getTokenValidationParameters() { + TokenValidationParameters tokenValidationParamaters = new TokenValidationParameters(); + tokenValidationParamaters.validateIssuer = true; + + ArrayList validIssuers = new ArrayList(); + validIssuers.add(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); + tokenValidationParamaters.validIssuers = validIssuers; + + tokenValidationParamaters.validateAudience = false; + tokenValidationParamaters.validateLifetime = true; + tokenValidationParamaters.clockSkew = Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); + tokenValidationParamaters.requireSignedTokens = true; + + return tokenValidationParamaters; + } private EnterpriseChannelValidation() { @@ -99,7 +98,7 @@ public static CompletableFuture authenticateToken( .thenCompose(channelService -> { JwtTokenExtractor tokenExtractor = new JwtTokenExtractor( - TOKENVALIDATIONPARAMETERS, + getTokenValidationParameters(), String.format( AuthenticationConstants.TO_BOT_FROM_ENTERPRISE_CHANNEL_OPENID_METADATA_URL_FORMAT, channelService diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java index 7c1506050..cb2de0305 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/GovernmentChannelValidation.java @@ -17,25 +17,23 @@ public final class GovernmentChannelValidation { GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_OPENID_METADATA_URL; /** - * TO BOT FROM GOVERNMENT CHANNEL: Token validation parameters when connecting - * to a bot. + * TO BOT FROM GOVERNMENT CHANNEL. + * @return Token validation parameters when connecting to a bot. */ - public static final TokenValidationParameters TOKENVALIDATIONPARAMETERS = - new TokenValidationParameters() { - { - this.validateIssuer = true; - this.validIssuers = new ArrayList() { - { - add(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); - } - }; - this.validateAudience = false; - this.validateLifetime = true; - this.clockSkew = - Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); - this.requireSignedTokens = true; - } - }; + public static TokenValidationParameters getTokenValidationParameters() { + TokenValidationParameters tokenValidationParameters = new TokenValidationParameters(); + + ArrayList validIssuers = new ArrayList(); + tokenValidationParameters.validIssuers = validIssuers; + + tokenValidationParameters.validateIssuer = true; + tokenValidationParameters.validateAudience = false; + tokenValidationParameters.validateLifetime = true; + tokenValidationParameters.clockSkew = Duration.ofMinutes(AuthenticationConstants.DEFAULT_CLOCKSKEW_MINUTES); + tokenValidationParameters.requireSignedTokens = true; + + return tokenValidationParameters; + } private GovernmentChannelValidation() { @@ -107,7 +105,7 @@ public static CompletableFuture authenticateToken( AuthenticationConfiguration authConfig ) { JwtTokenExtractor tokenExtractor = new JwtTokenExtractor( - TOKENVALIDATIONPARAMETERS, + getTokenValidationParameters(), getOpenIdMetaDataUrl(), AuthenticationConstants.ALLOWED_SIGNING_ALGORITHMS ); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java index 899c5225a..d98921bff 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryParams.java @@ -23,9 +23,9 @@ public class RetryParams { * @return A RetryParams that returns false for {@link #getShouldRetry()}. */ public static RetryParams stopRetrying() { - return new RetryParams() {{ - setShouldRetry(false); - }}; + RetryParams retryParams = new RetryParams(); + retryParams.setShouldRetry(false); + return retryParams; } /** diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java index db50423ed..4df7754dc 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AllowedCallersClaimsValidationTests.java @@ -35,14 +35,22 @@ public static List>> getConfigureServicesSucceedsData( // Empty allowed callers array resultList.add(Pair.of(null, new ArrayList())); // Allow any caller - resultList.add(Pair.of(primaryAppId, new ArrayList() { { add("*"); } })); + ArrayList anyCaller = new ArrayList(); + anyCaller.add("*"); + resultList.add(Pair.of(primaryAppId, anyCaller)); // Specify allowed caller - resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(primaryAppId); } }))); + ArrayList allowedCaller = new ArrayList(); + allowedCaller.add(primaryAppId); + resultList.add((Pair.of(primaryAppId, allowedCaller))); // Specify multiple callers - resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(primaryAppId); - add(secondaryAppId); } }))); + ArrayList multipleCallers = new ArrayList(); + multipleCallers.add(primaryAppId); + multipleCallers.add(secondaryAppId); + resultList.add((Pair.of(primaryAppId, multipleCallers))); // Blocked caller throws exception - resultList.add((Pair.of(primaryAppId, new ArrayList() { { add(secondaryAppId); } }))); + ArrayList blockedCallers = new ArrayList(); + blockedCallers.add(secondaryAppId); + resultList.add((Pair.of(primaryAppId, blockedCallers))); return resultList; } diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java index 68297d38f..54055707f 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/AttachmentsTest.java @@ -16,16 +16,14 @@ public class AttachmentsTest extends BotConnectorTestBase { @Test public void GetAttachmentInfo() { - AttachmentData attachment = new AttachmentData() {{ - setName("bot-framework.png"); - setType("image/png"); - setOriginalBase64(encodeToBase64(new File(getClass().getClassLoader().getResource("bot-framework.png").getFile()))); - }}; + AttachmentData attachment = new AttachmentData(); + attachment.setName("bot-framework.png"); + attachment.setType("image/png"); + attachment.setOriginalBase64(encodeToBase64(new File(getClass().getClassLoader().getResource("bot-framework.png").getFile()))); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -49,16 +47,14 @@ public void GetAttachment() { e.printStackTrace(); } - AttachmentData attachment = new AttachmentData() {{ - setName("bot_icon.png"); - setType("image/png"); - setOriginalBase64(attachmentPayload); - }}; + AttachmentData attachment = new AttachmentData(); + attachment.setName("bot_icon.png"); + attachment.setType("image/png"); + attachment.setOriginalBase64(attachmentPayload); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java index 9902d8633..416eeb6bd 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/ConversationsTest.java @@ -21,17 +21,15 @@ public class ConversationsTest extends BotConnectorTestBase { @Test public void CreateConversation() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Create Conversation"); - }}; - - ConversationParameters params = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Create Conversation"); + + ConversationParameters params = new ConversationParameters(); + params.setMembers(Collections.singletonList(user)); + params.setBot(bot); + params.setActivity(activity); ConversationResourceResponse result = connector.getConversations().createConversation(params).join(); @@ -41,18 +39,16 @@ public void CreateConversation() { @Test public void CreateConversationWithInvalidBot() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Create Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Create Conversation"); bot.setId("invalid-id"); - ConversationParameters params = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters params = new ConversationParameters(); + params.setMembers(Collections.singletonList(user)); + params.setBot(bot); + params.setActivity(activity); try { ConversationResourceResponse result = connector.getConversations().createConversation(params).join(); @@ -70,17 +66,15 @@ public void CreateConversationWithInvalidBot() { @Test public void CreateConversationWithoutMembers() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Create Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Create Conversation"); - ConversationParameters params = new ConversationParameters() {{ - setMembers(Collections.emptyList()); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters params = new ConversationParameters(); + params.setMembers(Collections.emptyList()); + params.setBot(bot); + params.setActivity(activity); try { ConversationResourceResponse result = connector.getConversations().createConversation(params).join(); @@ -98,17 +92,15 @@ public void CreateConversationWithoutMembers() { @Test public void CreateConversationWithBotMember() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Create Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Create Conversation"); - ConversationParameters params = new ConversationParameters() {{ - setMembers(Collections.singletonList(bot)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters params = new ConversationParameters(); + params.setMembers(Collections.singletonList(bot)); + params.setBot(bot); + params.setActivity(activity); try { ConversationResourceResponse result = connector.getConversations().createConversation(params).join(); @@ -131,10 +123,9 @@ public void CreateConversationWithNullParameter() { @Test public void GetConversationMembers() { - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -153,10 +144,9 @@ public void GetConversationMembers() { @Test public void GetConversationMembersWithInvalidConversationId() { - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -185,10 +175,9 @@ public void GetConversationMembersWithNullConversationId() { @Test public void GetConversationPagedMembers() { - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -210,17 +199,15 @@ public void GetConversationPagedMembers() { @Test public void GetConversationPagedMembersWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Get Activity Members"); - }}; - - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Get Activity Members"); + + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); + createMessage.setActivity(activity); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -239,17 +226,15 @@ public void GetConversationPagedMembersWithInvalidConversationId() { @Test public void SendToConversation() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setName("activity"); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setName("activity"); + activity.setText("TEST Send to Conversation"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -261,17 +246,15 @@ public void SendToConversation() { @Test public void SendToConversationWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setName("activity"); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setName("activity"); + activity.setText("TEST Send to Conversation"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -291,20 +274,18 @@ public void SendToConversationWithInvalidConversationId() { @Test public void SendToConversationWithInvalidBotId() { - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); bot.setId("B21S8SG7K:T03CWQ0QB"); - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setName("activity"); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setName("activity"); + activity.setText("TEST Send to Conversation"); try { ResourceResponse response = connector.getConversations().sendToConversation(conversation.getId(), activity).join(); @@ -321,11 +302,10 @@ public void SendToConversationWithInvalidBotId() { @Test public void SendToConversationWithNullConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Send to Conversation with null conversation id"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Send to Conversation with null conversation id"); try { connector.getConversations().sendToConversation(null, activity).join(); @@ -349,39 +329,34 @@ public void SendToConversationWithNullActivity() { @Test public void SendCardToConversation() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setName("activity"); - setText("TEST Send Card to Conversation"); - setAttachments(Arrays.asList( - new Attachment() {{ - setContentType("application/vnd.microsoft.card.hero"); - setContent(new HeroCard() {{ - setTitle("A static image"); - setSubtitle("JPEG image"); - setImages(Collections.singletonList(new CardImage() {{ - setUrl("https://docs.microsoft.com/en-us/bot-framework/media/designing-bots/core/dialogs-screens.png"); - }})); - }}); - }}, - new Attachment() {{ - setContentType("application/vnd.microsoft.card.hero"); - setContent(new HeroCard() {{ - setTitle("An animation"); - setSubtitle("GIF image"); - setImages(Collections.singletonList(new CardImage() {{ - setUrl("http://i.giphy.com/Ki55RUbOV5njy.gif"); - }})); - }}); - }} - )); - }}; - - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setName("activity"); + activity.setText("TEST Send Card to Conversation"); + CardImage imageJPEG = new CardImage(); + imageJPEG.setUrl("https://docs.microsoft.com/en-us/bot-framework/media/designing-bots/core/dialogs-screens.png"); + HeroCard heroCardJPEG = new HeroCard(); + heroCardJPEG.setTitle("A static image"); + heroCardJPEG.setSubtitle("JPEG image"); + heroCardJPEG.setImages(Collections.singletonList(imageJPEG)); + Attachment attachmentJPEG = new Attachment(); + attachmentJPEG.setContentType("application/vnd.microsoft.card.hero"); + attachmentJPEG.setContent(heroCardJPEG); + + CardImage imageGIF = new CardImage(); + imageGIF.setUrl("http://i.giphy.com/Ki55RUbOV5njy.gif"); + HeroCard heroCardGIF = new HeroCard(); + heroCardGIF.setTitle("An animation"); + heroCardGIF.setSubtitle("GIF image"); + heroCardGIF.setImages(Collections.singletonList(imageGIF)); + Attachment attachmentGIF = new Attachment(); + attachmentGIF.setContentType("application/vnd.microsoft.card.hero"); + attachmentGIF.setContent(heroCardGIF); + activity.setAttachments(Arrays.asList(attachmentJPEG, attachmentGIF)); + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -393,17 +368,15 @@ public void SendCardToConversation() { @Test public void GetActivityMembers() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Get Activity Members"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Get Activity Members"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); + createMessage.setActivity(activity); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -422,17 +395,15 @@ public void GetActivityMembers() { @Test public void GetActivityMembersWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Get Activity Members"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Get Activity Members"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); + createMessage.setActivity(activity); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -472,22 +443,19 @@ public void GetActivityMembersWithNullActivityId() { @Test public void ReplyToActivity() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Send to Conversation"); - Activity reply = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Reply to Activity"); - }}; + Activity reply = new Activity(ActivityTypes.MESSAGE); + reply.setRecipient(user); + reply.setFrom(bot); + reply.setText("TEST Reply to Activity"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -501,22 +469,19 @@ public void ReplyToActivity() { @Test public void ReplyToActivityWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Send to Conversation"); - Activity reply = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Reply to Activity"); - }}; + Activity reply = new Activity(ActivityTypes.MESSAGE); + reply.setRecipient(user); + reply.setFrom(bot); + reply.setText("TEST Reply to Activity"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -537,11 +502,10 @@ public void ReplyToActivityWithInvalidConversationId() { @Test public void ReplyToActivityWithNullConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Reply activity with null conversation id"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Reply activity with null conversation id"); try { connector.getConversations().replyToActivity(null, "id", activity).join(); @@ -553,11 +517,10 @@ public void ReplyToActivityWithNullConversationId() { @Test public void ReplyToActivityWithNullActivityId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Reply activity with null activity id"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Reply activity with null activity id"); try { connector.getConversations().replyToActivity("id", null, activity).join(); @@ -579,16 +542,14 @@ public void ReplyToActivityWithNullActivity() { @Test public void ReplyToActivityWithNullReply() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Reply activity with null reply"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Reply activity with null reply"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -605,17 +566,15 @@ public void ReplyToActivityWithNullReply() { @Test public void DeleteActivity() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Delete Activity"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Delete Activity"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); + createMessage.setActivity(activity); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -627,17 +586,15 @@ public void DeleteActivity() { @Test public void DeleteActivityWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Delete Activity"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Delete Activity"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - setActivity(activity); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); + createMessage.setActivity(activity); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -677,16 +634,14 @@ public void DeleteActivityWithNullActivityId() { @Test public void UpdateActivity() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Send to Conversation"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -703,16 +658,14 @@ public void UpdateActivity() { @Test public void UpdateActivityWithInvalidConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Send to Conversation"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Send to Conversation"); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); @@ -736,11 +689,10 @@ public void UpdateActivityWithInvalidConversationId() { @Test public void UpdateActivityWithNullConversationId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Activity to be updated with null conversation Id"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Activity to be updated with null conversation Id"); try { connector.getConversations().updateActivity(null, "id", activity).join(); @@ -752,11 +704,10 @@ public void UpdateActivityWithNullConversationId() { @Test public void UpdateActivityWithNullActivityId() { - Activity activity = new Activity(ActivityTypes.MESSAGE) {{ - setRecipient(user); - setFrom(bot); - setText("TEST Activity to be updated with null activity Id"); - }}; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setRecipient(user); + activity.setFrom(bot); + activity.setText("TEST Activity to be updated with null activity Id"); try { connector.getConversations().updateActivity("id", null, activity).join(); @@ -779,16 +730,14 @@ public void UpdateActivityWithNullActivity() { @Test public void UploadAttachment() { - AttachmentData attachment = new AttachmentData() {{ - setName("bot-framework.png"); - setType("image/png"); - setOriginalBase64(encodeToBase64(new File(getClass().getClassLoader().getResource("bot-framework.png").getFile()))); - }}; + AttachmentData attachment = new AttachmentData(); + attachment.setName("bot-framework.png"); + attachment.setType("image/png"); + attachment.setOriginalBase64(encodeToBase64(new File(getClass().getClassLoader().getResource("bot-framework.png").getFile()))); - ConversationParameters createMessage = new ConversationParameters() {{ - setMembers(Collections.singletonList(user)); - setBot(bot); - }}; + ConversationParameters createMessage = new ConversationParameters(); + createMessage.setMembers(Collections.singletonList(user)); + createMessage.setBot(bot); ConversationResourceResponse conversation = connector.getConversations().createConversation(createMessage).join(); diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java index 96f6b1bdd..609b64843 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenExtractorTests.java @@ -54,9 +54,9 @@ public class JwtTokenExtractorTests { @Before public void setup() throws GeneralSecurityException, IOException { - ChannelValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false; - EmulatorValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false; - GovernmentChannelValidation.TOKENVALIDATIONPARAMETERS.validateLifetime = false; + ChannelValidation.getTokenValidationParameters().validateLifetime = false; + EmulatorValidation.getTokenValidationParameters().validateLifetime = false; + GovernmentChannelValidation.getTokenValidationParameters().validateLifetime = false; valid = loadCert("bot-connector.pkcs12"); expired = loadCert("bot-connector-expired.pkcs12"); @@ -132,26 +132,26 @@ private static String createTokenForCertificate(X509Certificate cert, PrivateKey private static TokenValidationParameters createTokenValidationParameters(X509Certificate cert) { - return new TokenValidationParameters() {{ - validateIssuer = false; - validIssuers = Collections.singletonList(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); - - // Audience validation takes place in JwtTokenExtractor - validateAudience = false; - validateLifetime = true; - clockSkew = Duration.ofMinutes(5); - requireSignedTokens = true; - - // provide a custom resolver so that calls to openid won't happen (which wouldn't - // work for these tests). - issuerSigningKeyResolver = key -> (OpenIdMetadata) keyId -> { - // return our certificate data - OpenIdMetadataKey key1 = new OpenIdMetadataKey(); - key1.key = (RSAPublicKey) cert.getPublicKey(); - key1.certificateChain = Collections.singletonList(encodeCertificate(cert)); - return key1; - }; - }}; + TokenValidationParameters parameters = new TokenValidationParameters(); + parameters.validateIssuer = false; + parameters.validIssuers = Collections.singletonList(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER); + + // Audience validation takes place in JwtTokenExtractor + parameters.validateAudience = false; + parameters.validateLifetime = true; + parameters.clockSkew = Duration.ofMinutes(5); + parameters.requireSignedTokens = true; + + // provide a custom resolver so that calls to openid won't happen (which wouldn't + // work for these tests). + parameters.issuerSigningKeyResolver = key -> (OpenIdMetadata) keyId -> { + // return our certificate data + OpenIdMetadataKey key1 = new OpenIdMetadataKey(); + key1.key = (RSAPublicKey) cert.getPublicKey(); + key1.certificateChain = Collections.singletonList(encodeCertificate(cert)); + return key1; + }; + return parameters; } private static class CertInfo { @@ -166,12 +166,12 @@ private static CertInfo loadCert(String pkcs12File) KeyStore p12 = KeyStore.getInstance("pkcs12"); p12.load(fis, "botframework".toCharArray()); - return new CertInfo() {{ - cert = (X509Certificate) p12.getCertificate("bot-connector-pkcs12"); - keypair = new KeyPair(cert.getPublicKey(), - (PrivateKey) p12.getKey("bot-connector-pkcs12", "botframework".toCharArray()) - ); - }}; + CertInfo certInfo = new CertInfo(); + certInfo.cert = (X509Certificate) p12.getCertificate("bot-connector-pkcs12"); + certInfo.keypair = new KeyPair(certInfo.cert.getPublicKey(), + (PrivateKey) p12.getKey("bot-connector-pkcs12", "botframework".toCharArray()) + ); + return certInfo; } private static String encodeCertificate(Certificate certificate) { diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java index 90b5485d8..7b307e5eb 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java @@ -5,6 +5,7 @@ import com.microsoft.bot.connector.authentication.*; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.ConversationReference; import com.microsoft.bot.schema.RoleTypes; @@ -161,10 +162,10 @@ public void Emulator_AuthHeader_CorrectAppIdAndServiceUrl_WithPrivateChannelServ public void ChannelMsaHeaderValidServiceUrlShouldBeTrusted() throws IOException, ExecutionException, InterruptedException { String header = getHeaderToken(); CredentialProvider credentials = new SimpleCredentialProvider(APPID, ""); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"); JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -181,10 +182,10 @@ public void ChannelMsaHeaderInvalidServiceUrlShouldNotBeTrusted() throws IOExcep CredentialProvider credentials = new SimpleCredentialProvider("7f74513e-6f96-4dbc-be9d-9a81fea22b88", ""); try { + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://webchat.botframework.com/"); JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://webchat.botframework.com/"); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -203,10 +204,10 @@ public void ChannelAuthenticationDisabledShouldBeAnonymous() throws ExecutionExc String header = ""; CredentialProvider credentials = new SimpleCredentialProvider("", ""); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://webchat.botframework.com/"); ClaimsIdentity identity = JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://webchat.botframework.com/"); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -221,13 +222,15 @@ public void ChannelAuthenticationDisabledAndSkillShouldBeAnonymous() throws Exec String header = ""; CredentialProvider credentials = new SimpleCredentialProvider("", ""); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://webchat.botframework.com/"); + activity.setChannelId(Channels.EMULATOR); + activity.setRelatesTo(new ConversationReference()); + ChannelAccount skillAccount = new ChannelAccount(); + skillAccount.setRole(RoleTypes.SKILL); + activity.setRecipient(skillAccount); ClaimsIdentity identity = JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://webchat.botframework.com/"); - setChannelId(Channels.EMULATOR); - setRelatesTo(new ConversationReference()); - setRecipient(new ChannelAccount() { { setRole(RoleTypes.SKILL); } }); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -241,10 +244,10 @@ public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException try { String header = ""; CredentialProvider credentials = new SimpleCredentialProvider(APPID, APPPASSWORD); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"); JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -264,10 +267,10 @@ public void ChannelAuthenticationDisabledServiceUrlShouldNotBeTrusted() throws E String header = ""; CredentialProvider credentials = new SimpleCredentialProvider("", ""); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setServiceUrl("https://webchat.botframework.com/"); ClaimsIdentity identity = JwtTokenValidation.authenticateRequest( - new Activity() {{ - setServiceUrl("https://webchat.botframework.com/"); - }}, + activity, header, credentials, new SimpleChannelProvider()).join(); @@ -280,10 +283,9 @@ public void EnterpriseChannelValidation_Succeeds() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -299,10 +301,9 @@ public void EnterpriseChannelValidation_NoAuthentication_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(null, claims); try { @@ -319,9 +320,8 @@ public void EnterpriseChannelValidation_NoAudienceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -338,10 +338,9 @@ public void EnterpriseChannelValidation_NoAudienceClaimValue_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, ""); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, ""); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -358,10 +357,9 @@ public void EnterpriseChannelValidation_WrongAudienceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, "abc"); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, "abc"); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -378,9 +376,8 @@ public void EnterpriseChannelValidation_NoServiceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -397,10 +394,9 @@ public void EnterpriseChannelValidation_NoServiceClaimValue_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, ""); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, ""); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -417,10 +413,9 @@ public void EnterpriseChannelValidation_WrongServiceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, null); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, "other"); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, "other"); ClaimsIdentity identity = new ClaimsIdentity(AuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -437,10 +432,9 @@ public void GovernmentChannelValidation_Succeeds() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -456,10 +450,9 @@ public void GovernmentChannelValidation_NoAuthentication_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(null, claims); try { @@ -476,9 +469,8 @@ public void GovernmentChannelValidation_NoAudienceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -495,10 +487,9 @@ public void GovernmentChannelValidation_NoAudienceClaimValue_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, ""); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, ""); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -515,10 +506,9 @@ public void GovernmentChannelValidation_WrongAudienceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, "abc"); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, "abc"); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -535,10 +525,9 @@ public void GovernmentChannelValidation_WrongAudienceClaimIssuer_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, serviceUrl); ClaimsIdentity identity = new ClaimsIdentity("https://wrongissuer.com", claims); try { @@ -555,9 +544,8 @@ public void GovernmentChannelValidation_NoServiceClaim_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -574,10 +562,9 @@ public void GovernmentChannelValidation_NoServiceClaimValue_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, ""); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, ""); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { @@ -594,10 +581,9 @@ public void GovernmentChannelValidation_WrongServiceClaimValue_Fails() { String serviceUrl = "https://webchat.botframework.com/"; CredentialProvider credentials = new SimpleCredentialProvider(appId, ""); - Map claims = new HashMap() {{ - put(AuthenticationConstants.AUDIENCE_CLAIM, appId); - put(AuthenticationConstants.SERVICE_URL_CLAIM, "other"); - }}; + Map claims = new HashMap(); + claims.put(AuthenticationConstants.AUDIENCE_CLAIM, appId); + claims.put(AuthenticationConstants.SERVICE_URL_CLAIM, "other"); ClaimsIdentity identity = new ClaimsIdentity(GovernmentAuthenticationConstants.TO_BOT_FROM_CHANNEL_TOKEN_ISSUER, claims); try { diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java index 9d8b9b4c0..f40de129b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryTests.java @@ -15,9 +15,8 @@ public class RetryTests { @Test public void Retry_NoRetryWhenTaskSucceeds() { - FaultyClass faultyClass = new FaultyClass() {{ - exceptionToThrow = null; - }}; + FaultyClass faultyClass = new FaultyClass(); + faultyClass.exceptionToThrow = null; Retry.run(() -> faultyClass.faultyTask(), @@ -30,10 +29,9 @@ public void Retry_NoRetryWhenTaskSucceeds() { @Test public void Retry_RetryThenSucceed() { - FaultyClass faultyClass = new FaultyClass() {{ - exceptionToThrow = new IllegalArgumentException(); - triesUntilSuccess = 3; - }}; + FaultyClass faultyClass = new FaultyClass(); + faultyClass.exceptionToThrow = new IllegalArgumentException(); + faultyClass.triesUntilSuccess = 3; Retry.run(() -> faultyClass.faultyTask(), @@ -46,10 +44,9 @@ public void Retry_RetryThenSucceed() { @Test public void Retry_RetryUntilFailure() { - FaultyClass faultyClass = new FaultyClass() {{ - exceptionToThrow = new IllegalArgumentException(); - triesUntilSuccess = 12; - }}; + FaultyClass faultyClass = new FaultyClass(); + faultyClass.exceptionToThrow = new IllegalArgumentException(); + faultyClass.triesUntilSuccess = 12; try { Retry.run(() -> diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 0c282e9c8..277f50e85 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -495,11 +495,10 @@ public CompletableFuture emitEvent(String name) { */ public CompletableFuture emitEvent(String name, Object value, boolean bubble, boolean fromLeaf) { // Initialize event - DialogEvent dialogEvent = new DialogEvent() {{ - setBubble(bubble); - setName(name); - setValue(value); - }}; + DialogEvent dialogEvent = new DialogEvent(); + dialogEvent.setBubble(bubble); + dialogEvent.setName(name); + dialogEvent.setValue(value); DialogContext dc = this; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java index 1861eb224..dd00ab798 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Recognizer.java @@ -160,14 +160,18 @@ protected static RecognizerResult createChooseIntentResult(Map choices, String text) { * @return The created Activity. */ public static Activity heroCard(List choices, String text, String speak) { - HeroCard card = new HeroCard() { - { - setText(text); - setButtons(extractActions(choices)); - } - }; + HeroCard card = new HeroCard(); + card.setText(text); + card.setButtons(extractActions(choices)); List attachments = new ArrayList() { /** @@ -398,13 +395,11 @@ private static List extractActions(List choices) { return choice.getAction(); } - return new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue(choice.getValue()); - setTitle(choice.getValue()); - } - }; + CardAction card = new CardAction(); + card.setType(ActionTypes.IM_BACK); + card.setValue(choice.getValue()); + card.setTitle(choice.getValue()); + return card; }).collect(Collectors.toList()); } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java index e8709fb73..f0e950a50 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/ChoiceRecognizers.java @@ -109,17 +109,17 @@ private static void matchChoiceByIndex( int index = Integer.parseInt(value) - 1; if (index >= 0 && index < list.size()) { Choice choice = list.get(index); - matched.add(new ModelResult() {{ - setStart(match.getStart()); - setEnd(match.getEnd()); - setTypeName("choice"); - setText(match.getText()); - setResolution(new FoundChoice() {{ - setValue(choice.getValue()); - setIndex(index); - setScore(1.0f); - }}); - }}); + FoundChoice resolution = new FoundChoice(); + resolution.setValue(choice.getValue()); + resolution.setIndex(index); + resolution.setScore(1.0f); + ModelResult modelResult = new ModelResult(); + modelResult.setStart(match.getStart()); + modelResult.setEnd(match.getEnd()); + modelResult.setTypeName("choice"); + modelResult.setText(match.getText()); + modelResult.setResolution(resolution); + matched.add(modelResult); } } catch (Throwable ignored) { // noop here, as in dotnet/node repos @@ -128,14 +128,15 @@ private static void matchChoiceByIndex( private static List> recognizeNumbers(String utterance, IModel model) { List result = model.parse(utterance == null ? "" : utterance); - return result.stream().map(r -> - new ModelResult() {{ - setStart(r.start); - setEnd(r.end); - setText(r.text); - setResolution(new FoundChoice() {{ - setValue(r.resolution.get("value").toString()); - }}); - }}).collect(Collectors.toList()); + return result.stream().map(r -> { + FoundChoice resolution = new FoundChoice(); + resolution.setValue(r.resolution.get("value").toString()); + ModelResult modelResult = new ModelResult(); + modelResult.setStart(r.start); + modelResult.setEnd(r.end); + modelResult.setText(r.text); + modelResult.setResolution(resolution); + return modelResult; + }).collect(Collectors.toList()); } } diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java index c638003f1..e081c8d71 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/choices/Find.java @@ -104,18 +104,18 @@ public static List> findChoices( return findValues(utterance, synonyms, options).stream().map(v -> { Choice choice = choices.get(v.getResolution().getIndex()); - return new ModelResult() {{ - setStart(v.getStart()); - setEnd(v.getEnd()); - setTypeName("choice"); - setText(v.getText()); - setResolution(new FoundChoice() {{ - setValue(choice.getValue()); - setIndex(v.getResolution().getIndex()); - setScore(v.getResolution().getScore()); - setSynonym(v.getResolution().getValue()); - }}); - }}; + FoundChoice resolution = new FoundChoice(); + resolution.setValue(choice.getValue()); + resolution.setIndex(v.getResolution().getIndex()); + resolution.setScore(v.getResolution().getScore()); + resolution.setSynonym(v.getResolution().getValue()); + ModelResult modelResult = new ModelResult(); + modelResult.setStart(v.getStart()); + modelResult.setEnd(v.getEnd()); + modelResult.setTypeName("choice"); + modelResult.setText(v.getText()); + modelResult.setResolution(resolution); + return modelResult; }).collect(Collectors.toList()); } @@ -295,15 +295,15 @@ private static ModelResult matchValue( float score = completeness * accuracy; // Format result + FoundValue resolution = new FoundValue(); + resolution.setValue(value); + resolution.setIndex(index); + resolution.setScore(score); result = new ModelResult<>(); result.setStart(start); result.setEnd(end); result.setTypeName("value"); - result.setResolution(new FoundValue() {{ - setValue(value); - setIndex(index); - setScore(score); - }}); + result.setResolution(resolution); } return result; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java index e9c411d0c..a8775435c 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ChoicePrompt.java @@ -75,15 +75,12 @@ public ChoicePrompt(String dialogId, PromptValidator validator, Str choiceDefaults = new HashMap(); for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { - choiceDefaults.put(model.getLocale(), new ChoiceFactoryOptions() { - { - setInlineSeparator(model.getSeparator()); - setInlineOr(model.getInlineOr()); - setInlineOrMore(model.getInlineOrMore()); - setIncludeNumbers(true); - } - } - ); + ChoiceFactoryOptions options = new ChoiceFactoryOptions(); + options.setInlineSeparator(model.getSeparator()); + options.setInlineOr(model.getInlineOr()); + options.setInlineOrMore(model.getInlineOrMore()); + options.setIncludeNumbers(true); + choiceDefaults.put(model.getLocale(), options); } this.style = ListStyle.AUTO; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java index 94bdf0805..6352fe8e9 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/ConfirmPrompt.java @@ -79,14 +79,11 @@ public ConfirmPrompt(String dialogId, PromptValidator validator, String for (PromptCultureModel model : PromptCultureModels.getSupportedCultures()) { Choice yesChoice = new Choice(model.getYesInLanguage()); Choice noChoice = new Choice(model.getNoInLanguage()); - ChoiceFactoryOptions factoryOptions = new ChoiceFactoryOptions() { - { - setInlineSeparator(model.getSeparator()); - setInlineOr(model.getInlineOr()); - setInlineOrMore(model.getInlineOrMore()); - setIncludeNumbers(true); - } - }; + ChoiceFactoryOptions factoryOptions = new ChoiceFactoryOptions(); + factoryOptions.setInlineSeparator(model.getSeparator()); + factoryOptions.setInlineOr(model.getInlineOr()); + factoryOptions.setInlineOrMore(model.getInlineOrMore()); + factoryOptions.setIncludeNumbers(true); choiceDefaults.put(model.getLocale(), new Triplet(yesChoice, @@ -312,12 +309,9 @@ protected CompletableFuture> onRecognize(TurnCon // The text may be a number in which case we will interpret that as a choice. Pair confirmedChoices = confirmChoices != null ? confirmChoices : new Pair(defaults.getValue0(), defaults.getValue1()); - ArrayList choices = new ArrayList() { - { - add(confirmedChoices.getValue0()); - add(confirmedChoices.getValue1()); - } - }; + ArrayList choices = new ArrayList(); + choices.add(confirmedChoices.getValue0()); + choices.add(confirmedChoices.getValue1()); List> secondAttemptResults = ChoiceRecognizers.recognizeChoices(utterance, choices); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java index f5fc8347f..e7b8278cc 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/prompts/OAuthPrompt.java @@ -407,11 +407,11 @@ public static CompletableFuture> recognize result.setSucceeded(true); TokenResponse finalResponse = tokenExchangeResponse; - result.setValue(new TokenResponse() { { - setChannelId(finalResponse.getChannelId()); - setConnectionName(finalResponse.getConnectionName()); - setToken(finalResponse.getToken()); - }}); + TokenResponse response = new TokenResponse(); + response.setChannelId(finalResponse.getChannelId()); + response.setConnectionName(finalResponse.getConnectionName()); + response.setToken(finalResponse.getToken()); + result.setValue(response); } } } else if (turnContext.getActivity().getType().equals(ActivityTypes.MESSAGE)) { diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java index cb6221dcf..97d63d07c 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/ObjectPathTests.java @@ -233,16 +233,16 @@ public void anonymous_PartialOverlay() throws NoSuchFieldException, IllegalAcces @Test public void jsonNode_OnlyDefaultTest() { - JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ - lastName = "Smith"; - firstName = "Fred"; - age = 22; - bool = true; - location = new Location() {{ - latitude = 1.2312312F; - longitude = 3.234234F; - }}; - }}); + Options options = new Options(); + options.lastName = "Smith"; + options.firstName = "Fred"; + options.age = 22; + options.bool = true; + Location location = new Location(); + location.latitude = 1.2312312F; + location.longitude = 3.234234F; + options.location = location; + JsonNode defaultOptions = Serialization.objectToTree(options); JsonNode overlay = Serialization.objectToTree(new Options()); @@ -262,16 +262,16 @@ public void jsonNode_OnlyDefaultTest() { public void jsonNode_OnlyOverlay() { JsonNode defaultOptions = Serialization.objectToTree(new Options()); - JsonNode overlay = Serialization.objectToTree(new Options() {{ - lastName = "Smith"; - firstName = "Fred"; - age = 22; - bool = true; - location = new Location() {{ - latitude = 1.2312312F; - longitude = 3.234234F; - }}; - }}); + Options options = new Options(); + options.lastName = "Smith"; + options.firstName = "Fred"; + options.age = 22; + options.bool = true; + Location location = new Location(); + location.latitude = 1.2312312F; + location.longitude = 3.234234F; + options.location = location; + JsonNode overlay = Serialization.objectToTree(options); Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); @@ -288,27 +288,27 @@ public void jsonNode_OnlyOverlay() { @Test public void jsonNode_FullOverlay() { - JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ - lastName = "Smith"; - firstName = "Fred"; - age = 22; - bool = true; - location = new Location() {{ - latitude = 1.2312312F; - longitude = 3.234234F; - }}; - }}); - - JsonNode overlay = Serialization.objectToTree(new Options() {{ - lastName = "Grant"; - firstName = "Eddit"; - age = 32; - bool = false; - location = new Location() {{ - latitude = 2.2312312F; - longitude = 2.234234F; - }}; - }}); + Options defaultOpts = new Options(); + defaultOpts.lastName = "Smith"; + defaultOpts.firstName = "Fred"; + defaultOpts.age = 22; + defaultOpts.bool = true; + Location defaultLocation = new Location(); + defaultLocation.latitude = 1.2312312F; + defaultLocation.longitude = 3.234234F; + defaultOpts.location = defaultLocation; + JsonNode defaultOptions = Serialization.objectToTree(defaultOpts); + + Options overlayOpts = new Options(); + overlayOpts.lastName = "Grant"; + overlayOpts.firstName = "Eddit"; + overlayOpts.age = 32; + overlayOpts.bool = false; + Location overlayLocation = new Location(); + overlayLocation.latitude = 2.2312312F; + overlayLocation.longitude = 2.234234F; + overlayOpts.location = overlayLocation; + JsonNode overlay = Serialization.objectToTree(overlayOpts); Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); @@ -325,20 +325,20 @@ public void jsonNode_FullOverlay() { @Test public void jsonNode_PartialOverlay() { - JsonNode defaultOptions = Serialization.objectToTree(new Options() {{ - lastName = "Smith"; - firstName = "Fred"; - age = 22; - bool = true; - location = new Location() {{ - latitude = 1.2312312F; - longitude = 3.234234F; - }}; - }}); - - JsonNode overlay = Serialization.objectToTree(new Options() {{ - lastName = "Grant"; - }}); + Options defaultOpts = new Options(); + defaultOpts.lastName = "Smith"; + defaultOpts.firstName = "Fred"; + defaultOpts.age = 22; + defaultOpts.bool = true; + Location defaultLocation = new Location(); + defaultLocation.latitude = 1.2312312F; + defaultLocation.longitude = 3.234234F; + defaultOpts.location = defaultLocation; + JsonNode defaultOptions = Serialization.objectToTree(defaultOpts); + + Options overlayOpts = new Options(); + overlayOpts.lastName = "Grant"; + JsonNode overlay = Serialization.objectToTree(overlayOpts); Options result = ObjectPath.assign(defaultOptions, overlay, Options.class); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java index 03cb1e993..b12c70b6f 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoiceFactoryTests.java @@ -54,7 +54,10 @@ public void shouldRenderChoicesAsAList() { @Test public void shouldRenderUnincludedNumbersChoicesAsAList() { - Activity activity = ChoiceFactory.list(colorChoices, "select from:", null, new ChoiceFactoryOptions() {{ setIncludeNumbers(false); }}); + ChoiceFactoryOptions choiceFactoryOptions = new ChoiceFactoryOptions(); + choiceFactoryOptions.setIncludeNumbers(false); + + Activity activity = ChoiceFactory.list(colorChoices, "select from:", null, choiceFactoryOptions); Assert.assertEquals("select from:\n\n - red\n - green\n - blue", activity.getText()); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java index 82ac40997..34d9aed62 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesChannelTests.java @@ -9,6 +9,7 @@ import com.microsoft.bot.connector.Channels; import com.microsoft.bot.connector.authentication.SimpleCredentialProvider; import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; import org.junit.Assert; import org.junit.Test; @@ -117,7 +118,8 @@ public void shouldReturnFalseForHasMessageFeedWithCortana() { @Test public void shouldReturnChannelIdFromContextActivity() { - Activity testActivity = new Activity() {{ setChannelId(Channels.FACEBOOK); }}; + Activity testActivity = new Activity(ActivityTypes.MESSAGE); + testActivity.setChannelId(Channels.FACEBOOK); TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); String channelId = Channel.getChannelId(testContext); Assert.assertEquals(Channels.FACEBOOK, channelId); @@ -125,7 +127,8 @@ public void shouldReturnChannelIdFromContextActivity() { @Test public void shouldReturnEmptyFromContextActivityMissingChannel() { - Activity testActivity = new Activity() {{ setChannelId(null); }}; + Activity testActivity = new Activity(ActivityTypes.MESSAGE); + testActivity.setChannelId(null); TurnContext testContext = new TurnContextImpl(new BotFrameworkAdapter(new SimpleCredentialProvider()), testActivity); String channelId = Channel.getChannelId(testContext); Assert.assertNull(channelId); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java index a55b4d31f..dbc9a6865 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/choices/ChoicesRecognizerTests.java @@ -72,7 +72,9 @@ public void shouldFindMultipleValuesThatOverlap() { @Test public void shouldCorrectlyDisambiguateBetweenVerySimilarValues() { - List> found = Find.findValues("option B", similarValues, new FindChoicesOptions() {{ setAllowPartialMatches(true);}}); + FindChoicesOptions options = new FindChoicesOptions(); + options.setAllowPartialMatches(true); + List> found = Find.findValues("option B", similarValues, options); Assert.assertEquals(1, found.size()); assertValue(found.get(0), "option B", 1, 1.0f); } @@ -169,19 +171,24 @@ public void shouldAcceptNullUtteranceInRecognizeChoices() { @Test public void shouldNOTFindAChoiceInAnUtteranceByOrdinalPosition_RecognizeOrdinalsFalseAndRecognizeNumbersFalse() { + FindChoicesOptions options = new FindChoicesOptions(); + options.setRecognizeOrdinals(false); + options.setRecognizeNumbers(false); List> found = ChoiceRecognizers.recognizeChoicesFromStrings( "the first one please.", colorChoices, - new FindChoicesOptions() {{ setRecognizeOrdinals(false); setRecognizeNumbers(false);}}); + options); Assert.assertEquals(0, found.size()); } @Test public void shouldNOTFindAChoiceInAnUtteranceByNumericalIndex_Text_RecognizeNumbersFalse() { + FindChoicesOptions options = new FindChoicesOptions(); + options.setRecognizeNumbers(false); List> found = ChoiceRecognizers.recognizeChoicesFromStrings( "one", colorChoices, - new FindChoicesOptions() {{ setRecognizeNumbers(false);}}); + options); Assert.assertEquals(0, found.size()); } @@ -215,4 +222,4 @@ private static void assertChoice(ModelResult result, String value, Assert.assertEquals(synonym, resolution.getSynonym()); } } -} \ No newline at end of file +} diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java index 85ba09df3..adf0fdf57 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ActivityPromptTests.java @@ -65,7 +65,8 @@ public void BasicActivityPrompt() { dialogs.add(eventPrompt); // Create mock Activity for testing. - Activity eventActivity = new Activity() { { setType(ActivityTypes.EVENT); setValue(2); }}; + Activity eventActivity = new Activity(ActivityTypes.EVENT); + eventActivity.setValue(2); BotCallbackHandler botLogic = (turnContext -> { DialogContext dc = dialogs.createContext(turnContext).join(); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java index 38bc0e342..cf14f7d04 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/ChoicePromptTests.java @@ -209,6 +209,19 @@ public void ShouldSendPromptUsingSuggestedActions() { dialogs.add(listPrompt); + CardAction red = new CardAction(); + red.setType(ActionTypes.IM_BACK); + red.setValue("red"); + red.setTitle("red"); + CardAction green = new CardAction(); + green.setType(ActionTypes.IM_BACK); + green.setValue("green"); + green.setTitle("green"); + CardAction blue = new CardAction(); + blue.setType(ActionTypes.IM_BACK); + blue.setValue("blue"); + blue.setTitle("blue"); + CardAction[] suggestedActions = new CardAction[] { red, green, blue }; new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); DialogTurnResult results = dc.continueDialog().join(); @@ -222,31 +235,7 @@ public void ShouldSendPromptUsingSuggestedActions() { return CompletableFuture.completedFuture(null); }) .send("hello") - .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new - CardAction[] - { - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("red"); - setTitle("red"); - } - }, - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("green"); - setTitle("green"); - } - }, - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("blue"); - setTitle("blue"); - } - } - }))) + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(suggestedActions))) .startTest() .join(); } @@ -266,6 +255,26 @@ public void ShouldSendPromptUsingHeroCard() { dialogs.add(listPrompt); + CardAction red = new CardAction(); + red.setType(ActionTypes.IM_BACK); + red.setValue("red"); + red.setTitle("red"); + CardAction green = new CardAction(); + green.setType(ActionTypes.IM_BACK); + green.setValue("green"); + green.setTitle("green"); + CardAction blue = new CardAction(); + blue.setType(ActionTypes.IM_BACK); + blue.setValue("blue"); + blue.setTitle("blue"); + ArrayList buttons = new ArrayList(); + buttons.add(red); + buttons.add(green); + buttons.add(blue); + HeroCard card = new HeroCard(); + card.setText("favorite color?"); + card.setButtons(buttons); + new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); DialogTurnResult results = dc.continueDialog().join(); @@ -279,37 +288,7 @@ public void ShouldSendPromptUsingHeroCard() { return CompletableFuture.completedFuture(null); }) .send("hello") - .assertReply(new Validators().validateHeroCard( - new HeroCard() { - { - setText("favorite color?"); - setButtons(new ArrayList() { - { - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("red"); - setTitle("red"); - } - }); - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("green"); - setTitle("green"); - } - }); - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("blue"); - setTitle("blue"); - } - }); - } - }); - } - }, 0)) + .assertReply(new Validators().validateHeroCard(card, 0)) .startTest().join(); } @@ -327,6 +306,26 @@ public void ShouldSendPromptUsingAppendedHeroCard() { listPrompt.setStyle(ListStyle.HEROCARD); dialogs.add(listPrompt); + HeroCard card = new HeroCard(); + card.setText("favorite color?"); + CardAction red = new CardAction(); + red.setType(ActionTypes.IM_BACK); + red.setValue("red"); + red.setTitle("red"); + CardAction green = new CardAction(); + green.setType(ActionTypes.IM_BACK); + green.setValue("green"); + green.setTitle("green"); + CardAction blue = new CardAction(); + blue.setType(ActionTypes.IM_BACK); + blue.setValue("blue"); + blue.setTitle("blue"); + ArrayList buttons = new ArrayList(); + buttons.add(red); + buttons.add(green); + buttons.add(blue); + card.setButtons(buttons); + new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); @@ -347,37 +346,7 @@ public void ShouldSendPromptUsingAppendedHeroCard() { return CompletableFuture.completedFuture(null); }) .send("hello") - .assertReply(new Validators().validateHeroCard( - new HeroCard() { - { - setText("favorite color?"); - setButtons(new ArrayList() { - { - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("red"); - setTitle("red"); - } - }); - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("green"); - setTitle("green"); - } - }); - add(new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("blue"); - setTitle("blue"); - } - }); - } - }); - } - }, 1)) + .assertReply(new Validators().validateHeroCard(card, 1)) .startTest().join(); } @@ -580,6 +549,19 @@ public void ShouldUseChoiceStyleIfPresent() { listPrompt.setStyle(ListStyle.HEROCARD); dialogs.add(listPrompt); + CardAction red = new CardAction(); + red.setType(ActionTypes.IM_BACK); + red.setValue("red"); + red.setTitle("red"); + CardAction green = new CardAction(); + green.setType(ActionTypes.IM_BACK); + green.setValue("green"); + green.setTitle("green"); + CardAction blue = new CardAction(); + blue.setType(ActionTypes.IM_BACK); + blue.setValue("blue"); + blue.setTitle("blue"); + CardAction[] suggestedActions = new CardAction[]{red, green, blue}; new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); @@ -595,31 +577,7 @@ public void ShouldUseChoiceStyleIfPresent() { return CompletableFuture.completedFuture(null); }) .send("hello") - .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(new - CardAction[] - { - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("red"); - setTitle("red"); - } - }, - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("green"); - setTitle("green"); - } - }, - new CardAction() { - { - setType(ActionTypes.IM_BACK); - setValue("blue"); - setTitle("blue"); - } - } - }))) + .assertReply(new Validators().validateSuggestedActions("favorite color?", new SuggestedActions(suggestedActions))) .startTest() .join(); } @@ -696,16 +654,13 @@ public void ShouldAcceptAndRecognizeCustomLocaleDict() { TestAdapter adapter = new TestAdapter().use(new AutoSaveStateMiddleware(convoState)); // Create new DialogSet. DialogSet dialogs = new DialogSet(dialogState); - PromptCultureModel culture = new PromptCultureModel() { - { - setInlineOr(" customOr "); - setInlineOrMore(" customOrMore "); - setLocale("custom-custom"); - setSeparator("customSeparator"); - setNoInLanguage("customNo"); - setYesInLanguage("customYes"); - } - }; + PromptCultureModel culture = new PromptCultureModel(); + culture.setInlineOr(" customOr "); + culture.setInlineOrMore(" customOrMore "); + culture.setLocale("custom-custom"); + culture.setSeparator("customSeparator"); + culture.setNoInLanguage("customNo"); + culture.setYesInLanguage("customYes"); Map customDict = new HashMap(); ChoiceFactoryOptions choiceOption = new ChoiceFactoryOptions(culture.getSeparator(), diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java index 8a042586f..a831a170f 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/NumberPromptTests.java @@ -566,6 +566,9 @@ public void CultureThruActivityNumberPrompt() throws UnsupportedDataTypeExceptio NumberPrompt numberPrompt = new NumberPrompt("NumberPrompt", null, PromptCultureModels.DUTCH_CULTURE, Double.class); dialogs.add(numberPrompt); + Activity activityToSend = new Activity(ActivityTypes.MESSAGE); + activityToSend.setText("3,14"); + activityToSend.setLocale(PromptCultureModels.DUTCH_CULTURE); new TestFlow(adapter, (turnContext) -> { DialogContext dc = dialogs.createContext(turnContext).join(); DialogTurnResult results = dc.continueDialog().join(); @@ -584,13 +587,7 @@ public void CultureThruActivityNumberPrompt() throws UnsupportedDataTypeExceptio }) .send("hello") .assertReply("Enter a number.") - .send(new Activity() { - { - setType(ActivityTypes.MESSAGE); - setText("3,14"); - setLocale(PromptCultureModels.DUTCH_CULTURE); - } - }) + .send(activityToSend) .startTest() .join(); } diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java index 88deb325b..e7b8479d1 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/prompts/OAuthPromptTests.java @@ -83,12 +83,10 @@ public void OAuthPromptBeginDialogWithWrongOptions() { dialogs.add(prompt); ConversationAccount conversation = new ConversationAccount(); conversation.setId("123"); - TurnContextImpl tc = new TurnContextImpl(adapter, new Activity(ActivityTypes.MESSAGE) { - { - setConversation(conversation); - setChannelId("test"); - } - }); + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setConversation(conversation); + activity.setChannelId("test"); + TurnContextImpl tc = new TurnContextImpl(adapter, activity); DialogContext dc = dialogs.createContext(tc).join(); @@ -289,6 +287,13 @@ public void OAuthPromptWithTokenExchangeInvoke() { return CompletableFuture.completedFuture(null); }; + TokenExchangeInvokeRequest value = new TokenExchangeInvokeRequest(); + value.setConnectionName(connectionName); + value.setToken(exchangeToken); + Activity activityToSend = new Activity(ActivityTypes.INVOKE); + activityToSend.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + activityToSend.setValue(value); + new TestFlow(adapter, botCallbackHandler) .send("hello") .assertReply(activity -> { @@ -300,16 +305,7 @@ public void OAuthPromptWithTokenExchangeInvoke() { adapter.addExchangeableToken(connectionName, activity.getChannelId(), activity.getRecipient().getId(), exchangeToken, token); }) - .send(new Activity(ActivityTypes.INVOKE) { - { - setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); - setValue(new TokenExchangeInvokeRequest() { - { - setConnectionName(connectionName); - setToken(exchangeToken); - } - }); - }}) + .send(activityToSend) .assertReply(a -> { Assert.assertEquals("invokeResponse", a.getType()); InvokeResponse response = (InvokeResponse) ((Activity)a).getValue(); @@ -361,6 +357,13 @@ public void OAuthPromptWithTokenExchangeFail() { return CompletableFuture.completedFuture(null); }; + TokenExchangeInvokeRequest value = new TokenExchangeInvokeRequest(); + value.setConnectionName(connectionName); + value.setToken(exchangeToken); + Activity activityToSend = new Activity(ActivityTypes.INVOKE); + activityToSend.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + activityToSend.setValue(value); + new TestFlow(adapter, botCallbackHandler) .send("hello") .assertReply(activity -> { @@ -369,16 +372,7 @@ public void OAuthPromptWithTokenExchangeFail() { Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); // No exchangable token is added to the adapter }) - .send(new Activity(ActivityTypes.INVOKE) { - { - setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); - setValue(new TokenExchangeInvokeRequest() { - { - setConnectionName(connectionName); - setToken(exchangeToken); - } - }); - }}) + .send(activityToSend) .assertReply(a -> { Assert.assertEquals("invokeResponse", a.getType()); InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); @@ -428,6 +422,8 @@ public void OAuthPromptWithTokenExchangeNoBodyFails() { return CompletableFuture.completedFuture(null); }; + Activity activityToSend = new Activity(ActivityTypes.INVOKE); + activityToSend.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); new TestFlow(adapter, botCallbackHandler) .send("hello") .assertReply(activity -> { @@ -436,11 +432,7 @@ public void OAuthPromptWithTokenExchangeNoBodyFails() { Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); // No exchangable token is added to the adapter }) - .send(new Activity(ActivityTypes.INVOKE) { - { - setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); - } - }) + .send(activityToSend) .assertReply(a -> { Assert.assertEquals("invokeResponse", a.getType()); InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); @@ -491,6 +483,12 @@ public void OAuthPromptWithTokenExchangeWrongConnectionNameFail() { return CompletableFuture.completedFuture(null); }; + TokenExchangeInvokeRequest value = new TokenExchangeInvokeRequest(); + value.setConnectionName("beepboop"); + value.setToken(exchangeToken); + Activity activityToSend = new Activity(ActivityTypes.INVOKE); + activityToSend.setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); + activityToSend.setValue(value); new TestFlow(adapter, botCallbackHandler) .send("hello") .assertReply(activity -> { @@ -499,16 +497,7 @@ public void OAuthPromptWithTokenExchangeWrongConnectionNameFail() { Assert.assertEquals(InputHints.ACCEPTING_INPUT, ((Activity) activity).getInputHint()); // No exchangable token is added to the adapter }) - .send(new Activity(ActivityTypes.INVOKE) { - { - setName(SignInConstants.TOKEN_EXCHANGE_OPERATION_NAME); - setValue(new TokenExchangeInvokeRequest() { - { - setConnectionName("beepboop"); - setToken(exchangeToken); - } - }); - }}) + .send(activityToSend) .assertReply(a -> { Assert.assertEquals("invokeResponse", a.getType()); InvokeResponse response = (InvokeResponse) ((Activity) a).getValue(); @@ -542,27 +531,31 @@ public void TestAdapterTokenExchange() { token); // Positive case: Token - TokenResponse result = adapter.exchangeToken(turnContext, connectionName, userId, - new TokenExchangeRequest() {{ setToken(exchangeToken); }}).join(); + TokenExchangeRequest requestPositiveToken = new TokenExchangeRequest(); + requestPositiveToken.setToken(exchangeToken); + TokenResponse result = adapter.exchangeToken(turnContext, connectionName, userId, requestPositiveToken).join(); Assert.assertNotNull(result); Assert.assertEquals(token, result.getToken()); Assert.assertEquals(connectionName, result.getConnectionName()); // Positive case: URI - result = adapter.exchangeToken(turnContext, connectionName, userId, - new TokenExchangeRequest() { { setUri(exchangeToken); }}).join(); + TokenExchangeRequest requestPositiveURI = new TokenExchangeRequest(); + requestPositiveURI.setUri(exchangeToken); + result = adapter.exchangeToken(turnContext, connectionName, userId, requestPositiveURI).join(); Assert.assertNotNull(result); Assert.assertEquals(token, result.getToken()); Assert.assertEquals(connectionName, result.getConnectionName()); // Negative case: Token - result = adapter.exchangeToken(turnContext, connectionName, userId, - new TokenExchangeRequest() {{ setToken("beeboop"); }}).join(); + TokenExchangeRequest requestNegativeToken = new TokenExchangeRequest(); + requestNegativeToken.setToken("beeboop"); + result = adapter.exchangeToken(turnContext, connectionName, userId, requestNegativeToken).join(); Assert.assertNull(result); // Negative case: URI - result = adapter.exchangeToken(turnContext, connectionName, userId, - new TokenExchangeRequest() {{ setUri("beeboop"); }}).join(); + TokenExchangeRequest requestNegativeURI = new TokenExchangeRequest(); + requestNegativeURI.setToken("beeboop"); + result = adapter.exchangeToken(turnContext, connectionName, userId, requestNegativeURI).join(); Assert.assertNull(result); return CompletableFuture.completedFuture(null); diff --git a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/AdapterWithErrorHandler.java b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/AdapterWithErrorHandler.java index e56b1281a..06e835601 100644 --- a/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/AdapterWithErrorHandler.java +++ b/libraries/bot-integration-core/src/main/java/com/microsoft/bot/integration/AdapterWithErrorHandler.java @@ -94,14 +94,11 @@ private CompletableFuture sendTraceActivity( Throwable exception ) { if (StringUtils.equals(turnContext.getActivity().getChannelId(), Channels.EMULATOR)) { - Activity traceActivity = new Activity(ActivityTypes.TRACE) { - { - setLabel("TurnError"); - setName("OnTurnError Trace"); - setValue(ExceptionUtils.getStackTrace(exception)); - setValueType("https://www.botframework.com/schemas/error"); - } - }; + Activity traceActivity = new Activity(ActivityTypes.TRACE); + traceActivity.setLabel("TurnError"); + traceActivity.setName("OnTurnError Trace"); + traceActivity.setValue(ExceptionUtils.getStackTrace(exception)); + traceActivity.setValueType("https://www.botframework.com/schemas/error"); return turnContext.sendActivity(traceActivity).thenApply(resourceResponse -> null); } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java index f442fb2df..57f282ae0 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Activity.java @@ -253,20 +253,16 @@ public static Activity createTraceActivity( Object withValue, String withLabel ) { - return new Activity(ActivityTypes.TRACE) { - { - setName(withName); - setLabel(withLabel); - if (withValue != null) { - setValueType( - (withValueType == null) ? withValue.getClass().getTypeName() : withValueType - ); - } else { - setValueType(withValueType); - } - setValue(withValue); - } - }; + Activity activity = new Activity(ActivityTypes.TRACE); + activity.setName(withName); + activity.setLabel(withLabel); + if (withValue != null) { + activity.setValueType((withValueType == null) ? withValue.getClass().getTypeName() : withValueType); + } else { + activity.setValueType(withValueType); + } + activity.setValue(withValue); + return activity; } /** @@ -296,12 +292,10 @@ public static Activity createContactRelationUpdateActivity() { * @return A conversation update type Activity. */ public static Activity createConversationUpdateActivity() { - return new Activity(ActivityTypes.CONVERSATION_UPDATE) { - { - setMembersAdded(new ArrayList<>()); - setMembersRemoved(new ArrayList<>()); - } - }; + Activity activity = new Activity(ActivityTypes.CONVERSATION_UPDATE); + activity.setMembersAdded(new ArrayList<>()); + activity.setMembersRemoved(new ArrayList<>()); + return activity; } /** @@ -356,62 +350,58 @@ public static Activity createInvokeActivity() { * @return new cloned activity */ public static Activity clone(Activity activity) { - Activity clone = new Activity(activity.getType()) { - { - setId(activity.getId()); - setTimestamp(activity.getTimestamp()); - setLocalTimestamp(activity.getLocalTimestamp()); - setLocalTimeZone(activity.getLocalTimezone()); - setChannelData(activity.getChannelData()); - setFrom(ChannelAccount.clone(activity.getFrom())); - setRecipient(ChannelAccount.clone(activity.getRecipient())); - setConversation(ConversationAccount.clone(activity.getConversation())); - setChannelId(activity.getChannelId()); - setServiceUrl(activity.getServiceUrl()); - setChannelId(activity.getChannelId()); - setEntities(Entity.cloneList(activity.getEntities())); - setReplyToId(activity.getReplyToId()); - setSpeak(activity.getSpeak()); - setText(activity.getText()); - setInputHint(activity.getInputHint()); - setSummary(activity.getSummary()); - setSuggestedActions(SuggestedActions.clone(activity.getSuggestedActions())); - setAttachments(Attachment.cloneList(activity.getAttachments())); - setAction(activity.getAction()); - setLabel(activity.getLabel()); - setValueType(activity.getValueType()); - setValue(activity.getValue()); - setName(activity.getName()); - setRelatesTo(ConversationReference.clone(activity.getRelatesTo())); - setCode(activity.getCode()); - setExpiration(activity.getExpiration()); - setImportance(activity.getImportance()); - setDeliveryMode(activity.getDeliveryMode()); - setTextHighlights(activity.getTextHighlights()); - setCallerId(activity.getCallerId()); - setHistoryDisclosed(activity.getHistoryDisclosed()); - setLocale(activity.getLocale()); - setReactionsAdded(MessageReaction.cloneList(activity.getReactionsAdded())); - setReactionsRemoved(MessageReaction.cloneList(activity.getReactionsRemoved())); - setExpiration(activity.getExpiration()); - setMembersAdded(ChannelAccount.cloneList(activity.getMembersAdded())); - setMembersRemoved(ChannelAccount.cloneList(activity.getMembersRemoved())); - setTextFormat(activity.getTextFormat()); - setAttachmentLayout(activity.getAttachmentLayout()); - setTopicName(activity.getTopicName()); - if (activity.getListenFor() != null) { - setListenFor(new ArrayList<>(activity.getListenFor())); - } - } - }; - + Activity cloned = new Activity(activity.getType()); + cloned.setId(activity.getId()); + cloned.setTimestamp(activity.getTimestamp()); + cloned.setLocalTimestamp(activity.getLocalTimestamp()); + cloned.setLocalTimeZone(activity.getLocalTimezone()); + cloned.setChannelData(activity.getChannelData()); + cloned.setFrom(ChannelAccount.clone(activity.getFrom())); + cloned.setRecipient(ChannelAccount.clone(activity.getRecipient())); + cloned.setConversation(ConversationAccount.clone(activity.getConversation())); + cloned.setChannelId(activity.getChannelId()); + cloned.setServiceUrl(activity.getServiceUrl()); + cloned.setChannelId(activity.getChannelId()); + cloned.setEntities(Entity.cloneList(activity.getEntities())); + cloned.setReplyToId(activity.getReplyToId()); + cloned.setSpeak(activity.getSpeak()); + cloned.setText(activity.getText()); + cloned.setInputHint(activity.getInputHint()); + cloned.setSummary(activity.getSummary()); + cloned.setSuggestedActions(SuggestedActions.clone(activity.getSuggestedActions())); + cloned.setAttachments(Attachment.cloneList(activity.getAttachments())); + cloned.setAction(activity.getAction()); + cloned.setLabel(activity.getLabel()); + cloned.setValueType(activity.getValueType()); + cloned.setValue(activity.getValue()); + cloned.setName(activity.getName()); + cloned.setRelatesTo(ConversationReference.clone(activity.getRelatesTo())); + cloned.setCode(activity.getCode()); + cloned.setExpiration(activity.getExpiration()); + cloned.setImportance(activity.getImportance()); + cloned.setDeliveryMode(activity.getDeliveryMode()); + cloned.setTextHighlights(activity.getTextHighlights()); + cloned.setCallerId(activity.getCallerId()); + cloned.setHistoryDisclosed(activity.getHistoryDisclosed()); + cloned.setLocale(activity.getLocale()); + cloned.setReactionsAdded(MessageReaction.cloneList(activity.getReactionsAdded())); + cloned.setReactionsRemoved(MessageReaction.cloneList(activity.getReactionsRemoved())); + cloned.setExpiration(activity.getExpiration()); + cloned.setMembersAdded(ChannelAccount.cloneList(activity.getMembersAdded())); + cloned.setMembersRemoved(ChannelAccount.cloneList(activity.getMembersRemoved())); + cloned.setTextFormat(activity.getTextFormat()); + cloned.setAttachmentLayout(activity.getAttachmentLayout()); + cloned.setTopicName(activity.getTopicName()); + if (activity.getListenFor() != null) { + cloned.setListenFor(new ArrayList<>(activity.getListenFor())); + } for (Map.Entry entry : activity.getProperties().entrySet()) { - clone.setProperties(entry.getKey(), entry.getValue()); + cloned.setProperties(entry.getKey(), entry.getValue()); } - clone = ensureActivityHasId(clone); + cloned = ensureActivityHasId(cloned); - return clone; + return cloned; } private static Activity ensureActivityHasId(Activity activity) { @@ -1525,17 +1515,15 @@ public ResultPair tryGetChannelData(Class clsType) { */ @JsonIgnore public ConversationReference getConversationReference() { - return new ConversationReference() { - { - setActivityId(Activity.this.getId()); - setUser(Activity.this.getFrom()); - setBot(Activity.this.getRecipient()); - setConversation(Activity.this.getConversation()); - setChannelId(Activity.this.getChannelId()); - setLocale(Activity.this.getLocale()); - setServiceUrl(Activity.this.getServiceUrl()); - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setActivityId(getId()); + conversationReference.setUser(getFrom()); + conversationReference.setBot(getRecipient()); + conversationReference.setConversation(getConversation()); + conversationReference.setChannelId(getChannelId()); + conversationReference.setLocale(getLocale()); + conversationReference.setServiceUrl(getServiceUrl()); + return conversationReference; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java index c5a7538b7..a578fadc9 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AnimationCard.java @@ -344,11 +344,9 @@ public void setValue(Object withValue) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(AnimationCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Attachment.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Attachment.java index de03b0d3b..eb99e2807 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Attachment.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Attachment.java @@ -70,19 +70,17 @@ public static Attachment clone(Attachment attachment) { return null; } - return new Attachment() { - { - setContentType(attachment.getContentType()); - setContent(attachment.getContent()); - setContentUrl(attachment.getContentUrl()); - setName(attachment.getName()); - setThumbnailUrl(attachment.getThumbnailUrl()); - - for (String key : attachment.getProperties().keySet()) { - this.setProperties(key, attachment.getProperties().get(key)); - } - } - }; + Attachment cloned = new Attachment(); + cloned.setContentType(attachment.getContentType()); + cloned.setContent(attachment.getContent()); + cloned.setContentUrl(attachment.getContentUrl()); + cloned.setName(attachment.getName()); + cloned.setThumbnailUrl(attachment.getThumbnailUrl()); + + for (String key : attachment.getProperties().keySet()) { + cloned.setProperties(key, attachment.getProperties().get(key)); + } + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java index 753f3ed6c..4cc41b9cb 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AudioCard.java @@ -345,11 +345,9 @@ public void setValue(Object withValue) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(AudioCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java index 7cea03a9e..ae1c53c91 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/CardAction.java @@ -80,18 +80,17 @@ public static CardAction clone(CardAction cardAction) { return null; } - return new CardAction() { - { - setValue(cardAction.getValue()); - setTitle(cardAction.getTitle()); - setDisplayText(cardAction.getDisplayText()); - setImage(cardAction.getImage()); - setType(cardAction.getType()); - setText(cardAction.getText()); - setChannelData(cardAction.getChannelData()); - setImageAltText(cardAction.getImageAltText()); - } - }; + CardAction cloned = new CardAction(); + cloned.setValue(cardAction.getValue()); + cloned.setTitle(cardAction.getTitle()); + cloned.setDisplayText(cardAction.getDisplayText()); + cloned.setImage(cardAction.getImage()); + cloned.setType(cardAction.getType()); + cloned.setText(cardAction.getText()); + cloned.setChannelData(cardAction.getChannelData()); + cloned.setImageAltText(cardAction.getImageAltText()); + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ChannelAccount.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ChannelAccount.java index 9d62ec994..e7bdc190b 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ChannelAccount.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ChannelAccount.java @@ -52,18 +52,17 @@ public static ChannelAccount clone(ChannelAccount channelAccount) { return null; } - return new ChannelAccount() { - { - setId(channelAccount.getId()); - setRole(channelAccount.getRole()); - setName(channelAccount.getName()); - setAadObjectId(channelAccount.getAadObjectId()); - - for (String key : channelAccount.getProperties().keySet()) { - this.setProperties(key, channelAccount.getProperties().get(key)); - } - } - }; + ChannelAccount cloned = new ChannelAccount(); + cloned.setId(channelAccount.getId()); + cloned.setRole(channelAccount.getRole()); + cloned.setName(channelAccount.getName()); + cloned.setAadObjectId(channelAccount.getAadObjectId()); + + for (String key : channelAccount.getProperties().keySet()) { + cloned.setProperties(key, channelAccount.getProperties().get(key)); + } + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationAccount.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationAccount.java index 31dc809b7..70dd47a19 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationAccount.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationAccount.java @@ -278,21 +278,19 @@ public static ConversationAccount clone(ConversationAccount conversationAccount) return null; } - return new ConversationAccount() { - { - setId(conversationAccount.getId()); - setName(conversationAccount.getName()); - setIsGroup(conversationAccount.isGroup()); - setConversationType(conversationAccount.getConversationType()); - setAadObjectId(conversationAccount.getAadObjectId()); - setRole(conversationAccount.getRole()); - setAadObjectId(conversationAccount.getAadObjectId()); - - for (String key : conversationAccount.getProperties().keySet()) { - this.setProperties(key, conversationAccount.getProperties().get(key)); - } - } - }; + ConversationAccount cloned = new ConversationAccount(); + cloned.setId(conversationAccount.getId()); + cloned.setName(conversationAccount.getName()); + cloned.setIsGroup(conversationAccount.isGroup()); + cloned.setConversationType(conversationAccount.getConversationType()); + cloned.setAadObjectId(conversationAccount.getAadObjectId()); + cloned.setRole(conversationAccount.getRole()); + cloned.setAadObjectId(conversationAccount.getAadObjectId()); + for (String key : conversationAccount.getProperties().keySet()) { + cloned.setProperties(key, conversationAccount.getProperties().get(key)); + } + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java index e67a97ff9..ae03a8081 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ConversationReference.java @@ -52,17 +52,16 @@ public static ConversationReference clone(ConversationReference conversationRefe return null; } - return new ConversationReference() { - { - setActivityId(conversationReference.getActivityId()); - setBot(ChannelAccount.clone(conversationReference.getBot())); - setUser(ChannelAccount.clone(conversationReference.getUser())); - setConversation(ConversationAccount.clone(conversationReference.getConversation())); - setServiceUrl(conversationReference.getServiceUrl()); - setLocale(conversationReference.getLocale()); - setChannelId(conversationReference.getChannelId()); - } - }; + ConversationReference cloned = new ConversationReference(); + cloned.setActivityId(conversationReference.getActivityId()); + cloned.setBot(ChannelAccount.clone(conversationReference.getBot())); + cloned.setUser(ChannelAccount.clone(conversationReference.getUser())); + cloned.setConversation(ConversationAccount.clone(conversationReference.getConversation())); + cloned.setServiceUrl(conversationReference.getServiceUrl()); + cloned.setLocale(conversationReference.getLocale()); + cloned.setChannelId(conversationReference.getChannelId()); + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java index 11848f35d..83cbacb15 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/Entity.java @@ -47,15 +47,13 @@ public static Entity clone(Entity entity) { return null; } - return new Entity() { - { - setType(entity.getType()); - - for (String key : entity.getProperties().keySet()) { - setProperties(key, entity.getProperties().get(key)); - } - } - }; + Entity cloned = new Entity(); + cloned.setType(entity.getType()); + for (String key : entity.getProperties().keySet()) { + cloned.setProperties(key, entity.getProperties().get(key)); + } + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java index 0242c659a..bb93ae7dd 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HeroCard.java @@ -191,11 +191,9 @@ public void setTap(CardAction withTap) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(HeroCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MessageReaction.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MessageReaction.java index 3e928f1f9..53f9cdf96 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MessageReaction.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/MessageReaction.java @@ -31,12 +31,9 @@ public static MessageReaction clone(MessageReaction messageReaction) { if (messageReaction == null) { return null; } - - return new MessageReaction() { - { - setType(messageReaction.getType()); - } - }; + MessageReaction cloned = new MessageReaction(); + cloned.setType(messageReaction.getType()); + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java index 7524eafb0..a1013190a 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/OAuthCard.java @@ -112,12 +112,11 @@ public void setButtons(CardAction... withButtons) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(OAuthCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + + return attachment; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java index 53c8fd226..8b5050275 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ReceiptCard.java @@ -250,11 +250,9 @@ public void setButtons(CardAction... withButtons) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(ReceiptCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java index ea27727b2..6dd76761a 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SigninCard.java @@ -82,11 +82,9 @@ public void setButtons(CardAction... withButtons) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(SigninCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SuggestedActions.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SuggestedActions.java index 899e07e95..9ee107247 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SuggestedActions.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/SuggestedActions.java @@ -41,17 +41,16 @@ public static SuggestedActions clone(SuggestedActions suggestedActions) { return null; } - return new SuggestedActions() { - { - setTo(suggestedActions.getTo()); - - List cloned = suggestedActions.getActions() - .stream() - .map(card -> CardAction.clone(card)) - .collect(Collectors.toCollection(ArrayList::new)); - setActions(cloned); - } - }; + SuggestedActions cloned = new SuggestedActions(); + cloned.setTo(suggestedActions.getTo()); + + List actions = suggestedActions.getActions() + .stream() + .map(card -> CardAction.clone(card)) + .collect(Collectors.toCollection(ArrayList::new)); + cloned.setActions(actions); + + return cloned; } /** diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java index 55d0e8029..7ee200315 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/ThumbnailCard.java @@ -182,11 +182,9 @@ public void setTap(CardAction withTap) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(ThumbnailCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java index b0e758057..8cd1d336e 100644 --- a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/VideoCard.java @@ -310,11 +310,9 @@ public void setValue(Object withValue) { * @return An Attachment object containing the card. */ public Attachment toAttachment() { - return new Attachment() { - { - setContent(VideoCard.this); - setContentType(CONTENTTYPE); - } - }; + Attachment attachment = new Attachment(); + attachment.setContent(this); + attachment.setContentType(CONTENTTYPE); + return attachment; } } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java index 949226bcc..8966a4226 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ActivityTest.java @@ -34,11 +34,8 @@ public void GetConversationReference() { public void GetReplyConversationReference() { Activity activity = createActivity(); - ResourceResponse reply = new ResourceResponse() { - { - setId("1234"); - } - }; + ResourceResponse reply = new ResourceResponse(); + reply.setId("1234"); ConversationReference conversationReference = activity.getReplyConversationReference(reply); @@ -55,30 +52,22 @@ public void GetReplyConversationReference() { public void ApplyConversationReference_isIncoming() { Activity activity = createActivity(); - ConversationReference conversationReference = new ConversationReference() { - { - setChannelId("cr_123"); - setServiceUrl("cr_serviceUrl"); - setConversation(new ConversationAccount() { - { - setId("cr_456"); - } - }); - setUser(new ChannelAccount() { - { - setId("cr_abc"); - } - }); - setBot(new ChannelAccount() { - { - setId("cr_def"); - } - }); - setActivityId("cr_12345"); - setLocale("en-uS"); // Intentionally oddly-cased to check that it isn't defaulted somewhere, but - // tests stay in English - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setChannelId("cr_123"); + conversationReference.setServiceUrl("cr_serviceUrl"); + ConversationAccount conversation = new ConversationAccount(); + conversation.setId("cr_456"); + conversationReference.setConversation(conversation); + ChannelAccount userAccount = new ChannelAccount(); + userAccount.setId("cr_abc"); + conversationReference.setUser(userAccount); + ChannelAccount botAccount = new ChannelAccount(); + botAccount.setId("cr_def"); + conversationReference.setBot(botAccount); + conversationReference.setActivityId("cr_12345"); + // Intentionally oddly-cased to check that it isn't defaulted somewhere, but + // tests stay in English + conversationReference.setLocale("en-uS"); activity.applyConversationReference(conversationReference, true); @@ -96,30 +85,22 @@ public void ApplyConversationReference_isIncoming() { public void ApplyConversationReference() { Activity activity = createActivity(); - ConversationReference conversationReference = new ConversationReference() { - { - setChannelId("123"); - setServiceUrl("serviceUrl"); - setConversation(new ConversationAccount() { - { - setId("456"); - } - }); - setUser(new ChannelAccount() { - { - setId("abc"); - } - }); - setBot(new ChannelAccount() { - { - setId("def"); - } - }); - setActivityId("12345"); - setLocale("en-uS"); // Intentionally oddly-cased to check that it isn't defaulted somewhere, but - // tests stay in English - } - }; + ConversationReference conversationReference = new ConversationReference(); + conversationReference.setChannelId("123"); + conversationReference.setServiceUrl("serviceUrl"); + ConversationAccount conversation = new ConversationAccount(); + conversation.setId("456"); + conversationReference.setConversation(conversation); + ChannelAccount userAccount = new ChannelAccount(); + userAccount.setId("abc"); + conversationReference.setUser(userAccount); + ChannelAccount botAccount = new ChannelAccount(); + botAccount.setId("def"); + conversationReference.setBot(botAccount); + conversationReference.setActivityId("12345"); + // Intentionally oddly-cased to check that it isn't defaulted somewhere, but + // tests stay in English + conversationReference.setLocale("en-uS"); activity.applyConversationReference(conversationReference, false); @@ -143,46 +124,35 @@ public void CreateTraceAllowsNullRecipient() { } private Activity createActivity() { - ChannelAccount account1 = new ChannelAccount() { - { - setId("ChannelAccount_Id_1"); - setName("ChannelAccount_Name_1"); - setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); - setRole(RoleTypes.USER); - } - }; - - ChannelAccount account2 = new ChannelAccount() { - { - setId("ChannelAccount_Id_2"); - setName("ChannelAccount_Name_2"); - setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); - setRole(RoleTypes.USER); - } - }; - - ConversationAccount conversationAccount = new ConversationAccount() { - { - setConversationType("a"); - setId("123"); - setIsGroup(true); - setName("Name"); - setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); - } - }; - - Activity activity = new Activity() { - { - setId("123"); - setFrom(account1); - setRecipient(account2); - setConversation(conversationAccount); - setChannelId("ChannelId123"); - setLocale("en-uS"); // Intentionally oddly-cased to check that it isn't defaulted somewhere, but - // tests stay in English - setServiceUrl("ServiceUrl123"); - } - }; + ChannelAccount account1 = new ChannelAccount(); + account1.setId("ChannelAccount_Id_1"); + account1.setName("ChannelAccount_Name_1"); + account1.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + account1.setRole(RoleTypes.USER); + + ChannelAccount account2 = new ChannelAccount(); + account2.setId("ChannelAccount_Id_2"); + account2.setName("ChannelAccount_Name_2"); + account2.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + account2.setRole(RoleTypes.USER); + + ConversationAccount conversationAccount = new ConversationAccount(); + conversationAccount.setConversationType("a"); + conversationAccount.setId("123"); + conversationAccount.setIsGroup(true); + conversationAccount.setName("Name"); + conversationAccount.setProperties("Name", JsonNodeFactory.instance.objectNode().put("Name", "Value")); + + Activity activity = new Activity(); + activity.setId("123"); + activity.setFrom(account1); + activity.setRecipient(account2); + activity.setConversation(conversationAccount); + activity.setChannelId("ChannelId123"); + // Intentionally oddly-cased to check that it isn't defaulted somewhere, but + // tests stay in English + activity.setLocale("en-uS"); + activity.setServiceUrl("ServiceUrl123"); return activity; } @@ -470,16 +440,12 @@ public void HasContentIsTrueWhenActivityChannelDataHasContent() { public void GetMentions() { ArrayList mentions = new ArrayList(); - mentions.add(new Entity() { - { - setType("mention"); - } - }); - mentions.add(new Entity() { - { - setType("reaction"); - } - }); + Entity mentionEntity = new Entity(); + mentionEntity.setType("mention"); + mentions.add(mentionEntity); + Entity reactionEntity = new Entity(); + reactionEntity.setType("reaction"); + mentions.add(reactionEntity); Activity activity = createActivity(); diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AnimationCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AnimationCardTest.java index f00e0c1f6..d353cbd41 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AnimationCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AnimationCardTest.java @@ -13,19 +13,20 @@ public class AnimationCardTest { ArrayList media = new ArrayList(); - AnimationCard card = new AnimationCard(){ - { - setText("Test Animation Text"); - setMedia(media); - } - }; + AnimationCard getCard() { + AnimationCard card = new AnimationCard(); + card.setText("Test Animation Text"); + card.setMedia(media); + return card; + } + /** *Ensures that the AnimationCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.animation", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AudioCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AudioCardTest.java index a66861517..718836c7a 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AudioCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/AudioCardTest.java @@ -13,19 +13,19 @@ public class AudioCardTest { ArrayList media = new ArrayList(); - AudioCard card = new AudioCard(){ - { - setText("Test Audio Text"); - setMedia(media); - }; - }; + AudioCard getCard() { + AudioCard card = new AudioCard(); + card.setText("Test Audio Text"); + card.setMedia(media); + return card; + } /** *Ensures that the AudioCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.audio", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/EntitySchemaValidationTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/EntitySchemaValidationTest.java index a284397e2..f65dfb24d 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/EntitySchemaValidationTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/EntitySchemaValidationTest.java @@ -12,12 +12,9 @@ public class EntitySchemaValidationTest { @Test public void EntityTests_GeoCoordinatesSerializationDeserializationTest() { - GeoCoordinates geoCoordinates = new GeoCoordinates() { - { - setLatitude(22.00); - setLongitude(23.00); - } - }; + GeoCoordinates geoCoordinates = new GeoCoordinates(); + geoCoordinates.setLatitude(22.00); + geoCoordinates.setLongitude(23.00); Assert.assertEquals("GeoCoordinates", geoCoordinates.getType()); @@ -36,11 +33,8 @@ public void EntityTests_GeoCoordinatesSerializationDeserializationTest() { @Test public void EntityTests_MentionSerializationDeserializationTest() { - Mention mentionEntity = new Mention() { - { - setText("TESTTEST"); - } - }; + Mention mentionEntity = new Mention(); + mentionEntity.setText("TESTTEST"); Assert.assertEquals("mention", mentionEntity.getType()); @@ -59,11 +53,8 @@ public void EntityTests_MentionSerializationDeserializationTest() { @Test public void EntityTests_PlaceSerializationDeserializationTest() { - Place placeEntity = new Place() { - { - setName("TESTTEST"); - } - }; + Place placeEntity = new Place(); + placeEntity.setName("TESTTEST"); Assert.assertEquals("Place", placeEntity.getType()); diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/HeroCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/HeroCardTest.java index 4066a7c54..a5630aa15 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/HeroCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/HeroCardTest.java @@ -10,20 +10,20 @@ * Tests to ensure the HeroCard methods work as expected. */ public class HeroCardTest { - HeroCard card = new HeroCard(){ - { - setTitle("Hero Card Title"); - setSubtitle("Hero Card Subtitle"); - setText("Testing Text."); - } - }; + HeroCard getCard() { + HeroCard card = new HeroCard(); + card.setTitle("Hero Card Title"); + card.setSubtitle("Hero Card Subtitle"); + card.setText("Testing Text."); + return card; + } /** * Ensures that the HeroCard can be added as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.hero", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/OAuthCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/OAuthCardTest.java index d74c9c144..7012ced03 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/OAuthCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/OAuthCardTest.java @@ -10,19 +10,19 @@ * Tests to ensure the OAuthCard methods work as expected. */ public class OAuthCardTest { - OAuthCard card = new OAuthCard(){ - { - setText("Test OAuth Text"); - setConnectionName("Test Connection Name"); - } - }; + OAuthCard getCard() { + OAuthCard card = new OAuthCard(); + card.setText("Test OAuth Text"); + card.setConnectionName("Test Connection Name"); + return card; + } /** *Ensures that the OAuthCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.oauth", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ReceiptCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ReceiptCardTest.java index b81713091..cf02036bf 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ReceiptCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ReceiptCardTest.java @@ -11,18 +11,18 @@ */ public class ReceiptCardTest { - ReceiptCard card = new ReceiptCard() { - { - setTitle("John Doe"); - } - }; + ReceiptCard getCard() { + ReceiptCard card = new ReceiptCard(); + card.setTitle("John Doe"); + return card; + } /** * Ensures that the ReceiptCard can be added as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.receipt", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SigninCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SigninCardTest.java index 2929babd8..21c096e0f 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SigninCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/SigninCardTest.java @@ -10,18 +10,18 @@ * Tests to ensure the SigninCard methods work as expected. */ public class SigninCardTest { - SigninCard card = new SigninCard(){ - { - setText("Test Signin Text"); - } - }; + SigninCard getCard() { + SigninCard card = new SigninCard(); + card.setText("Test Signin Text"); + return card; + } /** *Ensures that the SigninCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.signin", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ThumbnailCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ThumbnailCardTest.java index 2e935f9ee..acd4c436f 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ThumbnailCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/ThumbnailCardTest.java @@ -10,22 +10,22 @@ * Tests to ensure the ThumbnailCard methods work as expected. */ public class ThumbnailCardTest { - ThumbnailCard card = new ThumbnailCard(){ - { - setText("Test Thumbnail Text"); - setTitle("Test Thumbnail Title"); - setSubtitle("Test Thumbnail Subtitle"); - setImage(new CardImage()); - setTap(new CardAction()); - } - }; + ThumbnailCard getCard() { + ThumbnailCard card = new ThumbnailCard(); + card.setText("Test Thumbnail Text"); + card.setTitle("Test Thumbnail Title"); + card.setSubtitle("Test Thumbnail Subtitle"); + card.setImage(new CardImage()); + card.setTap(new CardAction()); + return card; + } /** *Ensures that the ThumbnailCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.thumbnail", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/VideoCardTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/VideoCardTest.java index b3752b64f..59f78254b 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/VideoCardTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/VideoCardTest.java @@ -10,21 +10,21 @@ * Tests to ensure the VideoCard methods work as expected. */ public class VideoCardTest { - VideoCard card = new VideoCard(){ - { - setTitle("Test Video Title"); - setSubtitle("Test Video Subtitle"); - setText("Test Video Text"); - setImage(new ThumbnailUrl()); - } - }; + VideoCard getCard() { + VideoCard card = new VideoCard(); + card.setTitle("Test Video Title"); + card.setSubtitle("Test Video Subtitle"); + card.setText("Test Video Text"); + card.setImage(new ThumbnailUrl()); + return card; + } /** *Ensures that the VideoCard can be used as an attachment. */ @Test public void testToAttachment() { - Attachment attachment = card.toAttachment(); + Attachment attachment = getCard().toAttachment(); Assert.assertNotNull(attachment); Assert.assertEquals("application/vnd.microsoft.card.video", attachment.getContentType()); } diff --git a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/teams/MessageActionsPayloadTest.java b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/teams/MessageActionsPayloadTest.java index 41574403d..a6fb83fd1 100644 --- a/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/teams/MessageActionsPayloadTest.java +++ b/libraries/bot-schema/src/test/java/com/microsoft/bot/schema/teams/MessageActionsPayloadTest.java @@ -28,11 +28,8 @@ public void TestMessageActionPayloadConstructor(){ @Test public void TestGetId(){ String id = "testId"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setId(id); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setId(id); String result = messageActionsPayload.getId(); Assert.assertEquals(result, id); @@ -44,11 +41,8 @@ public void TestGetId(){ @Test public void TestGetReplyToId(){ String replyToId = "testReplyToId"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setReplyToId(replyToId); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setReplyToId(replyToId); String result = messageActionsPayload.getReplyToId(); Assert.assertEquals(result, replyToId); @@ -60,11 +54,8 @@ public void TestGetReplyToId(){ @Test public void TestGetMessageType(){ String messageType = "testMessageType"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setMessageType(messageType); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setMessageType(messageType); String result = messageActionsPayload.getMessageType(); Assert.assertEquals(result, messageType); @@ -76,11 +67,8 @@ public void TestGetMessageType(){ @Test public void TestGetCreatedDateTime(){ String createdDateTime = "2000-01-01"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setCreatedDateTime(createdDateTime); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setCreatedDateTime(createdDateTime); String result = messageActionsPayload.getCreatedDateTime(); Assert.assertEquals(result, createdDateTime); @@ -92,11 +80,8 @@ public void TestGetCreatedDateTime(){ @Test public void TestGetLastModifiedDateTime(){ String lastModifiedDateTime = "2000-01-01"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setLastModifiedDateTime(lastModifiedDateTime); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setLastModifiedDateTime(lastModifiedDateTime); String result = messageActionsPayload.getLastModifiedDateTime(); Assert.assertEquals(result, lastModifiedDateTime); @@ -108,11 +93,8 @@ public void TestGetLastModifiedDateTime(){ @Test public void TestGetDeleted(){ Boolean deleted = false; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setDeleted(deleted); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setDeleted(deleted); Boolean result = messageActionsPayload.getDeleted(); Assert.assertEquals(result, deleted); @@ -124,11 +106,8 @@ public void TestGetDeleted(){ @Test public void TestGetSubject(){ String subject = "testSubject"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setSubject(subject); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setSubject(subject); String result = messageActionsPayload.getSubject(); Assert.assertEquals(result, subject); @@ -140,11 +119,8 @@ public void TestGetSubject(){ @Test public void TestGetSummary(){ String summary = "testSummary"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setSummary(summary); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setSummary(summary); String result = messageActionsPayload.getSummary(); Assert.assertEquals(result, summary); @@ -156,11 +132,8 @@ public void TestGetSummary(){ @Test public void TestGetImportance(){ String importance = "normal"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setImportance(importance); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setImportance(importance); String result = messageActionsPayload.getImportance(); Assert.assertEquals(result, importance); @@ -172,11 +145,8 @@ public void TestGetImportance(){ @Test public void TestGetLinkToMessage(){ String linkToMessage = "https://teams.microsoft.com/l/message/testing-id"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setLinkToMessage(linkToMessage); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setLinkToMessage(linkToMessage); String result = messageActionsPayload.getLinkToMessage(); Assert.assertEquals(result, linkToMessage); @@ -188,11 +158,8 @@ public void TestGetLinkToMessage(){ @Test public void TestGetLocale(){ String locale = "US"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setLocale(locale); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setLocale(locale); String result = messageActionsPayload.getLocale(); Assert.assertEquals(result, locale); @@ -204,11 +171,8 @@ public void TestGetLocale(){ @Test public void TestGetFrom(){ MessageActionsPayloadFrom from = new MessageActionsPayloadFrom(); - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setFrom(from); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setFrom(from); MessageActionsPayloadFrom result = messageActionsPayload.getFrom(); Assert.assertEquals(result, from); @@ -220,11 +184,8 @@ public void TestGetFrom(){ @Test public void TestGetBody(){ MessageActionsPayloadBody body = new MessageActionsPayloadBody(); - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setBody(body); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setBody(body); MessageActionsPayloadBody result = messageActionsPayload.getBody(); Assert.assertEquals(result, body); @@ -236,11 +197,8 @@ public void TestGetBody(){ @Test public void TestGetAttachmentLayout(){ String attachmentLayout = "testAttachmentLayout"; - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setAttachmentLayout(attachmentLayout); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setAttachmentLayout(attachmentLayout); String result = messageActionsPayload.getAttachmentLayout(); Assert.assertEquals(result, attachmentLayout); @@ -252,11 +210,8 @@ public void TestGetAttachmentLayout(){ @Test public void TestGetAttachments(){ List attachments = new ArrayList(); - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setAttachments(attachments); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setAttachments(attachments); List result = messageActionsPayload.getAttachments(); Assert.assertEquals(result, attachments); @@ -268,11 +223,8 @@ public void TestGetAttachments(){ @Test public void TestGetMentions(){ List mentions = new ArrayList(); - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setMentions(mentions); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setMentions(mentions); List result = messageActionsPayload.getMentions(); Assert.assertEquals(result, mentions); @@ -284,11 +236,8 @@ public void TestGetMentions(){ @Test public void TestGetReactions(){ List reactions = new ArrayList(); - MessageActionsPayload messageActionsPayload = new MessageActionsPayload(){ - { - setReactions(reactions); - } - }; + MessageActionsPayload messageActionsPayload = new MessageActionsPayload(); + messageActionsPayload.setReactions(reactions); List result = messageActionsPayload.getReactions(); Assert.assertEquals(result, reactions); diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java index c0a1e94a6..87ee851ad 100644 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java +++ b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java @@ -175,49 +175,45 @@ protected CompletableFuture onMessageActivity(TurnContext turnContext) { } private CompletableFuture sendIntroCard(TurnContext turnContext) { - HeroCard card = new HeroCard() {{ - setTitle("Welcome to Bot Framework!"); - setText( - "Welcome to Welcome Users bot sample! This Introduction card " - + "is a great way to introduce your Bot to the user and suggest " - + "some things to get them started. We use this opportunity to " - + "recommend a few next steps for learning more creating and deploying bots." - ); - }}; - - card.setImages(Collections.singletonList(new CardImage() { - { - setUrl("https://aka.ms/bf-welcome-card-image"); - } - })); - - card.setButtons(Arrays.asList( - new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setTitle("Get an overview"); - setText("Get an overview"); - setDisplayText("Get an overview"); - setValue( - "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - ); - }}, - new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setTitle("Ask a question"); - setText("Ask a question"); - setDisplayText("Ask a question"); - setValue("https://stackoverflow.com/questions/tagged/botframework"); - }}, - new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setTitle("Learn how to deploy"); - setText("Learn how to deploy"); - setDisplayText("Learn how to deploy"); - setValue( - "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - ); - }}) + HeroCard card = new HeroCard(); + card.setTitle("Welcome to Bot Framework!"); + card.setText( + "Welcome to Welcome Users bot sample! This Introduction card " + + "is a great way to introduce your Bot to the user and suggest " + + "some things to get them started. We use this opportunity to " + + "recommend a few next steps for learning more creating and deploying bots." + ); + + CardImage image = new CardImage(); + image.setUrl("https://aka.ms/bf-welcome-card-image"); + + card.setImages(Collections.singletonList(image)); + + CardAction overviewAction = new CardAction(); + overviewAction.setType(ActionTypes.OPEN_URL); + overviewAction.setTitle("Get an overview"); + overviewAction.setText("Get an overview"); + overviewAction.setDisplayText("Get an overview"); + overviewAction.setValue( + "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + ); + + CardAction questionAction = new CardAction(); + questionAction.setType(ActionTypes.OPEN_URL); + questionAction.setTitle("Ask a question"); + questionAction.setText("Ask a question"); + questionAction.setDisplayText("Ask a question"); + questionAction.setValue("https://stackoverflow.com/questions/tagged/botframework"); + + CardAction deployAction = new CardAction(); + deployAction.setType(ActionTypes.OPEN_URL); + deployAction.setTitle("Learn how to deploy"); + deployAction.setText("Learn how to deploy"); + deployAction.setDisplayText("Learn how to deploy"); + deployAction.setValue( + "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" ); + card.setButtons(Arrays.asList(overviewAction, questionAction, deployAction)); Activity response = MessageFactory.attachment(card.toAttachment()); return turnContext.sendActivity(response); diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java index d4acaa008..1cf86be65 100644 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java +++ b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java @@ -73,29 +73,24 @@ public static ThumbnailCard getThumbnailCard() { public static ReceiptCard getReceiptCard() { ReceiptCard receiptCard = new ReceiptCard(); receiptCard.setTitle("John Doe"); - receiptCard.setFacts( - new Fact("Order Number", "1234"), new Fact("Payment Method", "VISA 5555-****")); - receiptCard.setItems( - new ReceiptItem(){{ - setTitle("Data Transfer"); - setPrice("$ 38.45"); - setQuantity("368"); - setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png")); - }}, - new ReceiptItem() {{ - setTitle("App Service"); - setPrice("$ 45.00"); - setQuantity("720"); - setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png")); - }} - ); + ReceiptItem receiptDataTransfer = new ReceiptItem(); + receiptDataTransfer.setTitle("Data Transfer"); + receiptDataTransfer.setPrice("$ 38.45"); + receiptDataTransfer.setQuantity("368"); + receiptDataTransfer.setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png")); + ReceiptItem receiptAppService = new ReceiptItem(); + receiptAppService.setTitle("App Service"); + receiptAppService.setPrice("$ 45.00"); + receiptAppService.setQuantity("720"); + receiptAppService.setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png")); + receiptCard.setFacts(new Fact("Order Number", "1234"), new Fact("Payment Method", "VISA 5555-****")); + receiptCard.setItems(receiptDataTransfer, receiptAppService); receiptCard.setTax("$ 7.50"); receiptCard.setTotal("$ 90.95"); - receiptCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "More information") {{ - setImage( - "https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png"); - setValue("https://azure.microsoft.com/en-us/pricing/"); - }}); + CardAction cardAction = new CardAction(ActionTypes.OPEN_URL, "More information"); + cardAction.setImage("https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png"); + cardAction.setValue("https://azure.microsoft.com/en-us/pricing/"); + receiptCard.setButtons(cardAction); return receiptCard; } diff --git a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java index a64e090ec..546f23a7b 100644 --- a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java +++ b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java @@ -83,10 +83,11 @@ private static Attachment createAdaptiveCardAttachment(String filePath) { String adaptiveCardJson = IOUtils .toString(inputStream, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + + return attachment; } catch (IOException e) { e.printStackTrace(); diff --git a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java index 5fb06fcd8..5c3b29c4a 100644 --- a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java +++ b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java @@ -94,35 +94,30 @@ private String processInput(String text) { private Activity createSuggestedActions() { Activity reply = MessageFactory.text("What is your favorite color?"); - reply.setSuggestedActions(new SuggestedActions() { - { - setActions(Arrays.asList(new CardAction() { - { - setTitle("Red"); - setType(ActionTypes.IM_BACK); - setValue("Red"); - setImage("https://via.placeholder.com/20/FF0000?text=R"); - setImageAltText("R"); - } - }, new CardAction() { - { - setTitle("Yellow"); - setType(ActionTypes.IM_BACK); - setValue("Yellow"); - setImage("https://via.placeholder.com/20/FFFF00?text=Y"); - setImageAltText("Y"); - } - }, new CardAction() { - { - setTitle("Blue"); - setType(ActionTypes.IM_BACK); - setValue("Blue"); - setImage("https://via.placeholder.com/20/0000FF?text=B"); - setImageAltText("B"); - } - })); - } - }); + CardAction redAction = new CardAction(); + redAction.setTitle("Red"); + redAction.setType(ActionTypes.IM_BACK); + redAction.setValue("Red"); + redAction.setImage("https://via.placeholder.com/20/FF0000?text=R"); + redAction.setImageAltText("R"); + + CardAction yellowAction = new CardAction(); + yellowAction.setTitle("Yellow"); + yellowAction.setType(ActionTypes.IM_BACK); + yellowAction.setValue("Yellow"); + yellowAction.setImage("https://via.placeholder.com/20/FFFF00?text=Y"); + yellowAction.setImageAltText("Y"); + + CardAction blueAction = new CardAction(); + blueAction.setTitle("Blue"); + blueAction.setType(ActionTypes.IM_BACK); + blueAction.setValue("Blue"); + blueAction.setImage("https://via.placeholder.com/20/0000FF?text=B"); + blueAction.setImageAltText("B"); + + SuggestedActions actions = new SuggestedActions(); + actions.setActions(Arrays.asList(redAction, yellowAction, blueAction)); + reply.setSuggestedActions(actions); return reply; } diff --git a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java b/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java index c09556d79..754d0a20f 100644 --- a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java +++ b/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java @@ -22,19 +22,17 @@ public QnABot(Configuration withConfiguration) { @Override protected CompletableFuture onMessageActivity(TurnContext turnContext) { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint() {{ - setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); - setEndpointKey(configuration.getProperty("QnAEndpointKey")); - setHost(configuration.getProperty("QnAEndpointHostName")); - }}; + QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); + qnAMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); + qnAMakerEndpoint.setEndpointKey(configuration.getProperty("QnAEndpointKey")); + qnAMakerEndpoint.setHost(configuration.getProperty("QnAEndpointHostName")); QnAMaker qnaMaker = new QnAMaker(qnAMakerEndpoint, null); LoggerFactory.getLogger(QnABot.class).info("Calling QnA Maker"); - QnAMakerOptions options = new QnAMakerOptions() {{ - setTop(1); - }}; + QnAMakerOptions options = new QnAMakerOptions(); + options.setTop(1); // The actual call to the QnA Maker service. return qnaMaker.getAnswers(turnContext, options) diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java index 635ae92b3..a6b8dde11 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java @@ -75,12 +75,10 @@ private CompletableFuture initialStep(WaterfallStepContext ste return stepContext.prompt("DateTimePrompt", promptOptions); } - DateTimeResolution dateTimeResolution = new DateTimeResolution() {{ - setTimex(timex); - }}; - List dateTimeResolutions = new ArrayList() {{ - add(dateTimeResolution); - }}; + DateTimeResolution dateTimeResolution = new DateTimeResolution(); + dateTimeResolution.setTimex(timex); + List dateTimeResolutions = new ArrayList(); + dateTimeResolutions.add(dateTimeResolution); return stepContext.next(dateTimeResolutions); } diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java index 48228d02f..fa6c8e476 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java @@ -85,10 +85,10 @@ private Attachment createAdaptiveCardAttachment() { String adaptiveCardJson = IOUtils .toString(inputStream, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + return attachment; } catch (IOException e) { e.printStackTrace(); diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java index 9693b8cf0..52cc1fd15 100644 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java +++ b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java @@ -42,12 +42,8 @@ public FlightBookingRecognizer(Configuration configuration) { // Set the recognizer options depending on which endpoint version you want to use. // More details can be found in // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3( - luisApplication) { - { - setIncludeInstanceData(true); - } - }; + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); + recognizerOptions.setIncludeInstanceData(true); this.recognizer = new LuisRecognizer(recognizerOptions); } diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java index 0a00c4bda..ca77a88e1 100644 --- a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java +++ b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java @@ -76,18 +76,17 @@ private CompletableFuture sendWelcomeMessage(TurnContext turnContext) { private CompletableFuture displayOptions(TurnContext turnContext) { // Create a HeroCard with options for the user to interact with the bot. - HeroCard card = new HeroCard() {{ - setText("You can upload an image or select one of the following choices"); - - // Note that some channels require different values to be used in order to get buttons to display text. - // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may - // need to provide a value for other parameters like 'text' or 'displayText'. - setButtons( - new CardAction(ActionTypes.IM_BACK, "1. Inline Attachment", "1"), - new CardAction(ActionTypes.IM_BACK, "2. Internet Attachment", "2"), - new CardAction(ActionTypes.IM_BACK, "3. Uploaded Attachment", "3") - ); - }}; + HeroCard card = new HeroCard(); + card.setText("You can upload an image or select one of the following choices"); + + // Note that some channels require different values to be used in order to get buttons to display text. + // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may + // need to provide a value for other parameters like 'text' or 'displayText'. + card.setButtons( + new CardAction(ActionTypes.IM_BACK, "1. Inline Attachment", "1"), + new CardAction(ActionTypes.IM_BACK, "2. Internet Attachment", "2"), + new CardAction(ActionTypes.IM_BACK, "3. Uploaded Attachment", "3") + ); Activity reply = MessageFactory.attachment(card.toAttachment()); return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); @@ -200,11 +199,11 @@ private CompletableFuture getInlineAttachment() { // Creates an Attachment to be sent from the bot to the user from a HTTP URL. private CompletableFuture getInternetAttachment() { - return CompletableFuture.completedFuture(new Attachment() {{ - setName("architecture-resize.png"); - setContentType("image/png"); - setContentUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png"); - }}); + Attachment attachment = new Attachment(); + attachment.setName("architecture-resize.png"); + attachment.setContentType("image/png"); + attachment.setContentUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png"); + return CompletableFuture.completedFuture(attachment); } private CompletableFuture getUploadedAttachment(TurnContext turnContext, String serviceUrl, String conversationId) { diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java index 39ac8e1b2..90205f125 100644 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java +++ b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java @@ -104,26 +104,19 @@ protected CompletableFuture onMessageActivity(TurnContext turnContext) { // than the default, then the translation middleware will pick it up from the user state and // translate messages both ways, i.e. user to bot and bot to user. Activity reply = MessageFactory.text("Choose your language:"); - CardAction esAction = new CardAction() { - { - setTitle("Español"); - setType(ActionTypes.POST_BACK); - setValue(ENGLISH_SPANISH); - } - }; - CardAction enAction = new CardAction() { - { - setTitle("English"); - setType(ActionTypes.POST_BACK); - setValue(ENGLISH_ENGLISH); - } - }; + CardAction esAction = new CardAction(); + esAction.setTitle("Español"); + esAction.setType(ActionTypes.POST_BACK); + esAction.setValue(ENGLISH_SPANISH); + + CardAction enAction = new CardAction(); + enAction.setTitle("English"); + enAction.setType(ActionTypes.POST_BACK); + enAction.setValue(ENGLISH_ENGLISH); + List actions = new ArrayList<>(Arrays.asList(esAction, enAction)); - SuggestedActions suggestedActions = new SuggestedActions() { - { - setActions(actions); - } - }; + SuggestedActions suggestedActions = new SuggestedActions(); + suggestedActions.setActions(actions); reply.setSuggestedActions(suggestedActions); return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); } @@ -154,10 +147,10 @@ private static Attachment createAdaptiveCardAttachment() { ) { String adaptiveCardJson = IOUtils.toString(input, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + return attachment; } catch (IOException e) { e.printStackTrace(); return new Attachment(); diff --git a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java index 475bd3376..17dfaa3ac 100644 --- a/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java +++ b/samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java @@ -48,37 +48,35 @@ protected CompletableFuture onTeamsMessagingExtensio ObjectNode data = Serialization.createObjectNode(); data.set("data", Serialization.objectToTree(item)); - ThumbnailCard previewCard = new ThumbnailCard() {{ - setTitle(item[0]); - setTap(new CardAction() {{ - setType(ActionTypes.INVOKE); - setValue(Serialization.toStringSilent(data)); - }}); - }}; + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.INVOKE); + cardAction.setValue(Serialization.toStringSilent(data)); + ThumbnailCard previewCard = new ThumbnailCard(); + previewCard.setTitle(item[0]); + previewCard.setTap(cardAction); if (!StringUtils.isEmpty(item[4])) { - previewCard.setImages(Collections.singletonList(new CardImage() {{ - setUrl(item[4]); - setAlt("Icon"); - }})); + CardImage cardImage = new CardImage(); + cardImage.setUrl(item[4]); + cardImage.setAlt("Icon"); + previewCard.setImages(Collections.singletonList(cardImage)); } - MessagingExtensionAttachment attachment = new MessagingExtensionAttachment() {{ - setContentType(HeroCard.CONTENTTYPE); - setContent(new HeroCard() {{ - setTitle(item[0]); - }}); - setPreview(previewCard.toAttachment()); - }}; + HeroCard heroCard = new HeroCard(); + heroCard.setTitle(item[0]); + + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setContent(heroCard); + attachment.setPreview(previewCard.toAttachment()); attachments.add(attachment); } - MessagingExtensionResult composeExtension = new MessagingExtensionResult() {{ - setType("result"); - setAttachmentLayout("list"); - setAttachments(attachments); - }}; + MessagingExtensionResult composeExtension = new MessagingExtensionResult(); + composeExtension.setType("result"); + composeExtension.setAttachmentLayout("list"); + composeExtension.setAttachments(attachments); return new MessagingExtensionResponse(composeExtension); }); @@ -92,33 +90,31 @@ protected CompletableFuture onTeamsMessagingExtensio Map cardValue = (Map) query; List data = (ArrayList) cardValue.get("data"); - ThumbnailCard card = new ThumbnailCard() {{ - setTitle(data.get(0)); - setSubtitle(data.get(2)); - setButtons(Arrays.asList(new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setTitle("Project"); - setValue(data.get(3)); - }})); - }}; + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.OPEN_URL); + cardAction.setTitle("Project"); + cardAction.setValue(data.get(3)); + + ThumbnailCard card = new ThumbnailCard(); + card.setTitle(data.get(0)); + card.setSubtitle(data.get(2)); + card.setButtons(Arrays.asList(cardAction)); if (!StringUtils.isEmpty(data.get(4))) { - card.setImages(Collections.singletonList(new CardImage() {{ - setUrl(data.get(4)); - setAlt("Icon"); - }})); + CardImage cardImage = new CardImage(); + cardImage.setUrl(data.get(4)); + cardImage.setAlt("Icon"); + card.setImages(Collections.singletonList(cardImage)); } - MessagingExtensionAttachment attachment = new MessagingExtensionAttachment() {{ - setContentType(ThumbnailCard.CONTENTTYPE); - setContent(card); - }}; + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContentType(ThumbnailCard.CONTENTTYPE); + attachment.setContent(card); - MessagingExtensionResult composeExtension = new MessagingExtensionResult() {{ - setType("result"); - setAttachmentLayout("list"); - setAttachments(Collections.singletonList(attachment)); - }}; + MessagingExtensionResult composeExtension = new MessagingExtensionResult(); + composeExtension.setType("result"); + composeExtension.setAttachmentLayout("list"); + composeExtension.setAttachments(Collections.singletonList(attachment)); return CompletableFuture.completedFuture(new MessagingExtensionResponse(composeExtension)); } diff --git a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java index b9ed343a5..2ed3d9f5f 100644 --- a/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java +++ b/samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java @@ -44,26 +44,25 @@ private CompletableFuture createCardCommand( ) { Map actionData = (Map) action.getData(); - HeroCard card = new HeroCard() {{ - setTitle(actionData.get("title")); - setSubtitle(actionData.get("subTitle")); - setText(actionData.get("text")); - }}; - - List attachments = Arrays - .asList(new MessagingExtensionAttachment() {{ - setContent(card); - setContentType(HeroCard.CONTENTTYPE); - setPreview(card.toAttachment()); - }}); - - return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{ - setComposeExtension(new MessagingExtensionResult() {{ - setAttachments(attachments); - setAttachmentLayout("list"); - setType("result"); - }}); - }}); + HeroCard card = new HeroCard(); + card.setTitle(actionData.get("title")); + card.setSubtitle(actionData.get("subTitle")); + card.setText(actionData.get("text")); + + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContent(card); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setPreview(card.toAttachment()); + List attachments = Arrays.asList(attachment); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setAttachments(attachments); + result.setAttachmentLayout("list"); + result.setType("result"); + MessagingExtensionActionResponse response = new MessagingExtensionActionResponse(); + response.setComposeExtension(result); + + return CompletableFuture.completedFuture(response); } private CompletableFuture shareMessageCommand( @@ -72,12 +71,11 @@ private CompletableFuture shareMessageCommand( ) { Map actionData = (Map) action.getData(); - HeroCard card = new HeroCard() {{ - setTitle( - action.getMessagePayload().getFrom().getUser() != null ? action.getMessagePayload() - .getFrom().getUser().getDisplayName() : ""); - setText(action.getMessagePayload().getBody().getContent()); - }}; + HeroCard card = new HeroCard(); + card.setTitle( + action.getMessagePayload().getFrom().getUser() != null ? action.getMessagePayload() + .getFrom().getUser().getDisplayName() : ""); + card.setText(action.getMessagePayload().getBody().getContent()); if (action.getMessagePayload().getAttachments() != null && !action.getMessagePayload() .getAttachments().isEmpty()) { @@ -88,22 +86,23 @@ private CompletableFuture shareMessageCommand( Boolean.valueOf(actionData.get("includeImage")) ) : false; if (includeImage) { - card.setImages(Arrays.asList(new CardImage() {{ - setUrl( - "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU"); - }})); + CardImage cardImage = new CardImage(); + cardImage.setUrl("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU"); + card.setImages(Arrays.asList(cardImage)); } - return CompletableFuture.completedFuture(new MessagingExtensionActionResponse() {{ - setComposeExtension(new MessagingExtensionResult() {{ - setAttachmentLayout("list"); - setType("result"); - setAttachments(Arrays.asList(new MessagingExtensionAttachment() {{ - setContent(card); - setContentType(HeroCard.CONTENTTYPE); - setPreview(card.toAttachment()); - }})); - }}); - }}); + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContent(card); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setPreview(card.toAttachment()); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setAttachmentLayout("list"); + result.setType("result"); + result.setAttachments(Arrays.asList(attachment)); + + MessagingExtensionActionResponse response = new MessagingExtensionActionResponse(); + response.setComposeExtension(result); + return CompletableFuture.completedFuture(response); } } diff --git a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java index 888e23c63..b0f0c1988 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java +++ b/samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java @@ -81,16 +81,17 @@ protected CompletableFuture onTeamsMessagingExtensio } } - return new MessagingExtensionResponse(new MessagingExtensionResult() {{ - setType("config"); - setSuggestedActions(new MessagingExtensionSuggestedAction() {{ - setAction(new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setValue(String.format("%s/searchSettings.html?settings=%s", - siteUrl, escapedSettings.get())); - }}); - }}); - }}); + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.OPEN_URL); + cardAction.setValue(String.format("%s/searchSettings.html?settings=%s", siteUrl, escapedSettings.get())); + + MessagingExtensionSuggestedAction suggestedAction = new MessagingExtensionSuggestedAction(); + suggestedAction.setAction(cardAction); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setType("config"); + result.setSuggestedActions(suggestedAction); + return new MessagingExtensionResponse(result); }); } @@ -137,37 +138,35 @@ private CompletableFuture packageExtensionQuery( ObjectNode data = Serialization.createObjectNode(); data.set("data", Serialization.objectToTree(item)); - ThumbnailCard previewCard = new ThumbnailCard() {{ - setTitle(item[0]); - setTap(new CardAction() {{ - setType(ActionTypes.INVOKE); - setValue(data); - }}); - }}; + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.INVOKE); + cardAction.setValue(data); + ThumbnailCard previewCard = new ThumbnailCard(); + previewCard.setTitle(item[0]); + previewCard.setTap(cardAction); if (!StringUtils.isEmpty(item[4])) { - previewCard.setImage(new CardImage() {{ - setUrl(item[4]); - setAlt("Icon"); - }}); + CardImage cardImage = new CardImage(); + cardImage.setUrl(item[4]); + cardImage.setAlt("Icon"); + previewCard.setImage(cardImage); } - MessagingExtensionAttachment attachment = new MessagingExtensionAttachment() {{ - setContentType(HeroCard.CONTENTTYPE); - setContent(new HeroCard() {{ - setTitle(item[0]); - }}); - setPreview(previewCard.toAttachment()); - }}; + HeroCard heroCard = new HeroCard(); + heroCard.setTitle(item[0]); + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setContent(heroCard); + attachment.setPreview(previewCard.toAttachment()); attachments.add(attachment); } - return new MessagingExtensionResponse(new MessagingExtensionResult() {{ - setType("result"); - setAttachmentLayout("list"); - setAttachments(attachments); - }}); + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setType("result"); + result.setAttachmentLayout("list"); + result.setAttachments(attachments); + return new MessagingExtensionResponse(result); }); } @@ -188,23 +187,26 @@ private CompletableFuture emailExtensionQuery( .thenCompose(response -> { if (response == null || StringUtils.isEmpty(response.getToken())) { // There is no token, so the user has not signed in yet. + return tokenProvider.getOAuthSignInLink(turnContext, connectionName) - .thenApply(link -> new MessagingExtensionResponse() {{ - setComposeExtension(new MessagingExtensionResult() {{ - setType("auth"); - setSuggestedActions( - new MessagingExtensionSuggestedAction() {{ - setAction( - new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setValue(link); - setTitle("Bot Service OAuth"); - }} - ); - }} - ); - }}); - }}); + .thenApply(link -> { + MessagingExtensionResponse extensionResponse = new MessagingExtensionResponse(); + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.OPEN_URL); + cardAction.setValue(extensionResponse); + cardAction.setTitle("Bot Service OAuth"); + + MessagingExtensionSuggestedAction suggestedAction = new MessagingExtensionSuggestedAction(); + suggestedAction.setAction(cardAction); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setType("auth"); + result.setSuggestedActions(suggestedAction); + + extensionResponse.setComposeExtension(result); + + return extensionResponse; + }); } String search = ""; @@ -223,33 +225,30 @@ protected CompletableFuture onTeamsMessagingExtensio ) { Map cardValue = (Map) query; List data = (ArrayList) cardValue.get("data"); - ThumbnailCard card = new ThumbnailCard() {{ - setTitle(data.get(0)); - setSubtitle(data.get(2)); - setButtons(Arrays.asList(new CardAction() {{ - setType(ActionTypes.OPEN_URL); - setTitle("Project"); - setValue(data.get(3)); - }})); - }}; - + CardAction cardAction = new CardAction(); + cardAction.setType(ActionTypes.OPEN_URL); + cardAction.setTitle("Project"); + cardAction.setValue(data.get(3)); + ThumbnailCard card = new ThumbnailCard(); + card.setTitle(data.get(0)); + card.setSubtitle(data.get(2)); + card.setButtons(Arrays.asList(cardAction)); + if (!StringUtils.isEmpty(data.get(4))) { - card.setImage(new CardImage() {{ - setUrl(data.get(4)); - setAlt("Icon"); - }}); + CardImage cardImage = new CardImage(); + cardImage.setUrl(data.get(4)); + cardImage.setAlt("Icon"); + card.setImage(cardImage); } - MessagingExtensionAttachment attachment = new MessagingExtensionAttachment() {{ - setContentType(ThumbnailCard.CONTENTTYPE); - setContent(card); - }}; + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContentType(ThumbnailCard.CONTENTTYPE); + attachment.setContent(card); - MessagingExtensionResult composeExtension = new MessagingExtensionResult() {{ - setType("result"); - setAttachmentLayout("list"); - setAttachments(Collections.singletonList(attachment)); - }}; + MessagingExtensionResult composeExtension = new MessagingExtensionResult(); + composeExtension.setType("result"); + composeExtension.setAttachmentLayout("list"); + composeExtension.setAttachments(Collections.singletonList(attachment)); return CompletableFuture.completedFuture(new MessagingExtensionResponse(composeExtension)); } @@ -269,20 +268,25 @@ protected CompletableFuture onTeamsMessagingEx ) { if (action.getCommandId().toUpperCase().equals("SIGNOUTCOMMAND")) { UserTokenProvider tokenProvider = (UserTokenProvider) turnContext.getAdapter(); + return tokenProvider.signOutUser( turnContext, connectionName, turnContext.getActivity().getFrom().getId() - ).thenApply(response -> new MessagingExtensionActionResponse() {{ - setTask(new TaskModuleContinueResponse() {{ - setValue(new TaskModuleTaskInfo() {{ - setCard(createAdaptiveCardAttachment()); - setHeight(200); - setWidth(400); - setTitle("Adaptive Card: Inputs"); - }}); - }}); - }}); + ).thenApply(response -> { + TaskModuleTaskInfo taskInfo = new TaskModuleTaskInfo(); + taskInfo.setCard(createAdaptiveCardAttachment()); + taskInfo.setHeight(200); + taskInfo.setWidth(400); + taskInfo.setTitle("Adaptive Card: Inputs"); + + TaskModuleContinueResponse continueResponse = new TaskModuleContinueResponse(); + continueResponse.setValue(taskInfo); + + MessagingExtensionActionResponse actionResponse = new MessagingExtensionActionResponse(); + actionResponse.setTask(continueResponse); + return actionResponse; + }); } return notImplemented(); @@ -295,10 +299,10 @@ private Attachment createAdaptiveCardAttachment() { ) { String adaptiveCardJson = IOUtils.toString(input, StandardCharsets.UTF_8.toString()); - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(Serialization.jsonToTree(adaptiveCardJson)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + return attachment; } catch (IOException e) { (LoggerFactory.getLogger(TeamsMessagingExtensionsSearchAuthConfigBot.class)) .error("Unable to createAdaptiveCardAttachment", e); @@ -312,31 +316,35 @@ private MessagingExtensionResult searchMail(String text, String token) { List attachments = new ArrayList<>(); for (Message msg : messages) { - attachments.add(new MessagingExtensionAttachment() {{ - setContentType(HeroCard.CONTENTTYPE); - setContent(new HeroCard() {{ - setTitle(msg.from.emailAddress.address); - setSubtitle(msg.subject); - setText(msg.body.content); - }}); - setPreview(new ThumbnailCard() {{ - setTitle(msg.from.emailAddress.address); - setText(String.format("%s
%s", msg.subject, msg.bodyPreview)); - setImage(new CardImage() {{ - setUrl( - "https://raw.githubusercontent.com/microsoft/botbuilder-samples/master/docs/media/OutlookLogo.jpg" - ); - setAlt("Outlook logo"); - }}); - }}.toAttachment()); - }}); + + CardImage cardImage = new CardImage(); + cardImage.setUrl("https://raw.githubusercontent.com/microsoft/botbuilder-samples/master/docs/media/OutlookLogo.jpg"); + cardImage.setAlt("Outlook logo"); + + HeroCard heroCard = new HeroCard(); + heroCard.setTitle(msg.from.emailAddress.address); + heroCard.setSubtitle(msg.subject); + heroCard.setText(msg.body.content); + + ThumbnailCard thumbnailCard = new ThumbnailCard(); + thumbnailCard.setTitle(msg.from.emailAddress.address); + thumbnailCard.setText(String.format("%s
%s", msg.subject, msg.bodyPreview)); + thumbnailCard.setImage(cardImage); + + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setContent(heroCard); + attachment.setPreview(thumbnailCard.toAttachment()); + + attachments.add(attachment); } - return new MessagingExtensionResult() {{ - setType("result"); - setAttachmentLayout("list"); - setAttachments(attachments); - }}; + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setType("result"); + result.setAttachmentLayout("list"); + result.setAttachments(attachments); + + return result; } private CompletableFuture> findPackages(String text) { diff --git a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java index f8d28359c..b970eafac 100644 --- a/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java +++ b/samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java @@ -64,18 +64,20 @@ protected CompletableFuture onTeamsMessagingEx Attachment adaptiveCardEditor = getAdaptiveCardAttachment("adaptiveCardEditor.json"); - return CompletableFuture.completedFuture( - new MessagingExtensionActionResponse(){{ - setTask(new TaskModuleContinueResponse(){{ - setValue(new TaskModuleTaskInfo(){{ - setCard(adaptiveCardEditor); - setWidth(500); - setHeight(450); - setTitle("Task Module Fetch Example"); - }}); - setType("continue"); - }}); - }}); + TaskModuleTaskInfo taskInfo = new TaskModuleTaskInfo(); + taskInfo.setCard(adaptiveCardEditor); + taskInfo.setWidth(500); + taskInfo.setHeight(450); + taskInfo.setTitle("Task Module Fetch Example"); + + TaskModuleContinueResponse continueResponse = new TaskModuleContinueResponse(); + continueResponse.setValue(taskInfo); + continueResponse.setType("continue"); + + MessagingExtensionActionResponse actionResponse = new MessagingExtensionActionResponse(); + actionResponse.setTask(continueResponse); + + return CompletableFuture.completedFuture(actionResponse); } @Override @@ -87,12 +89,14 @@ protected CompletableFuture onTeamsMessagingEx updateAttachmentAdaptiveCard(adaptiveCard, action); - return CompletableFuture.completedFuture(new MessagingExtensionActionResponse(){{ - setComposeExtension(new MessagingExtensionResult(){{ - setType("botMessagePreview"); - setActivityPreview(MessageFactory.attachment(adaptiveCard)); - }}); - }}); + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setType("botMessagePreview"); + result.setActivityPreview(MessageFactory.attachment(adaptiveCard)); + + MessagingExtensionActionResponse response = new MessagingExtensionActionResponse(); + response.setComposeExtension(result); + + return CompletableFuture.completedFuture(response); } @Override @@ -107,17 +111,20 @@ protected CompletableFuture onTeamsMessagingEx Attachment previewCard = preview.getAttachments().get(0); updateAttachmentAdaptiveCardEdit(adaptiveCard, previewCard); - return CompletableFuture.completedFuture(new MessagingExtensionActionResponse(){{ - setTask(new TaskModuleContinueResponse(){{ - setValue(new TaskModuleTaskInfo(){{ - setCard(adaptiveCard); - setHeight(450); - setWidth(500); - setTitle("Task Module Fetch Example"); - }}); - setType("continue"); - }}); - }}); + TaskModuleTaskInfo taskInfo = new TaskModuleTaskInfo(); + taskInfo.setCard(adaptiveCard); + taskInfo.setHeight(450); + taskInfo.setWidth(500); + taskInfo.setTitle("Task Module Fetch Example"); + + TaskModuleContinueResponse continueResponse = new TaskModuleContinueResponse(); + continueResponse.setValue(taskInfo); + continueResponse.setType("continue"); + + MessagingExtensionActionResponse actionResponse = new MessagingExtensionActionResponse(); + actionResponse.setTask(continueResponse); + + return CompletableFuture.completedFuture(actionResponse); } @Override @@ -153,10 +160,11 @@ private Attachment getAdaptiveCardAttachment(String fileName) { .getResourceAsStream(fileName); String content = IOUtils.toString(input, StandardCharsets.UTF_8); - return new Attachment(){{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(new ObjectMapper().readValue(content, AdaptiveCard.class)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(new ObjectMapper().readValue(content, AdaptiveCard.class)); + + return attachment; } catch (IOException e) { LoggerFactory.getLogger(TeamsMessagingExtensionsActionPreviewBot.class) .error("getAdaptiveCardAttachment", e); diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java index 6d3065b36..1c3898ba3 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java @@ -143,17 +143,16 @@ private void setTaskInfo(TaskModuleTaskInfo taskInfo, UISettings uiSettings) { } private Attachment getTaskModuleHeroCardOptions() { - List buttons = actions.stream().map(cardType -> - new TaskModuleAction(cardType.getButtonTitle(), - new CardTaskFetchValue() {{ - setData(cardType.getId()); - }}) - ).collect(Collectors.toCollection(ArrayList::new)); - - HeroCard card = new HeroCard() {{ - setTitle("Task Module Invocation from Hero Card"); - setButtons(buttons); - }}; + List buttons = actions.stream().map(cardType -> { + CardTaskFetchValue fetchValue = new CardTaskFetchValue(); + fetchValue.setData(cardType.getId()); + TaskModuleAction moduleAction = new TaskModuleAction(cardType.getButtonTitle(), fetchValue); + return moduleAction; + }).collect(Collectors.toCollection(ArrayList::new)); + + HeroCard card = new HeroCard(); + card.setTitle("Task Module Invocation from Hero Card"); + card.setButtons(buttons); return card.toAttachment(); } @@ -164,12 +163,12 @@ private Attachment getTaskModuleAdaptiveCardOptions() { String cardTemplate = IOUtils.toString(inputStream, StandardCharsets.UTF_8); List> cardActions = actions.stream().map(cardType -> { + AdaptiveCardTaskFetchValue fetchValue = new AdaptiveCardTaskFetchValue(); + fetchValue.setData(cardType.getId()); Map a = new HashMap<>(); a.put("type", "Action.Submit"); a.put("title", cardType.getButtonTitle()); - a.put("data", new AdaptiveCardTaskFetchValue() {{ - setData(cardType.getId()); - }}); + a.put("data", fetchValue); return a; }).collect(Collectors.toCollection(ArrayList::new)); @@ -193,9 +192,9 @@ private Attachment createAdaptiveCardAttachment() { } private Attachment adaptiveCardAttachmentFromJson(String json) throws IOException { - return new Attachment() {{ - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(new ObjectMapper().readValue(json, ObjectNode.class)); - }}; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(new ObjectMapper().readValue(json, ObjectNode.class)); + return attachment; } } diff --git a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleResponseFactory.java b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleResponseFactory.java index 903148262..2ee19b341 100644 --- a/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleResponseFactory.java +++ b/samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleResponseFactory.java @@ -10,19 +10,19 @@ public final class TaskModuleResponseFactory { public static TaskModuleResponse createResponse(String message) { - return new TaskModuleResponse() {{ - setTask(new TaskModuleMessageResponse() {{ - setValue(message); - }}); - }}; + TaskModuleMessageResponse taskModuleMessageResponse = new TaskModuleMessageResponse(); + taskModuleMessageResponse.setValue(message); + TaskModuleResponse taskModuleResponse = new TaskModuleResponse(); + taskModuleResponse.setTask(taskModuleMessageResponse); + return taskModuleResponse; } public static TaskModuleResponse createResponse(TaskModuleTaskInfo taskInfo) { - return new TaskModuleResponse() {{ - setTask(new TaskModuleContinueResponse() {{ - setValue(taskInfo); - }}); - }}; + TaskModuleContinueResponse taskModuleContinueResponse = new TaskModuleContinueResponse(); + taskModuleContinueResponse.setValue(taskInfo); + TaskModuleResponse taskModuleResponse = new TaskModuleResponse(); + taskModuleResponse.setTask(taskModuleContinueResponse); + return taskModuleResponse; } public static TaskModuleResponse toTaskModuleResponse(TaskModuleTaskInfo taskInfo) { diff --git a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java index 969008cb0..49586afe1 100644 --- a/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java +++ b/samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java @@ -31,25 +31,22 @@ protected CompletableFuture onTeamsAppBasedLinkQuery TurnContext turnContext, AppBasedLinkQuery query ) { - ThumbnailCard card = new ThumbnailCard() {{ - setTitle("Thumbnail Card"); - setText(query.getUrl()); - setImages(Collections.singletonList( - new CardImage( - "https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png") - )); - }}; + ThumbnailCard card = new ThumbnailCard(); + card.setTitle("Thumbnail Card"); + card.setText(query.getUrl()); + card.setImages(Collections.singletonList( + new CardImage("https://raw.githubusercontent.com/microsoft/botframework-sdk/master/icon.png") + )); - MessagingExtensionAttachment attachments = new MessagingExtensionAttachment() {{ - setContentType(HeroCard.CONTENTTYPE); - setContent(card); - }}; - MessagingExtensionResult result = new MessagingExtensionResult() {{ - setAttachmentLayout("list"); - setType("result"); - setAttachments(Collections.singletonList(attachments)); - }}; + MessagingExtensionAttachment attachments = new MessagingExtensionAttachment(); + attachments.setContentType(HeroCard.CONTENTTYPE); + attachments.setContent(card); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setAttachmentLayout("list"); + result.setType("result"); + result.setAttachments(Collections.singletonList(attachments)); return CompletableFuture.completedFuture(new MessagingExtensionResponse(result)); } @@ -63,26 +60,23 @@ protected CompletableFuture onTeamsMessagingExtensio // These commandIds are defined in the Teams App Manifest. if (StringUtils.equalsIgnoreCase("searchQuery", query.getCommandId())) { - HeroCard card = new HeroCard() {{ - setTitle("This is a Link Unfurling Sample"); - setSubtitle("It will unfurl links from *.BotFramework.com"); - setText("This sample demonstrates how to handle link unfurling in Teams. " - + "Please review the readme for more information."); - }}; + HeroCard card = new HeroCard(); + card.setTitle("This is a Link Unfurling Sample"); + card.setSubtitle("It will unfurl links from *.BotFramework.com"); + card.setText("This sample demonstrates how to handle link unfurling in Teams. " + + "Please review the readme for more information."); + + MessagingExtensionAttachment attachment = new MessagingExtensionAttachment(); + attachment.setContent(card); + attachment.setContentType(HeroCard.CONTENTTYPE); + attachment.setPreview(card.toAttachment()); + + MessagingExtensionResult result = new MessagingExtensionResult(); + result.setAttachmentLayout("list"); + result.setType("result"); + result.setAttachment(attachment); - return CompletableFuture.completedFuture(new MessagingExtensionResponse( - new MessagingExtensionResult() {{ - setAttachmentLayout("list"); - setType("result"); - setAttachment( - new MessagingExtensionAttachment() {{ - setContent(card); - setContentType(HeroCard.CONTENTTYPE); - setPreview(card.toAttachment()); - }} - ); - }} - )); + return CompletableFuture.completedFuture(new MessagingExtensionResponse(result)); } return notImplemented(String.format("Invalid CommandId: %s", query.getCommandId())); diff --git a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java index 0d602b8c6..2683a084f 100644 --- a/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java +++ b/samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java @@ -89,18 +89,16 @@ private CompletableFuture sendFileCard( Map consentContext = new HashMap<>(); consentContext.put("filename", filename); - FileConsentCard fileCard = new FileConsentCard() {{ - setDescription("This is the file I want to send you"); - setSizeInBytes(filesize); - setAcceptContext(consentContext); - setDeclineContext(consentContext); - }}; - - Attachment asAttachment = new Attachment() {{ - setContent(fileCard); - setContentType(FileConsentCard.CONTENT_TYPE); - setName(filename); - }}; + FileConsentCard fileCard = new FileConsentCard(); + fileCard.setDescription("This is the file I want to send you"); + fileCard.setSizeInBytes(filesize); + fileCard.setAcceptContext(consentContext); + fileCard.setDeclineContext(consentContext); + + Attachment asAttachment = new Attachment(); + asAttachment.setContent(fileCard); + asAttachment.setContentType(FileConsentCard.CONTENT_TYPE); + asAttachment.setName(filename); Activity reply = turnContext.getActivity().createReply(); reply.setAttachments(Collections.singletonList(asAttachment)); @@ -111,17 +109,15 @@ private CompletableFuture sendFileCard( private CompletableFuture fileUploadCompleted( TurnContext turnContext, FileConsentCardResponse fileConsentCardResponse ) { - FileInfoCard downloadCard = new FileInfoCard() {{ - setUniqueId(fileConsentCardResponse.getUploadInfo().getUniqueId()); - setFileType(fileConsentCardResponse.getUploadInfo().getFileType()); - }}; - - Attachment asAttachment = new Attachment() {{ - setContent(downloadCard); - setContentType(FileInfoCard.CONTENT_TYPE); - setName(fileConsentCardResponse.getUploadInfo().getName()); - setContentUrl(fileConsentCardResponse.getUploadInfo().getContentUrl()); - }}; + FileInfoCard downloadCard = new FileInfoCard(); + downloadCard.setUniqueId(fileConsentCardResponse.getUploadInfo().getUniqueId()); + downloadCard.setFileType(fileConsentCardResponse.getUploadInfo().getFileType()); + + Attachment asAttachment = new Attachment(); + asAttachment.setContent(downloadCard); + asAttachment.setContentType(FileInfoCard.CONTENT_TYPE); + asAttachment.setName(fileConsentCardResponse.getUploadInfo().getName()); + asAttachment.setContentUrl(fileConsentCardResponse.getUploadInfo().getContentUrl()); Activity reply = MessageFactory.text( String.format( diff --git a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java index b4f89b657..ca8997a13 100644 --- a/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java +++ b/samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java @@ -72,13 +72,10 @@ protected CompletableFuture onMessageActivity(TurnContext turnContext) { int count = 0; }; - HeroCard card = new HeroCard() { - { - setTitle("Welcome Card"); - setText("Click the buttons below to update this card"); - setButtons(getHeroCardButtons(value)); - } - }; + HeroCard card = new HeroCard(); + card.setTitle("Welcome Card"); + card.setText("Click the buttons below to update this card"); + card.setButtons(getHeroCardButtons(value)); return turnContext.sendActivity(MessageFactory.attachment(card.toAttachment())) .thenApply(resourceResponse -> null); @@ -130,14 +127,11 @@ private CompletableFuture messageAllMembers(TurnContext turnContext) { + ". I'm a Teams conversation bot." ); - ConversationParameters conversationParameters = new ConversationParameters() { - { - setIsGroup(false); - setBot(turnContext.getActivity().getRecipient()); - setMembers(Collections.singletonList(member)); - setTenantId(turnContext.getActivity().getConversation().getTenantId()); - } - }; + ConversationParameters conversationParameters = new ConversationParameters(); + conversationParameters.setIsGroup(false); + conversationParameters.setBot(turnContext.getActivity().getRecipient()); + conversationParameters.setMembers(Collections.singletonList(member)); + conversationParameters.setTenantId(turnContext.getActivity().getConversation().getTenantId()); conversations.add( ((BotFrameworkAdapter) turnContext.getAdapter()).createConversation( @@ -170,13 +164,10 @@ private CompletableFuture updateCardActivity(TurnContext turnContext) { Map data = (Map) turnContext.getActivity().getValue(); data.put("count", (int) data.get("count") + 1); - HeroCard card = new HeroCard() { - { - setTitle("Welcome Card"); - setText("Update count - " + data.get("count")); - setButtons(getHeroCardButtons(data)); - } - }; + HeroCard card = new HeroCard(); + card.setTitle("Welcome Card"); + card.setText("Update count - " + data.get("count")); + card.setButtons(getHeroCardButtons(data)); Activity updatedActivity = MessageFactory.attachment(card.toAttachment()); updatedActivity.setId(turnContext.getActivity().getReplyToId()); @@ -185,32 +176,28 @@ private CompletableFuture updateCardActivity(TurnContext turnContext) { } private List getHeroCardButtons(Object value) { - return Arrays.asList(new CardAction() { - { - setType(ActionTypes.MESSAGE_BACK); - setTitle("Update Card"); - setText("UpdateCardAction"); - setValue(value); - } - }, new CardAction() { - { - setType(ActionTypes.MESSAGE_BACK); - setTitle("Message All Members"); - setText("MessageAllMembers"); - } - }, new CardAction() { - { - setType(ActionTypes.MESSAGE_BACK); - setTitle("Delete card"); - setText("Delete"); - } - }, new CardAction() { - { - setType(ActionTypes.MESSAGE_BACK); - setTitle("Who am I?"); - setText("MentionMe"); - } - }); + CardAction updateAction = new CardAction(); + updateAction.setType(ActionTypes.MESSAGE_BACK); + updateAction.setTitle("Update Card"); + updateAction.setText("UpdateCardAction"); + updateAction.setValue(value); + + CardAction allMembersAction = new CardAction(); + allMembersAction.setType(ActionTypes.MESSAGE_BACK); + allMembersAction.setTitle("Message All Members"); + allMembersAction.setText("MessageAllMembers"); + + CardAction deleteAction = new CardAction(); + deleteAction.setType(ActionTypes.MESSAGE_BACK); + deleteAction.setTitle("Delete card"); + deleteAction.setText("Delete"); + + CardAction mentionAction = new CardAction(); + mentionAction.setType(ActionTypes.MESSAGE_BACK); + mentionAction.setTitle("Who am I?"); + mentionAction.setText("MentionMe"); + + return Arrays.asList(updateAction, allMembersAction, deleteAction, mentionAction); } private CompletableFuture mentionActivity(TurnContext turnContext) { diff --git a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java index ec2e0d8ae..44b06a2a5 100644 --- a/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java +++ b/samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java @@ -51,11 +51,10 @@ protected CompletableFuture onMessageActivity( .set("id", JsonNodeFactory.instance.textNode(teamsChannelId)) ); - ConversationParameters conversationParameters = new ConversationParameters(){{ - setIsGroup(true); - setActivity(message); - setChannelData(channelData); - }}; + ConversationParameters conversationParameters = new ConversationParameters(); + conversationParameters.setIsGroup(true); + conversationParameters.setActivity(message); + conversationParameters.setChannelData(channelData); BotFrameworkAdapter adapter = (BotFrameworkAdapter)turnContext.getAdapter(); diff --git a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java index 96b692f0a..7a8e3c63a 100644 --- a/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java +++ b/samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java @@ -82,12 +82,11 @@ private Attachment createAdaptiveCardAttachment(String fileName) { IOUtils.copy(bomIn, writer, StandardCharsets.UTF_8); content = writer.toString(); bomIn.close(); - return new Attachment() { - { - setContentType("application/vnd.microsoft.card.adaptive"); - setContent(new JacksonAdapter().serializer().readValue(content, AdaptiveCard.class)); - } - }; + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(new JacksonAdapter().serializer().readValue(content, AdaptiveCard.class)); + + return attachment; } catch (IOException e) { LoggerFactory.getLogger(RootBot.class).error("createAdaptiveCardAttachment", e); } From d4c8cb7106ab073c79635e6c0e6c5985e18b52cc Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 29 Mar 2021 08:59:13 -0500 Subject: [PATCH 122/221] Added Action.Execute support (#1104) Co-authored-by: tracyboehrer --- .../bot/builder/ActivityHandler.java | 89 +++++++++++++++++ .../bot/builder/ActivityHandlerTests.java | 55 +++++++++++ .../schema/AdaptiveCardAuthentication.java | 75 +++++++++++++++ .../bot/schema/AdaptiveCardInvokeAction.java | 95 +++++++++++++++++++ .../schema/AdaptiveCardInvokeResponse.java | 75 +++++++++++++++ .../bot/schema/AdaptiveCardInvokeValue.java | 78 +++++++++++++++ 6 files changed, 467 insertions(+) create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardAuthentication.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeAction.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeResponse.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeValue.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java index e9c4b6918..74d7f5ed3 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/ActivityHandler.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.fasterxml.jackson.databind.JsonNode; import com.microsoft.bot.connector.Async; import java.net.HttpURLConnection; import java.util.List; @@ -13,9 +14,12 @@ import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.AdaptiveCardInvokeResponse; +import com.microsoft.bot.schema.AdaptiveCardInvokeValue; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.MessageReaction; import com.microsoft.bot.schema.ResourceResponse; +import com.microsoft.bot.schema.Serialization; import com.microsoft.bot.schema.SignInConstants; /** @@ -398,6 +402,16 @@ protected CompletableFuture onEventActivity(TurnContext turnContext) { * @return A task that represents the work queued to execute. */ protected CompletableFuture onInvokeActivity(TurnContext turnContext) { + if (StringUtils.equals(turnContext.getActivity().getName(), "adaptiveCard/action")) { + AdaptiveCardInvokeValue invokeValue = null; + try { + invokeValue = getAdaptiveCardInvokeValue(turnContext.getActivity()); + } catch (InvokeResponseException e) { + return Async.completeExceptionally(e); + } + return onAdaptiveCardInvoke(turnContext, invokeValue).thenApply(result -> createInvokeResponse(result)); + } + if ( StringUtils.equals( turnContext.getActivity().getName(), SignInConstants.VERIFY_STATE_OPERATION_NAME @@ -612,6 +626,25 @@ protected CompletableFuture onInstallationUpdateRemove(TurnContext turnCon return CompletableFuture.completedFuture(null); } + /** + * Invoked when the bot is sent an Adaptive Card Action Execute. + * + * @param turnContext A strongly-typed context Object for this + * turn. + * @param invokeValue A stringly-typed Object from the incoming + * activity's Value. + * + * @return A task that represents the work queued to execute. + * + * When the {@link OnInvokeActivity(TurnContext{InvokeActivity})} method + * receives an Invoke with a {@link InvokeActivity#name} of + * `adaptiveCard/action`, it calls this method. + */ + protected CompletableFuture onAdaptiveCardInvoke( + TurnContext turnContext, AdaptiveCardInvokeValue invokeValue) { + return Async.completeExceptionally(new InvokeResponseException(HttpURLConnection.HTTP_NOT_IMPLEMENTED)); + } + /** * Override this in a derived class to provide logic specific to * ActivityTypes.END_OF_CONVERSATION activities. @@ -662,6 +695,62 @@ protected CompletableFuture onUnrecognizedActivityType(TurnContext turnCon return CompletableFuture.completedFuture(null); } + private AdaptiveCardInvokeValue getAdaptiveCardInvokeValue(Activity activity) throws InvokeResponseException { + if (activity.getValue() == null) { + AdaptiveCardInvokeResponse response = createAdaptiveCardInvokeErrorResponse( + HttpURLConnection.HTTP_BAD_REQUEST, "BadRequest", "Missing value property"); + throw new InvokeResponseException(HttpURLConnection.HTTP_BAD_REQUEST, response); + } + + Object obj = activity.getValue(); + JsonNode node = null; + if (obj instanceof JsonNode) { + node = (JsonNode) obj; + } else { + AdaptiveCardInvokeResponse response = createAdaptiveCardInvokeErrorResponse( + HttpURLConnection.HTTP_BAD_REQUEST, "BadRequest", "Value property instanceof not properly formed"); + throw new InvokeResponseException(HttpURLConnection.HTTP_BAD_REQUEST, response); + } + + AdaptiveCardInvokeValue invokeValue = Serialization.treeToValue(node, AdaptiveCardInvokeValue.class); + if (invokeValue == null) { + AdaptiveCardInvokeResponse response = createAdaptiveCardInvokeErrorResponse( + HttpURLConnection.HTTP_BAD_REQUEST, "BadRequest", "Value property instanceof not properly formed"); + throw new InvokeResponseException(HttpURLConnection.HTTP_BAD_REQUEST, response); + } + + if (invokeValue.getAction() == null) { + AdaptiveCardInvokeResponse response = createAdaptiveCardInvokeErrorResponse( + HttpURLConnection.HTTP_BAD_REQUEST, "BadRequest", "Missing action property"); + throw new InvokeResponseException(HttpURLConnection.HTTP_BAD_REQUEST, response); + } + + if (!invokeValue.getAction().getType().equals("Action.Execute")) { + AdaptiveCardInvokeResponse response = createAdaptiveCardInvokeErrorResponse( + HttpURLConnection.HTTP_BAD_REQUEST, "NotSupported", + String.format("The action '%s'is not supported.", invokeValue.getAction().getType())); + throw new InvokeResponseException(HttpURLConnection.HTTP_BAD_REQUEST, response); + } + + return invokeValue; + } + + private AdaptiveCardInvokeResponse createAdaptiveCardInvokeErrorResponse( + Integer statusCode, + String code, + String message + ) { + AdaptiveCardInvokeResponse adaptiveCardInvokeResponse = new AdaptiveCardInvokeResponse(); + adaptiveCardInvokeResponse.setStatusCode(statusCode); + adaptiveCardInvokeResponse.setType("application/vnd.getmicrosoft().error"); + com.microsoft.bot.schema.Error error = new com.microsoft.bot.schema.Error(); + error.setCode(code); + error.setMessage(message); + adaptiveCardInvokeResponse.setValue(error); + return adaptiveCardInvokeResponse; + } + + /** * InvokeResponse Exception. */ diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java index e12802fe3..a5c41c686 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/ActivityHandlerTests.java @@ -3,6 +3,7 @@ package com.microsoft.bot.builder; +import com.fasterxml.jackson.databind.JsonNode; import com.microsoft.bot.connector.Async; import com.microsoft.bot.schema.*; import org.junit.Assert; @@ -97,6 +98,33 @@ public void TestInstallationUpdateRemoveUpgrade() { Assert.assertEquals("onInstallationUpdateRemove", bot.getRecord().get(1)); } + @Test + public void TestOnAdaptiveCardInvoke() { + + AdaptiveCardInvokeValue adaptiveCardInvokeValue = new AdaptiveCardInvokeValue(); + AdaptiveCardInvokeAction adaptiveCardInvokeAction = new AdaptiveCardInvokeAction(); + adaptiveCardInvokeAction.setType("Action.Execute"); + adaptiveCardInvokeValue.setAction(adaptiveCardInvokeAction); + + JsonNode node = Serialization.objectToTree(adaptiveCardInvokeValue); + Activity activity = new Activity() { + { + setType(ActivityTypes.INVOKE); + setName("adaptiveCard/action"); + setValue(node); + } + }; + + TurnContext turnContext = new TurnContextImpl(new TestInvokeAdapter(), activity); + + TestActivityHandler bot = new TestActivityHandler(); + bot.onTurn(turnContext).join(); + + Assert.assertEquals(2, bot.getRecord().size()); + Assert.assertEquals("onInvokeActivity", bot.getRecord().get(0)); + Assert.assertEquals("onAdaptiveCardInvoke", bot.getRecord().get(1)); + } + @Test public void TestOnTypingActivity() { Activity activity = new Activity(ActivityTypes.TYPING); @@ -375,6 +403,26 @@ public void TestUnrecognizedActivityType() { Assert.assertEquals("onUnrecognizedActivityType", bot.getRecord().get(0)); } + private class TestInvokeAdapter extends NotImplementedAdapter { + + private Activity activity; + + public Activity getActivity() { + return activity; + } + + public CompletableFuture sendActivities( + TurnContext context, + List activities + ) { + activity = activities.stream() + .filter(x -> x.getType().equals(ActivityTypes.INVOKE_RESPONSE)) + .findFirst() + .get(); + return CompletableFuture.completedFuture(new ResourceResponse[0]); + } + } + private static class NotImplementedAdapter extends BotAdapter { @Override public CompletableFuture sendActivities( @@ -532,5 +580,12 @@ protected CompletableFuture onCommandResultActivity(TurnContext turnContext) { return super.onCommandResultActivity(turnContext); } + @Override + protected CompletableFuture onAdaptiveCardInvoke( + TurnContext turnContext, AdaptiveCardInvokeValue invokeValue) { + record.add("onAdaptiveCardInvoke"); + return CompletableFuture.completedFuture(new AdaptiveCardInvokeResponse()); + } + } } diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardAuthentication.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardAuthentication.java new file mode 100644 index 000000000..9acedd10a --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardAuthentication.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that arrives in the Activity.getValue().Authentication + * for Invoke activity with Name of 'adaptiveCard/action'. + */ +public class AdaptiveCardAuthentication { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "connectionName") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String connectionName; + + @JsonProperty(value = "token") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String token; + + /** + * Gets the Id of the adaptive card invoke authentication. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * Sets the Id of the adaptive card invoke authentication. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + + /** + * Gets the connection name of the adaptive card authentication. + * @return the ConnectionName value as a String. + */ + public String getConnectionName() { + return this.connectionName; + } + + /** + * Sets the connection name of the adaptive card authentication. + * @param withConnectionName The ConnectionName value. + */ + public void setConnectionName(String withConnectionName) { + this.connectionName = withConnectionName; + } + + /** + * Gets the token of the adaptive card authentication. + * @return the Token value as a String. + */ + public String getToken() { + return this.token; + } + + /** + * Sets the token of the adaptive card authentication. + * @param withToken The Token value. + */ + public void setToken(String withToken) { + this.token = withToken; + } + +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeAction.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeAction.java new file mode 100644 index 000000000..afa60915a --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeAction.java @@ -0,0 +1,95 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that arrives in the Activity.getValue().Action for + * Invoke activity with Name of 'adaptiveCard/action'. + */ +public class AdaptiveCardInvokeAction { + + @JsonProperty(value = "type") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String type; + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + @JsonProperty(value = "verb") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String verb; + + @JsonProperty(value = "data") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Object data; + + /** + * Gets the Type of this adaptive card action invoke. + * @return the Type value as a String. + */ + public String getType() { + return this.type; + } + + /** + * Sets the Type of this adaptive card action invoke. + * @param withType The Type value. + */ + public void setType(String withType) { + this.type = withType; + } + + /** + * Gets the Id of this adaptive card action invoke. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * Sets the Id of this adaptive card action invoke. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } + + /** + * Gets the Verb of this adaptive card action invoke. + * @return the Verb value as a String. + */ + public String getVerb() { + return this.verb; + } + + /** + * Sets the Verb of this adaptive card action invoke. + * @param withVerb The Verb value. + */ + public void setVerb(String withVerb) { + this.verb = withVerb; + } + + /** + * Gets the Data of this adaptive card action invoke. + * @return the Data value as a Object. + */ + public Object getData() { + return this.data; + } + + /** + * Sets the Data of this adaptive card action invoke. + * @param withData The Data value. + */ + public void setData(Object withData) { + this.data = withData; + } + +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeResponse.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeResponse.java new file mode 100644 index 000000000..d30fdac0f --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeResponse.java @@ -0,0 +1,75 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that is returned as the result of an Invoke activity + * with Name of 'adaptiveCard/action'. + */ +public class AdaptiveCardInvokeResponse { + + @JsonProperty(value = "statusCode") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private int statusCode; + + @JsonProperty(value = "type") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String type; + + @JsonProperty(value = "value") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private Object value; + + /** + * Gets the Card Action response getStatusCode(). + * @return the StatusCode value as a int. + */ + public int getStatusCode() { + return this.statusCode; + } + + /** + * Sets the Card Action response getStatusCode(). + * @param withStatusCode The StatusCode value. + */ + public void setStatusCode(int withStatusCode) { + this.statusCode = withStatusCode; + } + + /** + * Gets the Type of this {@link AdaptiveCardInvokeResponse} . + * @return the Type value as a String. + */ + public String getType() { + return this.type; + } + + /** + * Sets the Type of this {@link AdaptiveCardInvokeResponse} . + * @param withType The Type value. + */ + public void setType(String withType) { + this.type = withType; + } + + /** + * Gets the json response Object. + * @return the Value value as a Object. + */ + public Object getValue() { + return this.value; + } + + /** + * Sets the json response Object. + * @param withValue The Value value. + */ + public void setValue(Object withValue) { + this.value = withValue; + } + +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeValue.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeValue.java new file mode 100644 index 000000000..c64764d51 --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/AdaptiveCardInvokeValue.java @@ -0,0 +1,78 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + + +package com.microsoft.bot.schema; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines the structure that arrives in the Activity.Value for Invoke activity + * with Name of 'adaptiveCard/action'. + */ +public class AdaptiveCardInvokeValue { + + @JsonProperty(value = "action") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private AdaptiveCardInvokeAction action; + + @JsonProperty(value = "authentication") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private AdaptiveCardAuthentication authentication; + + @JsonProperty(value = "state") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String state; + + /** + * Gets the action of this adaptive card action invoke value. + * @return the Action value as a AdaptiveCardInvokeAction. + */ + public AdaptiveCardInvokeAction getAction() { + return this.action; + } + + /** + * Sets the action of this adaptive card action invoke value. + * @param withAction The Action value. + */ + public void setAction(AdaptiveCardInvokeAction withAction) { + this.action = withAction; + } + + /** + * Gets the {@link AdaptiveCardAuthentication} for this adaptive + * card invoke action value. + * @return the Authentication value as a AdaptiveCardAuthentication. + */ + public AdaptiveCardAuthentication getAuthentication() { + return this.authentication; + } + + /** + * Sets the {@link AdaptiveCardAuthentication} for this adaptive + * card invoke action value. + * @param withAuthentication The Authentication value. + */ + public void setAuthentication(AdaptiveCardAuthentication withAuthentication) { + this.authentication = withAuthentication; + } + + /** + * Gets the 'state' or magic code for an OAuth flow. + * @return the State value as a String. + */ + public String getState() { + return this.state; + } + + /** + * Sets the 'state' or magic code for an OAuth flow. + * @param withState The State value. + */ + public void setState(String withState) { + this.state = withState; + } + +} From e43a7dd123e719e938ad3abbb4f75ac66e92815d Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 29 Mar 2021 13:57:41 -0500 Subject: [PATCH 123/221] Fixed Azure Storage Tests on MacOS (#1107) * Update to detect on MacOS * Correct path for storage emulator --- .../bot/azure/AzureEmulatorUtils.java | 36 +++ .../microsoft/bot/azure/AzureQueueTests.java | 30 +-- .../bot/azure/TranscriptStoreTests.java | 236 ++++++++---------- .../bot/azure/blobs/BlobsStorageTests.java | 74 ++---- 4 files changed, 159 insertions(+), 217 deletions(-) create mode 100644 libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureEmulatorUtils.java diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureEmulatorUtils.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureEmulatorUtils.java new file mode 100644 index 000000000..ca79b74ac --- /dev/null +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureEmulatorUtils.java @@ -0,0 +1,36 @@ +package com.microsoft.bot.azure; + +import java.io.File; +import java.io.IOException; + +public class AzureEmulatorUtils { + + private static boolean isStorageEmulatorAvailable = false; + private static boolean hasStorageEmulatorBeenTested = false; + private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; + + public static boolean isStorageEmulatorAvailable() { + if (!hasStorageEmulatorBeenTested) { + try { + File emulator = new File(System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe"); + if (emulator.exists()) { + Process p = Runtime.getRuntime().exec("cmd /C \"" + System.getenv("ProgramFiles") + + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); + int result = p.waitFor(); + // status = 0: the service was started. + // status = -5: the service is already started. Only one instance of the + // application + // can be run at the same time. + isStorageEmulatorAvailable = result == 0 || result == -5; + } else { + isStorageEmulatorAvailable = false; + } + } catch (IOException | InterruptedException ex) { + isStorageEmulatorAvailable = false; + System.out.println(NO_EMULATOR_MESSAGE); + } + hasStorageEmulatorBeenTested = true; + } + return isStorageEmulatorAvailable; + } +} diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java index 663e7b826..eb51bba8a 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/AzureQueueTests.java @@ -25,6 +25,7 @@ import com.microsoft.bot.schema.ConversationReference; import org.apache.commons.codec.binary.Base64; import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; @@ -42,20 +43,7 @@ public class AzureQueueTests { private static final Integer DEFAULT_DELAY = 2000; - private static boolean emulatorIsRunning = false; private final String connectionString = "UseDevelopmentStorage=true"; - private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; - - @BeforeClass - public static void allTestsInit() throws IOException, InterruptedException { - Process p = Runtime.getRuntime().exec - ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); - int result = p.waitFor(); - // status = 0: the service was started. - // status = -5: the service is already started. Only one instance of the application - // can be run at the same time. - emulatorIsRunning = result == 0 || result == -5; - } // These tests require Azure Storage Emulator v5.7 public QueueClient containerInit(String name) { @@ -68,9 +56,13 @@ public QueueClient containerInit(String name) { return queue; } + @Before + public void beforeTest() { + org.junit.Assume.assumeTrue(AzureEmulatorUtils.isStorageEmulatorAvailable()); + } + @Test public void continueConversationLaterTests() { - if (runIfEmulator()) { String queueName = "continueconversationlatertests"; QueueClient queue = containerInit(queueName); @@ -128,16 +120,6 @@ public void continueConversationLaterTests() { e.printStackTrace(); Assert.fail(); } - } - } - - private boolean runIfEmulator() { - if (!emulatorIsRunning) { - System.out.println(NO_EMULATOR_MESSAGE); - return false; - } - - return true; } private class ContinueConversationLater extends Dialog { diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java index 668971103..cb54cbfba 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/TranscriptStoreTests.java @@ -24,12 +24,11 @@ import org.apache.commons.lang3.StringUtils; import org.junit.After; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; -import java.io.IOException; import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.List; @@ -40,25 +39,23 @@ import java.util.concurrent.TimeoutException; /** - * These tests require Azure Storage Emulator v5.7 - * The emulator must be installed at this path C:\Program Files (x86)\Microsoft SDKs\Azure\Storage Emulator\AzureStorageEmulator.exe - * More info: https://docs.microsoft.com/azure/storage/common/storage-use-emulator + * These tests require Azure Storage Emulator v5.7 The emulator must be + * installed at this path C:\Program Files (x86)\Microsoft SDKs\Azure\Storage + * Emulator\AzureStorageEmulator.exe More info: + * https://docs.microsoft.com/azure/storage/common/storage-use-emulator */ public class TranscriptStoreTests { @Rule public TestName TEST_NAME = new TestName(); - protected String blobStorageEmulatorConnectionString = - "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"; + protected String blobStorageEmulatorConnectionString = "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"; private String channelId = "test"; - private static final String[] CONVERSATION_IDS = { - "qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm", "123", "456", "789", - "ZAQ", "XSW", "CDE", "VFR", "BGT", "NHY", "NHY", "098", "765", "432", - "zxc", "vbn", "mlk", "jhy", "yui", "kly", "asd", "asw", "aaa", "zzz", - }; + private static final String[] CONVERSATION_IDS = { "qaz", "wsx", "edc", "rfv", "tgb", "yhn", "ujm", "123", "456", + "789", "ZAQ", "XSW", "CDE", "VFR", "BGT", "NHY", "NHY", "098", "765", "432", "zxc", "vbn", "mlk", "jhy", + "yui", "kly", "asd", "asw", "aaa", "zzz", }; private static final String[] CONVERSATION_SPECIAL_IDS = { "asd !&/#.'+:?\"", "ASD@123<>|}{][", "$%^;\\*()_" }; @@ -70,22 +67,21 @@ private TranscriptStore getTranscriptStore() { return new BlobsTranscriptStore(blobStorageEmulatorConnectionString, getContainerName()); } - private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; - - @BeforeClass - public static void allTestsInit() throws IOException, InterruptedException { - assertEmulator(); + @Before + public void beforeTest() { + org.junit.Assume.assumeTrue(AzureEmulatorUtils.isStorageEmulatorAvailable()); } @After public void testCleanup() { - BlobContainerClient containerClient = new BlobContainerClientBuilder() - .connectionString(blobStorageEmulatorConnectionString) - .containerName(getContainerName()) - .buildClient(); + if (AzureEmulatorUtils.isStorageEmulatorAvailable()) { + BlobContainerClient containerClient = new BlobContainerClientBuilder() + .connectionString(blobStorageEmulatorConnectionString).containerName(getContainerName()) + .buildClient(); - if (containerClient.exists()) { - containerClient.delete(); + if (containerClient.exists()) { + containerClient.delete(); + } } } @@ -93,9 +89,12 @@ public void testCleanup() { @Test public void blobTranscriptParamTest() { Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(null, getContainerName())); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, null)); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(new String(), getContainerName())); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, new String())); + Assert.assertThrows(IllegalArgumentException.class, + () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, null)); + Assert.assertThrows(IllegalArgumentException.class, + () -> new BlobsTranscriptStore(new String(), getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, + () -> new BlobsTranscriptStore(blobStorageEmulatorConnectionString, new String())); } @Test @@ -109,7 +108,7 @@ public void transcriptsEmptyTest() { @Test public void activityEmptyTest() { TranscriptStore transcriptStore = getTranscriptStore(); - for(String convoId: CONVERSATION_SPECIAL_IDS) { + for (String convoId : CONVERSATION_SPECIAL_IDS) { PagedResult activities = transcriptStore.getTranscriptActivities(channelId, convoId).join(); Assert.assertEquals(0, activities.getItems().size()); } @@ -124,8 +123,8 @@ public void activityAddTest() { Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_IDS); transcriptStore.logActivity(a).join(); activities.add(a); - loggedActivities[i] = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]) - .join().getItems().get(0); + loggedActivities[i] = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join() + .getItems().get(0); } Assert.assertEquals(5, loggedActivities.length); @@ -140,7 +139,7 @@ public void transcriptRemoveTest() { transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); PagedResult loggedActivities = transcriptStore - .getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join(); + .getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join(); Assert.assertEquals(0, loggedActivities.getItems().size()); } @@ -171,8 +170,8 @@ public void transcriptRemoveSpecialCharsTest() { Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_SPECIAL_IDS); transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); - PagedResult loggedActivities = transcriptStore. - getTranscriptActivities(channelId, CONVERSATION_SPECIAL_IDS[i]).join(); + PagedResult loggedActivities = transcriptStore + .getTranscriptActivities(channelId, CONVERSATION_SPECIAL_IDS[i]).join(); Assert.assertEquals(0, loggedActivities.getItems().size()); } } @@ -192,7 +191,8 @@ public void activityAddPagedResultTest() { activities.add(a); } - PagedResult loggedPagedResult = transcriptStore.getTranscriptActivities(cleanChannel, CONVERSATION_IDS[0]).join(); + PagedResult loggedPagedResult = transcriptStore + .getTranscriptActivities(cleanChannel, CONVERSATION_IDS[0]).join(); String ct = loggedPagedResult.getContinuationToken(); Assert.assertEquals(20, loggedPagedResult.getItems().size()); Assert.assertNotNull(ct); @@ -208,11 +208,12 @@ public void transcriptRemovePagedTest() { TranscriptStore transcriptStore = getTranscriptStore(); int i; for (i = 0; i < CONVERSATION_SPECIAL_IDS.length; i++) { - Activity a = TranscriptStoreTests.createActivity(i ,i , CONVERSATION_IDS); + Activity a = TranscriptStoreTests.createActivity(i, i, CONVERSATION_IDS); transcriptStore.deleteTranscript(a.getChannelId(), a.getConversation().getId()).join(); } - PagedResult loggedActivities = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]).join(); + PagedResult loggedActivities = transcriptStore.getTranscriptActivities(channelId, CONVERSATION_IDS[i]) + .join(); Assert.assertEquals(0, loggedActivities.getItems().size()); } @@ -222,17 +223,16 @@ public void nullParameterTests() { Assert.assertThrows(IllegalArgumentException.class, () -> store.logActivity(null)); Assert.assertThrows(IllegalArgumentException.class, - () -> store.getTranscriptActivities(null, CONVERSATION_IDS[0])); + () -> store.getTranscriptActivities(null, CONVERSATION_IDS[0])); Assert.assertThrows(IllegalArgumentException.class, () -> store.getTranscriptActivities(channelId, null)); } @Test public void logActivities() { TranscriptStore transcriptStore = getTranscriptStore(); - ConversationReference conversation = TestAdapter - .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); - TestAdapter adapter = new TestAdapter(conversation) - .use(new TranscriptLoggerMiddleware(transcriptStore)); + ConversationReference conversation = TestAdapter.createConversationReference(UUID.randomUUID().toString(), + "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation).use(new TranscriptLoggerMiddleware(transcriptStore)); new TestFlow(adapter, turnContext -> { delay(500); Activity typingActivity = new Activity(ActivityTypes.TYPING); @@ -241,18 +241,10 @@ public void logActivities() { delay(500); turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join(); return CompletableFuture.completedFuture(null); - }) - .send("foo") - .assertReply(activity -> - Assert.assertTrue(activity.isType(ActivityTypes.TYPING)) - ) - .assertReply("echo:foo") - .send("bar") - .assertReply(activity -> - Assert.assertTrue(activity.isType(ActivityTypes.TYPING)) - ) - .assertReply("echo:bar") - .startTest().join(); + }).send("foo").assertReply(activity -> Assert.assertTrue(activity.isType(ActivityTypes.TYPING))) + .assertReply("echo:foo").send("bar") + .assertReply(activity -> Assert.assertTrue(activity.isType(ActivityTypes.TYPING))) + .assertReply("echo:bar").startTest().join(); PagedResult pagedResult = null; try { @@ -273,7 +265,7 @@ public void logActivities() { Assert.assertTrue(pagedResult.getItems().get(4).isType(ActivityTypes.TYPING)); Assert.assertTrue(pagedResult.getItems().get(5).isType(ActivityTypes.MESSAGE)); Assert.assertEquals("echo:bar", pagedResult.getItems().get(5).getText()); - for (Activity activity: pagedResult.getItems()) { + for (Activity activity : pagedResult.getItems()) { Assert.assertTrue(!StringUtils.isBlank(activity.getId())); Assert.assertTrue(activity.getTimestamp().isAfter(OffsetDateTime.MIN)); } @@ -282,14 +274,13 @@ public void logActivities() { @Test public void logUpdateActivities() { TranscriptStore transcriptStore = getTranscriptStore(); - ConversationReference conversation = TestAdapter - .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); - TestAdapter adapter = new TestAdapter(conversation) - .use(new TranscriptLoggerMiddleware(transcriptStore)); - final Activity[] activityToUpdate = {null}; + ConversationReference conversation = TestAdapter.createConversationReference(UUID.randomUUID().toString(), + "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation).use(new TranscriptLoggerMiddleware(transcriptStore)); + final Activity[] activityToUpdate = { null }; new TestFlow(adapter, turnContext -> { delay(500); - if(turnContext.getActivity().getText().equals("update")) { + if (turnContext.getActivity().getText().equals("update")) { activityToUpdate[0].setText("new response"); turnContext.updateActivity(activityToUpdate[0]).join(); } else { @@ -297,20 +288,17 @@ public void logUpdateActivities() { ResourceResponse response = turnContext.sendActivity(activity).join(); activity.setId(response.getId()); - ObjectMapper objectMapper = new ObjectMapper() - .findAndRegisterModules(); + ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); try { // clone the activity, so we can use it to do an update - activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), Activity.class); + activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), + Activity.class); } catch (JsonProcessingException ex) { ex.printStackTrace(); } } return CompletableFuture.completedFuture(null); - }).send("foo") - .send("update") - .assertReply("new response") - .startTest().join(); + }).send("foo").send("update").assertReply("new response").startTest().join(); PagedResult pagedResult = null; try { @@ -331,27 +319,25 @@ public void logUpdateActivities() { @Test public void logMissingUpdateActivity() { TranscriptStore transcriptStore = getTranscriptStore(); - ConversationReference conversation = TestAdapter - .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); - TestAdapter adapter = new TestAdapter(conversation) - .use(new TranscriptLoggerMiddleware(transcriptStore)); - final String[] fooId = {new String()}; - ObjectMapper objectMapper = new ObjectMapper() - .findAndRegisterModules(); + ConversationReference conversation = TestAdapter.createConversationReference(UUID.randomUUID().toString(), + "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation).use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] fooId = { new String() }; + ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); new TestFlow(adapter, turnContext -> { fooId[0] = turnContext.getActivity().getId(); Activity updateActivity = null; try { // clone the activity, so we can use it to do an update - updateActivity = objectMapper.readValue(objectMapper.writeValueAsString(turnContext.getActivity()), Activity.class); + updateActivity = objectMapper.readValue(objectMapper.writeValueAsString(turnContext.getActivity()), + Activity.class); } catch (JsonProcessingException ex) { ex.printStackTrace(); } updateActivity.setText("updated response"); ResourceResponse response = turnContext.updateActivity(updateActivity).join(); return CompletableFuture.completedFuture(null); - }).send("foo") - .startTest().join(); + }).send("foo").startTest().join(); delay(3000); @@ -375,11 +361,10 @@ public void logMissingUpdateActivity() { public void testDateLogUpdateActivities() { TranscriptStore transcriptStore = getTranscriptStore(); OffsetDateTime dateTimeStartOffset1 = OffsetDateTime.now(); - ConversationReference conversation = TestAdapter - .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); - TestAdapter adapter = new TestAdapter(conversation) - .use(new TranscriptLoggerMiddleware(transcriptStore)); - final Activity[] activityToUpdate = {null}; + ConversationReference conversation = TestAdapter.createConversationReference(UUID.randomUUID().toString(), + "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation).use(new TranscriptLoggerMiddleware(transcriptStore)); + final Activity[] activityToUpdate = { null }; new TestFlow(adapter, turnContext -> { if (turnContext.getActivity().getText().equals("update")) { activityToUpdate[0].setText("new response"); @@ -393,16 +378,14 @@ public void testDateLogUpdateActivities() { ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules(); try { // clone the activity, so we can use it to do an update - activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), Activity.class); + activityToUpdate[0] = objectMapper.readValue(objectMapper.writeValueAsString(activity), + Activity.class); } catch (JsonProcessingException ex) { ex.printStackTrace(); } } return CompletableFuture.completedFuture(null); - }).send("foo") - .send("update") - .assertReply("new response") - .startTest().join(); + }).send("foo").send("update").assertReply("new response").startTest().join(); try { TimeUnit.MILLISECONDS.sleep(5000); @@ -411,11 +394,8 @@ public void testDateLogUpdateActivities() { } // Perform some queries - PagedResult pagedResult = transcriptStore.getTranscriptActivities( - conversation.getChannelId(), - conversation.getConversation().getId(), - null, - dateTimeStartOffset1).join(); + PagedResult pagedResult = transcriptStore.getTranscriptActivities(conversation.getChannelId(), + conversation.getConversation().getId(), null, dateTimeStartOffset1).join(); Assert.assertEquals(3, pagedResult.getItems().size()); Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); @@ -425,11 +405,8 @@ public void testDateLogUpdateActivities() { Assert.assertEquals("update", pagedResult.getItems().get(2).getText()); // Perform some queries - pagedResult = transcriptStore.getTranscriptActivities( - conversation.getChannelId(), - conversation.getConversation().getId(), - null, - OffsetDateTime.MIN).join(); + pagedResult = transcriptStore.getTranscriptActivities(conversation.getChannelId(), + conversation.getConversation().getId(), null, OffsetDateTime.MIN).join(); Assert.assertEquals(3, pagedResult.getItems().size()); Assert.assertTrue(pagedResult.getItems().get(0).isType(ActivityTypes.MESSAGE)); Assert.assertEquals("foo", pagedResult.getItems().get(0).getText()); @@ -439,22 +416,19 @@ public void testDateLogUpdateActivities() { Assert.assertEquals("update", pagedResult.getItems().get(2).getText()); // Perform some queries - pagedResult = transcriptStore.getTranscriptActivities( - conversation.getChannelId(), - conversation.getConversation().getId(), - null, - OffsetDateTime.MAX).join(); + pagedResult = transcriptStore.getTranscriptActivities(conversation.getChannelId(), + conversation.getConversation().getId(), null, OffsetDateTime.MAX).join(); Assert.assertEquals(0, pagedResult.getItems().size()); } @Test public void logDeleteActivities() { TranscriptStore transcriptStore = getTranscriptStore(); - ConversationReference conversation = TestAdapter - .createConversationReference(UUID.randomUUID().toString(), "User1", "Bot"); - TestAdapter adapter = new TestAdapter(conversation) - .use(new TranscriptLoggerMiddleware(transcriptStore)); - final String[] activityId = {null}; + + ConversationReference conversation = TestAdapter.createConversationReference(UUID.randomUUID().toString(), + "User1", "Bot"); + TestAdapter adapter = new TestAdapter(conversation).use(new TranscriptLoggerMiddleware(transcriptStore)); + final String[] activityId = { null }; new TestFlow(adapter, turnContext -> { delay(500); if (turnContext.getActivity().getText().equals("deleteIt")) { @@ -465,10 +439,7 @@ public void logDeleteActivities() { activityId[0] = response.getId(); } return CompletableFuture.completedFuture(null); - }).send("foo") - .assertReply("response") - .send("deleteIt") - .startTest().join(); + }).send("foo").assertReply("response").send("deleteIt").startTest().join(); PagedResult pagedResult = null; try { @@ -505,19 +476,21 @@ private static Activity createActivity(Integer j, String conversationId) { } /** - * There are some async oddities within TranscriptLoggerMiddleware that make it difficult to set a short delay when - * running this tests that ensures - * the TestFlow completes while also logging transcripts. Some tests will not pass without longer delays, - * but this method minimizes the delay required. - * @param conversation ConversationReference to pass to GetTranscriptActivitiesAsync() - * that contains ChannelId and Conversation.Id. + * There are some async oddities within TranscriptLoggerMiddleware that make it + * difficult to set a short delay when running this tests that ensures the + * TestFlow completes while also logging transcripts. Some tests will not pass + * without longer delays, but this method minimizes the delay required. + * + * @param conversation ConversationReference to pass to + * GetTranscriptActivitiesAsync() that contains ChannelId + * and Conversation.Id. * @param expectedLength Expected length of pagedResult array. - * @param maxTimeout Maximum time to wait to retrieve pagedResult. + * @param maxTimeout Maximum time to wait to retrieve pagedResult. * @return PagedResult. * @throws TimeoutException */ private CompletableFuture> getPagedResult(ConversationReference conversation, - Integer expectedLength, Integer maxTimeout) throws TimeoutException { + Integer expectedLength, Integer maxTimeout) throws TimeoutException { TranscriptStore transcriptStore = getTranscriptStore(); if (maxTimeout == null) { maxTimeout = 5000; @@ -528,39 +501,26 @@ private CompletableFuture> getPagedResult(ConversationRefe delay(500); try { pagedResult = transcriptStore - .getTranscriptActivities(conversation.getChannelId(), conversation.getConversation().getId()).join(); + .getTranscriptActivities(conversation.getChannelId(), conversation.getConversation().getId()) + .join(); if (pagedResult.getItems().size() >= expectedLength) { break; } - } catch (NoSuchElementException ex) { } - catch (NullPointerException e) { } + } catch (NoSuchElementException ex) { + } catch (NullPointerException e) { + } } - if(pagedResult == null) { + if (pagedResult == null) { return Async.completeExceptionally(new TimeoutException("Unable to retrieve pagedResult in time")); } return CompletableFuture.completedFuture(pagedResult); } - private static void assertEmulator() throws IOException, InterruptedException { - if (!checkEmulator()) { - Assert.fail(NO_EMULATOR_MESSAGE); - } - } - - private static Boolean checkEmulator() throws IOException, InterruptedException { - Process p = Runtime.getRuntime().exec - ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); - int result = p.waitFor(); - // status = 0: the service was started. - // status = -5: the service is already started. Only one instance of the application - // can be run at the same time. - return result == 0 || result == -5; - } - /** * Time period delay. + * * @param delay Time to delay. */ private void delay(Integer delay) { diff --git a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java index 9efc4de8c..3197972bc 100644 --- a/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java +++ b/libraries/bot-azure/src/test/java/com/microsoft/bot/azure/blobs/BlobsStorageTests.java @@ -5,6 +5,7 @@ import com.azure.storage.blob.BlobContainerClient; import com.azure.storage.blob.BlobContainerClientBuilder; +import com.microsoft.bot.azure.AzureEmulatorUtils; import com.microsoft.bot.builder.BotAdapter; import com.microsoft.bot.builder.ConversationState; import com.microsoft.bot.builder.StatePropertyAccessor; @@ -20,7 +21,7 @@ import com.microsoft.bot.schema.ResourceResponse; import org.junit.After; import org.junit.Assert; -import org.junit.BeforeClass; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TestName; @@ -38,51 +39,37 @@ public class BlobsStorageTests extends StorageBaseTests { private final String connectionString = "AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;DefaultEndpointsProtocol=http;BlobEndpoint=http://127.0.0.1:10000/devstoreaccount1;QueueEndpoint=http://127.0.0.1:10001/devstoreaccount1;TableEndpoint=http://127.0.0.1:10002/devstoreaccount1;"; - private static boolean emulatorIsRunning = false; - - private static final String NO_EMULATOR_MESSAGE = "This test requires Azure STORAGE Emulator! Go to https://docs.microsoft.com/azure/storage/common/storage-use-emulator to download and install."; - public String getContainerName() { return "blobs" + testName.getMethodName().toLowerCase().replace("_", ""); } - @BeforeClass - public static void allTestsInit() throws IOException, InterruptedException { - Process p = Runtime.getRuntime().exec - ("cmd /C \"" + System.getenv("ProgramFiles") + " (x86)\\Microsoft SDKs\\Azure\\Storage Emulator\\AzureStorageEmulator.exe\" start"); - int result = p.waitFor(); - // status = 0: the service was started. - // status = -5: the service is already started. Only one instance of the application - // can be run at the same time. - emulatorIsRunning = result == 0 || result == -5; + @Before + public void beforeTest() { + org.junit.Assume.assumeTrue(AzureEmulatorUtils.isStorageEmulatorAvailable()); } @After public void testCleanup() { - BlobContainerClient containerClient = new BlobContainerClientBuilder() - .connectionString(connectionString) - .containerName(getContainerName()) - .buildClient(); + if (AzureEmulatorUtils.isStorageEmulatorAvailable()) { + BlobContainerClient containerClient = new BlobContainerClientBuilder().connectionString(connectionString) + .containerName(getContainerName()).buildClient(); - if (containerClient.exists()) { - containerClient.delete(); + if (containerClient.exists()) { + containerClient.delete(); + } } } @Test public void blobStorageParamTest() { - if (runIfEmulator()) { - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(null, getContainerName())); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, null)); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(new String(), getContainerName())); - Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, new String())); - } + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(null, getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, null)); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(new String(), getContainerName())); + Assert.assertThrows(IllegalArgumentException.class, () -> new BlobsStorage(connectionString, new String())); } @Test - public void testBlobStorageWriteRead() - { - if (runIfEmulator()) { + public void testBlobStorageWriteRead() { // Arrange Storage storage = new BlobsStorage(connectionString, getContainerName()); @@ -92,19 +79,16 @@ public void testBlobStorageWriteRead() // Act storage.write(changes).join(); - Map result = storage.read(new String[] {"x", "y"}).join(); + Map result = storage.read(new String[] { "x", "y" }).join(); // Assert Assert.assertEquals(2, result.size()); Assert.assertEquals("hello", result.get("x")); Assert.assertEquals("world", result.get("y")); - } } @Test - public void testBlobStorageWriteDeleteRead() - { - if (runIfEmulator()) { + public void testBlobStorageWriteDeleteRead() { // Arrange Storage storage = new BlobsStorage(connectionString, getContainerName()); @@ -115,17 +99,15 @@ public void testBlobStorageWriteDeleteRead() // Act storage.write(changes).join(); storage.delete(new String[] { "x" }).join(); - Map result = storage.read(new String[] {"x", "y"}).join(); + Map result = storage.read(new String[] { "x", "y" }).join(); // Assert Assert.assertEquals(1, result.size()); Assert.assertEquals("world", result.get("y")); - } } @Test public void testBlobStorageChanges() { - if (runIfEmulator()) { // Arrange Storage storage = new BlobsStorage(connectionString, getContainerName()); @@ -150,12 +132,10 @@ public void testBlobStorageChanges() { Assert.assertEquals(2, result.size()); Assert.assertEquals("1.1", result.get("a")); Assert.assertEquals("3.0", result.get("c")); - } } @Test public void testConversationStateBlobStorage() { - if (runIfEmulator()) { // Arrange Storage storage = new BlobsStorage(connectionString, getContainerName()); @@ -185,27 +165,21 @@ public void testConversationStateBlobStorage() { propAccessor.delete(turnContext1).join(); conversationState.saveChanges(turnContext1).join(); - } } @Test public void testConversationStateBlobStorage_TypeNameHandlingDefault() { - if (runIfEmulator()) { Storage storage = new BlobsStorage(connectionString, getContainerName()); testConversationStateBlobStorage_Method(storage); - } } @Test public void statePersistsThroughMultiTurn_TypeNameHandlingNone() { - if (runIfEmulator()) { Storage storage = new BlobsStorage(connectionString, getContainerName()); statePersistsThroughMultiTurn(storage); - } } private void testConversationStateBlobStorage_Method(Storage blobs) { - if (runIfEmulator()) { // Arrange ConversationState conversationState = new ConversationState(blobs); StatePropertyAccessor propAccessor = conversationState.createProperty("prop"); @@ -229,16 +203,6 @@ private void testConversationStateBlobStorage_Method(Storage blobs) { // Assert Assert.assertEquals("hello", propValue2.getX()); Assert.assertEquals("world", propValue2.getY()); - } - } - - private boolean runIfEmulator() { - if (!emulatorIsRunning) { - System.out.println(NO_EMULATOR_MESSAGE); - return false; - } - - return true; } private class TestStorageAdapter extends BotAdapter { From 6f41bfa12d9679ea89a20b26814640858d9b6567 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Tue, 30 Mar 2021 10:49:51 -0500 Subject: [PATCH 124/221] Update telemetry to log attachments (#1114) * Update to log attachments * Add unit test for logging attachments --- .../bot/builder/TelemetryConstants.java | 1 + .../builder/TelemetryLoggerMiddleware.java | 9 +++++ .../bot/builder/TelemetryMiddlewareTests.java | 40 +++++++++++++++++++ 3 files changed, 50 insertions(+) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryConstants.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryConstants.java index a81e6eca0..5e23f07f8 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryConstants.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryConstants.java @@ -11,6 +11,7 @@ private TelemetryConstants() { } + public static final String ATTACHMENTSPROPERTY = "attachments"; public static final String CHANNELIDPROPERTY = "channelId"; public static final String CONVERSATIONIDPROPERTY = "conversationId"; public static final String CONVERSATIONNAMEPROPERTY = "conversationName"; diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java index 1bf2966ba..9a6670856 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java @@ -188,6 +188,7 @@ protected CompletableFuture onDeleteActivity(Activity activity) { * {@link BotTelemetryClient#trackEvent} method for the * BotMessageReceived event. */ + @SuppressWarnings("PMD.EmptyCatchBlock") protected CompletableFuture> fillReceiveEventProperties( Activity activity, Map additionalProperties @@ -217,6 +218,14 @@ protected CompletableFuture> fillReceiveEventProperties( if (!StringUtils.isEmpty(activity.getSpeak())) { properties.put(TelemetryConstants.SPEAKPROPERTY, activity.getSpeak()); } + + if (activity.getAttachments() != null && activity.getAttachments().size() > 0) { + try { + properties.put(TelemetryConstants.ATTACHMENTSPROPERTY, + Serialization.toString(activity.getAttachments())); + } catch (JsonProcessingException e) { + } + } } populateAdditionalChannelProperties(activity, properties); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java index 7ff33f851..605c34581 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TelemetryMiddlewareTests.java @@ -9,6 +9,7 @@ import com.microsoft.bot.connector.Channels; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.Attachment; import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.Serialization; @@ -528,6 +529,45 @@ public void Telemetry_AdditionalProps() { ); } + @Test + public void Telemetry_LogAttachments() throws JsonProcessingException { + BotTelemetryClient mockTelemetryClient = mock(BotTelemetryClient.class); + TestAdapter adapter = new TestAdapter(Channels.MSTEAMS).use( + new TelemetryLoggerMiddleware(mockTelemetryClient, true) + ); + + TeamInfo teamInfo = new TeamInfo(); + teamInfo.setId("teamId"); + teamInfo.setName("teamName"); + + Activity activity = MessageFactory.text("test"); + ChannelAccount from = new ChannelAccount(); + from.setId("userId"); + from.setName("userName"); + from.setAadObjectId("aadId"); + activity.setFrom(from); + Attachment attachment = new Attachment(); + attachment.setContent("Hello World"); + attachment.setContentType("test/attachment"); + attachment.setName("testname"); + activity.setAttachment(attachment); + + new TestFlow(adapter).send(activity).startTest().join(); + + verify(mockTelemetryClient).trackEvent( + eventNameCaptor.capture(), + propertiesCaptor.capture() + ); + List eventNames = eventNameCaptor.getAllValues(); + List> properties = propertiesCaptor.getAllValues(); + + Assert.assertEquals(TelemetryLoggerConstants.BOTMSGRECEIVEEVENT, eventNames.get(0)); + String loggedAttachment = properties.get(0).get("attachments"); + String originalAttachment = Serialization.toString(activity.getAttachments()); + Assert.assertTrue(StringUtils.equals(loggedAttachment, originalAttachment)); + } + + @Test public void Telemetry_LogTeamsProperties() throws JsonProcessingException { BotTelemetryClient mockTelemetryClient = mock(BotTelemetryClient.class); From 42edeb37d308f08570cfc3b67f87ba90deb2ed4e Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 1 Apr 2021 09:24:20 -0500 Subject: [PATCH 125/221] Add retry fallback for 429 responses (#1121) --- .../CredentialsAuthenticator.java | 32 ++++--- .../authentication/RetryAfterHelper.java | 68 +++++++++++++ .../bot/connector/RetryAfterHelperTests.java | 95 +++++++++++++++++++ 3 files changed, 181 insertions(+), 14 deletions(-) create mode 100644 libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryAfterHelper.java create mode 100644 libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryAfterHelperTests.java diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java index 1bfbe6c03..9727335b8 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/CredentialsAuthenticator.java @@ -7,6 +7,7 @@ import com.microsoft.aad.msal4j.ClientCredentialParameters; import com.microsoft.aad.msal4j.ConfidentialClientApplication; import com.microsoft.aad.msal4j.IAuthenticationResult; +import com.microsoft.aad.msal4j.MsalServiceException; import java.net.MalformedURLException; import java.util.Collections; @@ -28,16 +29,12 @@ public class CredentialsAuthenticator implements Authenticator { * @throws MalformedURLException Invalid endpoint. */ CredentialsAuthenticator(String appId, String appPassword, OAuthConfiguration configuration) - throws MalformedURLException { + throws MalformedURLException { - app = ConfidentialClientApplication - .builder(appId, ClientCredentialFactory.createFromSecret(appPassword)) - .authority(configuration.getAuthority()) - .build(); + app = ConfidentialClientApplication.builder(appId, ClientCredentialFactory.createFromSecret(appPassword)) + .authority(configuration.getAuthority()).build(); - parameters = ClientCredentialParameters - .builder(Collections.singleton(configuration.getScope())) - .build(); + parameters = ClientCredentialParameters.builder(Collections.singleton(configuration.getScope())).build(); } /** @@ -47,12 +44,19 @@ public class CredentialsAuthenticator implements Authenticator { */ @Override public CompletableFuture acquireToken() { - return app.acquireToken(parameters) - .exceptionally( - exception -> { - // wrapping whatever msal throws into our own exception - throw new AuthenticationException(exception); + return Retry.run(() -> app.acquireToken(parameters).exceptionally(exception -> { + // wrapping whatever msal throws into our own exception + throw new AuthenticationException(exception); + }), (exception, count) -> { + if (exception instanceof RetryException && exception.getCause() instanceof MsalServiceException) { + MsalServiceException serviceException = (MsalServiceException) exception.getCause(); + if (serviceException.headers().containsKey("Retry-After")) { + return RetryAfterHelper.processRetry(serviceException.headers().get("Retry-After"), count); + } else { + return RetryParams.defaultBackOff(++count); } - ); + } + return RetryParams.stopRetrying(); + }); } } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryAfterHelper.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryAfterHelper.java new file mode 100644 index 000000000..5c7c8db9e --- /dev/null +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/RetryAfterHelper.java @@ -0,0 +1,68 @@ +package com.microsoft.bot.connector.authentication; + +import java.time.Duration; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; + +/** + * Class that contains a helper function to process HTTP 429 Retry-After headers + * for the CredentialsAuthenticator. The reason to extract this was + * CredentialsAuthenticator is an internal class that isn't exposed except + * through other Authentication classes and we wanted a way to test the + * processing of 429 headers without building complicated test harnesses. + */ +public final class RetryAfterHelper { + + private RetryAfterHelper() { + + } + + /** + * Process a RetryException and see if we should wait for a requested amount of + * time before retrying to call the authentication service again. + * + * @param header The header values to process. + * @param count The count of how many times we have retried. + * @return A RetryParams with instructions of when or how many more times to + * retry. + */ + public static RetryParams processRetry(List header, Integer count) { + if (header == null || header.size() == 0) { + return RetryParams.defaultBackOff(++count); + } else { + String headerString = header.get(0); + if (StringUtils.isNotBlank(headerString)) { + // see if it matches a numeric value + if (headerString.matches("^[0-9]+\\.?0*$")) { + headerString = headerString.replaceAll("\\.0*$", ""); + Duration delay = Duration.ofSeconds(Long.parseLong(headerString)); + return new RetryParams(delay.toMillis()); + } else { + // check to see if it's a RFC_1123 format Date/Time + DateTimeFormatter gmtFormat = DateTimeFormatter.RFC_1123_DATE_TIME; + try { + ZonedDateTime zoned = ZonedDateTime.parse(headerString, gmtFormat); + if (zoned != null) { + ZonedDateTime now = ZonedDateTime.now(ZoneOffset.UTC); + long waitMillis = zoned.toInstant().toEpochMilli() - now.toInstant().toEpochMilli(); + if (waitMillis > 0) { + return new RetryParams(waitMillis); + } else { + return RetryParams.defaultBackOff(++count); + } + } + } catch (DateTimeParseException ex) { + return RetryParams.defaultBackOff(++count); + } + } + } + } + return RetryParams.defaultBackOff(++count); + } + +} diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryAfterHelperTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryAfterHelperTests.java new file mode 100644 index 000000000..155c96386 --- /dev/null +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/RetryAfterHelperTests.java @@ -0,0 +1,95 @@ +package com.microsoft.bot.connector; + +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.TemporalUnit; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import com.microsoft.aad.msal4j.MsalException; +import com.microsoft.aad.msal4j.MsalServiceException; +import com.microsoft.bot.connector.authentication.RetryAfterHelper; +import com.microsoft.bot.connector.authentication.RetryException; +import com.microsoft.bot.connector.authentication.RetryParams; + +import org.junit.Assert; +import org.junit.Test; + +public class RetryAfterHelperTests { + + @Test + public void TestRetryIncrement() { + RetryParams result = RetryAfterHelper.processRetry(new ArrayList(), 8); + Assert.assertTrue(result.getShouldRetry()); + result = RetryAfterHelper.processRetry(new ArrayList(), 9); + Assert.assertFalse(result.getShouldRetry()); + } + + @Test + public void TestRetryDelaySeconds() { + List headers = new ArrayList(); + headers.add("10"); + RetryParams result = RetryAfterHelper.processRetry(headers, 1); + Assert.assertEquals(result.getRetryAfter(), 10000); + } + + @Test + public void TestRetryDelayRFC1123Date() { + Instant instant = Instant.now().plusSeconds(5); + ZonedDateTime dateTime = instant.atZone(ZoneId.of("UTC")); + String dateTimeString = dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME); + List headers = new ArrayList(); + headers.add(dateTimeString); + RetryParams result = RetryAfterHelper.processRetry(headers, 1); + Assert.assertTrue(result.getShouldRetry()); + Assert.assertTrue(result.getRetryAfter() > 0); + } + + @Test + public void TestRetryDelayRFC1123DateInPast() { + Instant instant = Instant.now().plusSeconds(-5); + ZonedDateTime dateTime = instant.atZone(ZoneId.of("UTC")); + String dateTimeString = dateTime.format(DateTimeFormatter.RFC_1123_DATE_TIME); + List headers = new ArrayList(); + headers.add(dateTimeString); + RetryParams result = RetryAfterHelper.processRetry(headers, 1); + Assert.assertTrue(result.getShouldRetry()); + // default is 50, so since the time was in the past we should be seeing the default 50 here. + Assert.assertTrue(result.getRetryAfter() == 50); + } + + + @Test + public void TestRetryDelayRFC1123DateEmpty() { + List headers = new ArrayList(); + headers.add(""); + RetryParams result = RetryAfterHelper.processRetry(headers, 1); + Assert.assertTrue(result.getShouldRetry()); + // default is 50, so since the time was in the past we should be seeing the default 50 here. + Assert.assertTrue(result.getRetryAfter() == 50); + } + + @Test + public void TestRetryDelayRFC1123DateNull() { + List headers = new ArrayList(); + headers.add(null); + RetryParams result = RetryAfterHelper.processRetry(headers, 1); + Assert.assertTrue(result.getShouldRetry()); + // default is 50, so since the time was in the past we should be seeing the default 50 here. + Assert.assertTrue(result.getRetryAfter() == 50); + } + + @Test + public void TestRetryDelayRFC1123NeaderNull() { + RetryParams result = RetryAfterHelper.processRetry(null, 1); + Assert.assertTrue(result.getShouldRetry()); + // default is 50, so since the time was in the past we should be seeing the default 50 here. + Assert.assertTrue(result.getRetryAfter() == 50); + } + +} From 45130e6ae70f281cd5a968a7a7702e0f2fc48fe4 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 1 Apr 2021 09:42:40 -0500 Subject: [PATCH 126/221] Added Event Factory for handoff protocol (#1118) * Added Event Factory for handoff protocol * Added additional unit test. Co-authored-by: tracyboehrer --- .../microsoft/bot/builder/EventFactory.java | 134 +++++++++++++++++ .../bot/builder/EventFactoryTests.java | 138 ++++++++++++++++++ .../bot/schema/HandoffEventNames.java | 25 ++++ 3 files changed, 297 insertions(+) create mode 100644 libraries/bot-builder/src/main/java/com/microsoft/bot/builder/EventFactory.java create mode 100644 libraries/bot-builder/src/test/java/com/microsoft/bot/builder/EventFactoryTests.java create mode 100644 libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HandoffEventNames.java diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/EventFactory.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/EventFactory.java new file mode 100644 index 000000000..a7c7e06fc --- /dev/null +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/EventFactory.java @@ -0,0 +1,134 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import java.time.OffsetDateTime; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.UUID; + +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.Entity; +import com.microsoft.bot.schema.HandoffEventNames; +import com.microsoft.bot.schema.Transcript; + +import org.apache.commons.lang3.StringUtils; + +/** + * Contains utility methods for creating various event types. + */ +public final class EventFactory { + + private EventFactory() { + + } + + /** + * Create handoff initiation event. + * + * @param turnContext turn context. + * @param handoffContext agent hub-specific context. + * + * @return handoff event. + */ + public static Activity createHandoffInitiation(TurnContext turnContext, Object handoffContext) { + return createHandoffInitiation(turnContext, handoffContext, null); + } + + + /** + * Create handoff initiation event. + * + * @param turnContext turn context. + * @param handoffContext agent hub-specific context. + * @param transcript transcript of the conversation. + * + * @return handoff event. + */ + public static Activity createHandoffInitiation(TurnContext turnContext, Object handoffContext, + Transcript transcript) { + if (turnContext == null) { + throw new IllegalArgumentException("turnContext cannot be null."); + } + + Activity handoffEvent = createHandoffEvent(HandoffEventNames.INITIATEHANDOFF, handoffContext, + turnContext.getActivity().getConversation()); + + handoffEvent.setFrom(turnContext.getActivity().getFrom()); + handoffEvent.setRelatesTo(turnContext.getActivity().getConversationReference()); + handoffEvent.setReplyToId(turnContext.getActivity().getId()); + handoffEvent.setServiceUrl(turnContext.getActivity().getServiceUrl()); + handoffEvent.setChannelId(turnContext.getActivity().getChannelId()); + + if (transcript != null) { + Attachment attachment = new Attachment(); + attachment.setContent(transcript); + attachment.setContentType("application/json"); + attachment.setName("Transcript"); + handoffEvent.getAttachments().add(attachment); + } + + return handoffEvent; + } + + + /** + * Create handoff status event. + * + * @param conversation Conversation being handed over. + * @param state State, possible values are: "accepted", "failed", + * "completed". + * + * @return handoff event. + */ + public static Activity createHandoffStatus(ConversationAccount conversation, String state) { + return createHandoffStatus(conversation, state, null); + } + + /** + * Create handoff status event. + * + * @param conversation Conversation being handed over. + * @param state State, possible values are: "accepted", "failed", + * "completed". + * @param message Additional message for failed handoff. + * + * @return handoff event. + */ + public static Activity createHandoffStatus(ConversationAccount conversation, String state, String message) { + if (conversation == null) { + throw new IllegalArgumentException("conversation cannot be null."); + } + + if (state == null) { + throw new IllegalArgumentException("state cannot be null."); + } + + ObjectNode handoffContext = JsonNodeFactory.instance.objectNode(); + handoffContext.set("state", JsonNodeFactory.instance.textNode(state)); + if (StringUtils.isNotBlank(message)) { + handoffContext.set("message", JsonNodeFactory.instance.textNode(message)); + } + + Activity handoffEvent = createHandoffEvent(HandoffEventNames.HANDOFFSTATUS, handoffContext, conversation); + return handoffEvent; + } + + private static Activity createHandoffEvent(String name, Object value, ConversationAccount conversation) { + Activity handoffEvent = Activity.createEventActivity(); + + handoffEvent.setName(name); + handoffEvent.setValue(value); + handoffEvent.setId(UUID.randomUUID().toString()); + handoffEvent.setTimestamp(OffsetDateTime.now(ZoneId.of("UTC"))); + handoffEvent.setConversation(conversation); + handoffEvent.setAttachments(new ArrayList()); + handoffEvent.setEntities(new ArrayList()); + return handoffEvent; + } +} diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/EventFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/EventFactoryTests.java new file mode 100644 index 000000000..7a5cd1acc --- /dev/null +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/EventFactoryTests.java @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.builder; + +import java.util.ArrayList; +import java.util.List; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.adapters.TestAdapter; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.ConversationAccount; +import com.microsoft.bot.schema.HandoffEventNames; +import com.microsoft.bot.schema.Serialization; +import com.microsoft.bot.schema.Transcript; + +import org.junit.Assert; +import org.junit.Test; + +public class EventFactoryTests { + + @Test + public void HandoffInitiationNullTurnContext() { + Assert.assertThrows(IllegalArgumentException.class, + () -> EventFactory.createHandoffInitiation(null, "some text")); + } + + @Test + public void HandoffStatusNullConversation() { + Assert.assertThrows(IllegalArgumentException.class, () -> EventFactory.createHandoffStatus(null, "accepted")); + } + + @Test + public void HandoffStatusNullStatus() { + Assert.assertThrows(IllegalArgumentException.class, + () -> EventFactory.createHandoffStatus(new ConversationAccount(), null)); + } + + @Test + public void TestCreateHandoffInitiation() { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("TestCreateHandoffInitiation", "User1", "Bot")); + String fromD = "test"; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(""); + activity.setConversation(new ConversationAccount()); + activity.setRecipient(new ChannelAccount()); + activity.setFrom(new ChannelAccount(fromD)); + activity.setChannelId("testchannel"); + activity.setServiceUrl("http://myservice"); + TurnContext context = new TurnContextImpl(adapter, activity); + List activities = new ArrayList(); + activities.add(MessageFactory.text("hello")); + Transcript transcript = new Transcript(); + transcript.setActivities(activities); + + Assert.assertNull(transcript.getActivities().get(0).getChannelId()); + Assert.assertNull(transcript.getActivities().get(0).getServiceUrl()); + Assert.assertNull(transcript.getActivities().get(0).getConversation()); + + ObjectNode handoffContext = JsonNodeFactory.instance.objectNode(); + handoffContext.set("Skill", JsonNodeFactory.instance.textNode("any")); + + Activity handoffEvent = EventFactory.createHandoffInitiation(context, handoffContext, transcript); + Assert.assertEquals(handoffEvent.getName(), HandoffEventNames.INITIATEHANDOFF); + ObjectNode node = (ObjectNode) handoffEvent.getValue(); + String skill = node.get("Skill").asText(); + Assert.assertEquals("any", skill); + Assert.assertEquals(handoffEvent.getFrom().getId(), fromD); + } + + @Test + public void TestCreateHandoffInitiationNoTranscript() { + TestAdapter adapter = new TestAdapter( + TestAdapter.createConversationReference("TestCreateHandoffInitiation", "User1", "Bot")); + String fromD = "test"; + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setText(""); + activity.setConversation(new ConversationAccount()); + activity.setRecipient(new ChannelAccount()); + activity.setFrom(new ChannelAccount(fromD)); + activity.setChannelId("testchannel"); + activity.setServiceUrl("http://myservice"); + TurnContext context = new TurnContextImpl(adapter, activity); + List activities = new ArrayList(); + activities.add(MessageFactory.text("hello")); + + ObjectNode handoffContext = JsonNodeFactory.instance.objectNode(); + handoffContext.set("Skill", JsonNodeFactory.instance.textNode("any")); + + Activity handoffEvent = EventFactory.createHandoffInitiation(context, handoffContext); + Assert.assertEquals(handoffEvent.getName(), HandoffEventNames.INITIATEHANDOFF); + ObjectNode node = (ObjectNode) handoffEvent.getValue(); + String skill = node.get("Skill").asText(); + Assert.assertEquals("any", skill); + Assert.assertEquals(handoffEvent.getFrom().getId(), fromD); + } + + @Test + public void TestCreateHandoffStatus() throws JsonProcessingException { + String state = "failed"; + String message = "timed out"; + Activity handoffEvent = EventFactory.createHandoffStatus(new ConversationAccount(), state, message); + Assert.assertEquals(handoffEvent.getName(), HandoffEventNames.HANDOFFSTATUS); + + ObjectNode node = (ObjectNode) handoffEvent.getValue(); + + String stateFormEvent = node.get("state").asText(); + Assert.assertEquals(stateFormEvent, state); + + String messageFormEvent = node.get("message").asText(); + Assert.assertEquals(messageFormEvent, message); + + String status = Serialization.toString(node); + Assert.assertEquals(status, String.format("{\"state\":\"%s\",\"message\":\"%s\"}", state, message)); + Assert.assertNotNull(handoffEvent.getAttachments()); + Assert.assertNotNull(handoffEvent.getId()); + } + + @Test + public void TestCreateHandoffStatusNoMessage() { + String state = "failed"; + Activity handoffEvent = EventFactory.createHandoffStatus(new ConversationAccount(), state); + + ObjectNode node = (ObjectNode) handoffEvent.getValue(); + + String stateFormEvent = node.get("state").asText(); + Assert.assertEquals(stateFormEvent, state); + + JsonNode messageFormEvent = node.get("message"); + Assert.assertNull(messageFormEvent); + } +} diff --git a/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HandoffEventNames.java b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HandoffEventNames.java new file mode 100644 index 000000000..fbe99ae4a --- /dev/null +++ b/libraries/bot-schema/src/main/java/com/microsoft/bot/schema/HandoffEventNames.java @@ -0,0 +1,25 @@ + +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.schema; + +/** + * Defines values for handoff event names. + */ +public final class HandoffEventNames { + + private HandoffEventNames() { + + } + + /** + * The value of handoff events for initiate handoff. + */ + public static final String INITIATEHANDOFF = "handoff.initiate"; + + /** + * The value of handoff events for handoff status. + */ + public static final String HANDOFFSTATUS = "handoff.status"; +} From 0314f4e3634badd655c5f3eb8ae36e7ae485c968 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 1 Apr 2021 10:04:47 -0500 Subject: [PATCH 127/221] add delay to unit test (#1117) Co-authored-by: tracyboehrer --- .../com/microsoft/bot/builder/TranscriptMiddlewareTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java index be530436d..4fbd98a13 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/TranscriptMiddlewareTest.java @@ -65,9 +65,9 @@ public final void Transcript_LogActivities() { context.sendActivity("echo:" + context.getActivity().getText()).join(); return CompletableFuture.completedFuture(null); } - ).send("foo").assertReply( + ).send("foo").delay(50).assertReply( (activity) -> Assert.assertEquals(activity.getType(), ActivityTypes.TYPING) - ).assertReply("echo:foo").send("bar").assertReply( + ).assertReply("echo:foo").send("bar").delay(50).assertReply( (activity) -> Assert.assertEquals(activity.getType(), ActivityTypes.TYPING) ).assertReply("echo:bar").startTest().join(); From 7893dbdc2ca12ecfbe59a4a406a2c819792e31d8 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 1 Apr 2021 10:36:33 -0500 Subject: [PATCH 128/221] Updates to SetSpeakMiddleware (#1123) --- .../bot/builder/SetSpeakMiddleware.java | 14 ++++---------- .../bot/builder/SetSpeakMiddlewareTests.java | 18 ++++++------------ .../com/microsoft/bot/connector/Channels.java | 5 +++++ 3 files changed, 15 insertions(+), 22 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java index 23576df5b..8fe2d1894 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/SetSpeakMiddleware.java @@ -25,24 +25,17 @@ public class SetSpeakMiddleware implements Middleware { private final String voiceName; private final boolean fallbackToTextForSpeak; - private final String lang; /** * Initializes a new instance of the {@link SetSpeakMiddleware} class. * * @param voiceName The SSML voice name attribute value. - * @param lang The xml:lang value. * @param fallbackToTextForSpeak true if an empt Activity.Speak is populated * with Activity.getText(). */ - public SetSpeakMiddleware(String voiceName, String lang, boolean fallbackToTextForSpeak) { + public SetSpeakMiddleware(String voiceName, boolean fallbackToTextForSpeak) { this.voiceName = voiceName; this.fallbackToTextForSpeak = fallbackToTextForSpeak; - if (lang == null) { - throw new IllegalArgumentException("lang cannot be null."); - } else { - this.lang = lang; - } } /** @@ -73,10 +66,11 @@ public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next activity.setSpeak( String.format("%s", voiceName, activity.getSpeak())); } - activity.setSpeak(String .format("%s", lang, activity.getSpeak())); + + "xml:lang='%s'>%s", + activity.getLocale() != null ? activity.getLocale() : "en-US", + activity.getSpeak())); } } } diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java index 19ebf79f0..a079aacc5 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SetSpeakMiddlewareTests.java @@ -16,16 +16,10 @@ public class SetSpeakMiddlewareTests { - @Test - public void ConstructorValidation() { - // no 'lang' - Assert.assertThrows(IllegalArgumentException.class, () -> new SetSpeakMiddleware("voice", null, false)); - } - @Test public void NoFallback() { TestAdapter adapter = new TestAdapter(createConversation("NoFallback")) - .use(new SetSpeakMiddleware("male", "en-us", false)); + .use(new SetSpeakMiddleware("male", false)); new TestFlow(adapter, turnContext -> { Activity activity = MessageFactory.text("OK"); @@ -43,7 +37,7 @@ public void NoFallback() { @Test public void FallbackNullSpeak() { TestAdapter adapter = new TestAdapter(createConversation("NoFallback")) - .use(new SetSpeakMiddleware("male", "en-us", true)); + .use(new SetSpeakMiddleware("male", true)); new TestFlow(adapter, turnContext -> { Activity activity = MessageFactory.text("OK"); @@ -61,7 +55,7 @@ public void FallbackNullSpeak() { @Test public void FallbackWithSpeak() { TestAdapter adapter = new TestAdapter(createConversation("Fallback")) - .use(new SetSpeakMiddleware("male", "en-us", true)); + .use(new SetSpeakMiddleware("male", true)); new TestFlow(adapter, turnContext -> { Activity activity = MessageFactory.text("OK"); @@ -93,7 +87,7 @@ public void AddVoiceTelephony() { // Voice instanceof added to Speak property. public void AddVoice(String channelId) { TestAdapter adapter = new TestAdapter(createConversation("Fallback", channelId)) - .use(new SetSpeakMiddleware("male", "en-us", true)); + .use(new SetSpeakMiddleware("male", true)); new TestFlow(adapter, turnContext -> { Activity activity = MessageFactory.text("OK"); @@ -119,14 +113,14 @@ public void AddNoVoiceDirectlineSpeech() { @Test public void AddNoVoiceTelephony() { - AddNoVoice("telephony"); + AddNoVoice(Channels.TELEPHONY); } // With no 'voice' specified, the Speak property instanceof unchanged. public void AddNoVoice(String channelId) { TestAdapter adapter = new TestAdapter(createConversation("Fallback", channelId)) - .use(new SetSpeakMiddleware(null, "en-us", true)); + .use(new SetSpeakMiddleware(null, true)); new TestFlow(adapter, turnContext -> { Activity activity = MessageFactory.text("OK"); diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java index b2bae63ee..8a505f06a 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/Channels.java @@ -100,4 +100,9 @@ private Channels() { * Test channel. */ public static final String TEST = "test"; + + /** + * Telephony channel. + */ + public static final String TELEPHONY = "telephony"; } From a870286183fd9eb6a5d2bb581b9133582779e255 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 2 Apr 2021 08:59:28 -0500 Subject: [PATCH 129/221] Removed references to TrustServiceUrl (#1126) --- .../bot/builder/BotFrameworkAdapter.java | 20 ++--- .../authentication/AppCredentials.java | 83 +------------------ .../authentication/JwtTokenValidation.java | 8 +- .../connector/JwtTokenValidationTests.java | 39 --------- .../MicrosoftAppCredentialsTests.java | 21 ----- 5 files changed, 7 insertions(+), 164 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java index 1d079fea5..6e1fa16d4 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/BotFrameworkAdapter.java @@ -379,21 +379,11 @@ public CompletableFuture continueConversation( context.getTurnState().add(BOT_IDENTITY_KEY, claimsIdentity); context.getTurnState().add(OAUTH_SCOPE_KEY, audience); - String appIdFromClaims = JwtTokenValidation.getAppIdFromClaims(claimsIdentity.claims()); - return credentialProvider.isValidAppId(appIdFromClaims).thenCompose(isValidAppId -> { - // If we receive a valid app id in the incoming token claims, add the - // channel service URL to the trusted services list so we can send messages - // back. - if (!StringUtils.isEmpty(appIdFromClaims) && isValidAppId) { - AppCredentials.trustServiceUrl(reference.getServiceUrl()); - } - - return createConnectorClient(reference.getServiceUrl(), claimsIdentity, audience) - .thenCompose(connectorClient -> { - context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); - return runPipeline(context, callback); - }); - }); + return createConnectorClient(reference.getServiceUrl(), claimsIdentity, audience) + .thenCompose(connectorClient -> { + context.getTurnState().add(CONNECTOR_CLIENT_KEY, connectorClient); + return runPipeline(context, callback); + }); } catch (Exception e) { pipelineResult.completeExceptionally(e); } diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java index 497d94125..5be313ce4 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/AppCredentials.java @@ -7,14 +7,10 @@ import com.microsoft.bot.restclient.credentials.ServiceClientCredentials; import okhttp3.OkHttpClient; import org.apache.commons.lang3.StringUtils; -import org.slf4j.LoggerFactory; import java.net.MalformedURLException; import java.net.URL; -import java.time.LocalDateTime; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; /** * Base abstraction for AAD credentials for auth and caching. @@ -24,16 +20,6 @@ *

*/ public abstract class AppCredentials implements ServiceClientCredentials { - private static final int EXPIRATION_SLACK = 5; - private static final int EXPIRATION_DAYS = 1; - private static ConcurrentMap trustHostNames = new ConcurrentHashMap<>(); - - static { - trustHostNames.put("api.botframework.com", LocalDateTime.MAX); - trustHostNames.put("token.botframework.com", LocalDateTime.MAX); - trustHostNames.put("api.botframework.azure.us", LocalDateTime.MAX); - trustHostNames.put("token.botframework.azure.us", LocalDateTime.MAX); - } private String appId; private String authTenant; @@ -62,73 +48,6 @@ public AppCredentials(String withChannelAuthTenant, String withOAuthScope) { : withOAuthScope; } - /** - * Adds the host of service url to trusted hosts. - * - * @param serviceUrl The service URI. - */ - public static void trustServiceUrl(String serviceUrl) { - trustServiceUrl(serviceUrl, LocalDateTime.now().plusDays(EXPIRATION_DAYS)); - } - - /** - * Adds the host of service url to trusted hosts with the specified expiration. - * - *

- * Note: The will fail to add if the url is not valid. - *

- * - * @param serviceUrl The service URI. - * @param expirationTime The expiration time after which this service url is not - * trusted anymore. - */ - public static void trustServiceUrl(String serviceUrl, LocalDateTime expirationTime) { - try { - URL url = new URL(serviceUrl); - trustServiceUrl(url, expirationTime); - } catch (MalformedURLException e) { - LoggerFactory.getLogger(MicrosoftAppCredentials.class).error("trustServiceUrl", e); - } - } - - /** - * Adds the host of service url to trusted hosts with the specified expiration. - * - * @param serviceUrl The service URI. - * @param expirationTime The expiration time after which this service url is not - * trusted anymore. - */ - public static void trustServiceUrl(URL serviceUrl, LocalDateTime expirationTime) { - trustHostNames.put(serviceUrl.getHost(), expirationTime); - } - - /** - * Checks if the service url is for a trusted host or not. - * - * @param serviceUrl The service URI. - * @return true if the service is trusted. - */ - public static boolean isTrustedServiceUrl(String serviceUrl) { - try { - URL url = new URL(serviceUrl); - return isTrustedServiceUrl(url); - } catch (MalformedURLException e) { - LoggerFactory.getLogger(AppCredentials.class).error("trustServiceUrl", e); - return false; - } - } - - /** - * Checks if the service url is for a trusted host or not. - * - * @param serviceUrl The service URI. - * @return true if the service is trusted. - */ - public static boolean isTrustedServiceUrl(URL serviceUrl) { - return !trustHostNames.getOrDefault(serviceUrl.getHost(), LocalDateTime.MIN) - .isBefore(LocalDateTime.now().minusMinutes(EXPIRATION_SLACK)); - } - /** * Gets the App ID for this credential. * @@ -245,7 +164,7 @@ boolean shouldSetToken(String url) { if (StringUtils.isBlank(getAppId()) || getAppId().equals(AuthenticationConstants.ANONYMOUS_SKILL_APPID)) { return false; } - return isTrustedServiceUrl(url); + return true; } // lazy Authenticator create. diff --git a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java index 286eab799..34a77a78f 100644 --- a/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java +++ b/libraries/bot-connector/src/main/java/com/microsoft/bot/connector/authentication/JwtTokenValidation.java @@ -98,13 +98,7 @@ public static CompletableFuture authenticateRequest( return JwtTokenValidation.validateAuthHeader( authHeader, credentials, channelProvider, activity.getChannelId(), activity.getServiceUrl(), authConfig - ) - - .thenApply(identity -> { - // On the standard Auth path, we need to trust the URL that was incoming. - MicrosoftAppCredentials.trustServiceUrl(activity.getServiceUrl()); - return identity; - }); + ); } /** diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java index 7b307e5eb..b4bbb8b70 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/JwtTokenValidationTests.java @@ -155,24 +155,6 @@ public void Emulator_AuthHeader_CorrectAppIdAndServiceUrl_WithPrivateChannelServ "TheChannel"); } - /** - * Tests with a valid Token and service url; and ensures that Service url is added to Trusted service url list. - */ - @Test - public void ChannelMsaHeaderValidServiceUrlShouldBeTrusted() throws IOException, ExecutionException, InterruptedException { - String header = getHeaderToken(); - CredentialProvider credentials = new SimpleCredentialProvider(APPID, ""); - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/"); - JwtTokenValidation.authenticateRequest( - activity, - header, - credentials, - new SimpleChannelProvider()).join(); - - Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/")); - } - /** * Tests with a valid Token and invalid service url; and ensures that Service url is NOT added to Trusted service url list. */ @@ -192,7 +174,6 @@ public void ChannelMsaHeaderInvalidServiceUrlShouldNotBeTrusted() throws IOExcep Assert.fail("Should have thrown AuthenticationException"); } catch (CompletionException e) { Assert.assertTrue(e.getCause() instanceof AuthenticationException); - Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/")); } } @@ -255,26 +236,6 @@ public void ChannelNoHeaderAuthenticationEnabledShouldThrow() throws IOException } catch (CompletionException e) { Assert.assertTrue(e.getCause() instanceof AuthenticationException); } - - Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://smba.trafficmanager.net/amer-client-ss.msg/")); - } - - /** - * Tests with no authentication header and makes sure the service URL is not added to the trusted list. - */ - @Test - public void ChannelAuthenticationDisabledServiceUrlShouldNotBeTrusted() throws ExecutionException, InterruptedException { - String header = ""; - CredentialProvider credentials = new SimpleCredentialProvider("", ""); - - Activity activity = new Activity(ActivityTypes.MESSAGE); - activity.setServiceUrl("https://webchat.botframework.com/"); - ClaimsIdentity identity = JwtTokenValidation.authenticateRequest( - activity, - header, - credentials, - new SimpleChannelProvider()).join(); - Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://webchat.botframework.com/")); } @Test diff --git a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/MicrosoftAppCredentialsTests.java b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/MicrosoftAppCredentialsTests.java index eb466f119..c5cc21b6b 100644 --- a/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/MicrosoftAppCredentialsTests.java +++ b/libraries/bot-connector/src/test/java/com/microsoft/bot/connector/MicrosoftAppCredentialsTests.java @@ -14,27 +14,6 @@ import java.time.LocalDateTime; public class MicrosoftAppCredentialsTests { - @Test - public void ValidUrlTrusted() { - MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com"); - Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com")); - } - - @Test - public void InvalidUrlTrusted() { - MicrosoftAppCredentials.trustServiceUrl("badurl"); - Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("badurl")); - } - - @Test - public void TrustedUrlExpiration() throws InterruptedException { - // There is a +5 minute window for an expired url - MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com", LocalDateTime.now().minusMinutes(6)); - Assert.assertFalse(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com")); - - MicrosoftAppCredentials.trustServiceUrl("https://goodurl.com", LocalDateTime.now().minusMinutes(4)); - Assert.assertTrue(MicrosoftAppCredentials.isTrustedServiceUrl("https://goodurl.com")); - } @Test public void ValidateAuthEndpoint() { From 4008d1e5c04f93fdbb8319c511c8928d020def6a Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 2 Apr 2021 09:18:11 -0500 Subject: [PATCH 130/221] Removed obsolete DialogManager property. (#1127) Co-authored-by: tracyboehrer --- .../java/com/microsoft/bot/dialogs/DialogContext.java | 8 -------- 1 file changed, 8 deletions(-) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java index 277f50e85..1bd9c7a0b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogContext.java @@ -166,14 +166,6 @@ public TurnContextStateCollection getServices() { return services; } - /** - * Gets the current DialogManager for this dialogContext. - * @return The root dialogManager that was used to create this dialog context chain. - */ - public DialogManager getDialogManager() { - return getContext().getTurnState().get(DialogManager.class); - } - /** * Starts a new dialog and pushes it onto the dialog stack. * @param dialogId ID of the dialog to start. From 30ac79513a84a1253eaf453ea8857d4dda31c78b Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Fri, 2 Apr 2021 09:52:33 -0500 Subject: [PATCH 131/221] Sample 23.facebook-events (#1128) * Initial Sample Code * remove vscode files --- samples/23.facebook-events/LICENSE | 21 + samples/23.facebook-events/README.md | 91 ++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/23.facebook-events/pom.xml | 243 ++++++++++ .../sample/facebookevents/Application.java | 67 +++ .../facebookevents/bot/FacebookBot.java | 253 +++++++++++ .../facebookmodel/FacebookMessage.java | 97 ++++ .../facebookmodel/FacebookOptin.java | 57 +++ .../facebookmodel/FacebookPayload.java | 114 +++++ .../facebookmodel/FacebookPostback.java | 61 +++ .../facebookmodel/FacebookQuickReply.java | 36 ++ .../facebookmodel/FacebookRecipient.java | 33 ++ .../facebookmodel/FacebookSender.java | 33 ++ .../src/main/resources/application.properties | 3 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++++++++ .../facebookevents/ApplicationTest.java | 19 + 20 files changed, 2129 insertions(+) create mode 100644 samples/23.facebook-events/LICENSE create mode 100644 samples/23.facebook-events/README.md create mode 100644 samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/23.facebook-events/pom.xml create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java create mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java create mode 100644 samples/23.facebook-events/src/main/resources/application.properties create mode 100644 samples/23.facebook-events/src/main/resources/log4j2.json create mode 100644 samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/23.facebook-events/src/main/webapp/index.html create mode 100644 samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java diff --git a/samples/23.facebook-events/LICENSE b/samples/23.facebook-events/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/23.facebook-events/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/23.facebook-events/README.md b/samples/23.facebook-events/README.md new file mode 100644 index 000000000..f839c4916 --- /dev/null +++ b/samples/23.facebook-events/README.md @@ -0,0 +1,91 @@ +# Facebook Events + +Bot Framework v4 facebook events bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to integrate and consume Facebook specific payloads, such as postbacks, quick replies and opt-in events. Since Bot Framework supports multiple Facebook pages for a single bot, we also show how to know the page to which the message was sent, so developers can have custom behavior per page. + +More information about configuring a bot for Facebook Messenger can be found here: [Connect a bot to Facebook](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-facebook) + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\facebook-events-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "facebookBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="facebookBotPlan" newWebAppName="facebookBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="facebookBot" newAppServicePlanName="facebookBotPlan" appServicePlanLocation="westus" --name "facebookBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Facebook Quick Replies](https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies/0) +- [Facebook PostBack](https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_postbacks/) +- [Facebook Opt-in](https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_optins/) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json b/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json b/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/23.facebook-events/pom.xml b/samples/23.facebook-events/pom.xml new file mode 100644 index 000000000..c96875812 --- /dev/null +++ b/samples/23.facebook-events/pom.xml @@ -0,0 +1,243 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + facebook-events + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Facebook Events sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.facebookevents.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + compile + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.facebookevents.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java new file mode 100644 index 000000000..a0dc8c667 --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.facebookevents; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.facebookevents.bot.FacebookBot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot() { + return new FacebookBot(); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java new file mode 100644 index 000000000..b03e541e4 --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java @@ -0,0 +1,253 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.facebookevents.bot; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.dialogs.choices.Choice; +import com.microsoft.bot.dialogs.choices.ChoiceFactory; +import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookMessage; +import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookOptin; +import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookPayload; +import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookPostback; +import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookQuickReply; +import com.microsoft.bot.schema.ActionTypes; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.CardAction; +import com.microsoft.bot.schema.HeroCard; +import com.microsoft.bot.schema.Serialization; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * This class implements the functionality of the Bot. + */ +public class FacebookBot extends ActivityHandler { + + // These are the options provided to the user when they message the bot + final String FacebookPageIdOption = "Facebook Id"; + final String QuickRepliesOption = "Quick Replies"; + final String PostBackOption = "PostBack"; + + protected final Logger Logger; + + public FacebookBot() { + Logger = LoggerFactory.getLogger(FacebookBot.class); + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + Logger.info("Processing a Message Activity."); + + // Show choices if the Facebook Payload from ChannelData instanceof not handled + try { + return processFacebookPayload(turnContext, turnContext.getActivity().getChannelData()) + .thenCompose(result -> { + if (result == false) { + return showChoices(turnContext); + } else { + return CompletableFuture.completedFuture(null); + } + }); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return CompletableFuture.completedFuture(null); + } + } + + @Override + protected CompletableFuture onEventActivity(TurnContext turnContext) { + Logger.info("Processing an Event Activity."); + + // Analyze Facebook payload from EventActivity.Value + try { + return processFacebookPayload(turnContext, turnContext.getActivity().getValue()).thenApply(result -> null); + } catch (JsonProcessingException e) { + e.printStackTrace(); + return CompletableFuture.completedFuture(null); + } + } + + private CompletableFuture showChoices(TurnContext turnContext) { + // Create choices for the prompt + List choices = new ArrayList(); + + Choice firstChoice = new Choice(); + firstChoice.setValue(QuickRepliesOption); + CardAction firstChoiceCardAction = new CardAction(); + firstChoiceCardAction.setTitle(QuickRepliesOption); + firstChoiceCardAction.setType(ActionTypes.POST_BACK); + firstChoiceCardAction.setValue(QuickRepliesOption); + firstChoice.setAction(firstChoiceCardAction); + choices.add(firstChoice); + + Choice secondChoice = new Choice(); + firstChoice.setValue(FacebookPageIdOption); + CardAction secondChoiceCardAction = new CardAction(); + secondChoiceCardAction.setTitle(FacebookPageIdOption); + secondChoiceCardAction.setType(ActionTypes.POST_BACK); + secondChoiceCardAction.setValue(FacebookPageIdOption); + secondChoice.setAction(secondChoiceCardAction); + choices.add(secondChoice); + + Choice thirdChoice = new Choice(); + firstChoice.setValue(PostBackOption); + CardAction thirdChoiceCardAction = new CardAction(); + thirdChoiceCardAction.setTitle(PostBackOption); + thirdChoiceCardAction.setType(ActionTypes.POST_BACK); + thirdChoiceCardAction.setValue(PostBackOption); + thirdChoice.setAction(thirdChoiceCardAction); + + choices.add(thirdChoice); + + // Create the prompt message + Activity message = ChoiceFactory.forChannel( + turnContext.getActivity().getChannelId(), + choices, + "What Facebook feature would you like to try? Here are some quick replies to choose from!" + ); + return turnContext.sendActivity(message).thenApply(result -> null); + } + + + private CompletableFuture processFacebookPayload( + TurnContext turnContext, + Object data + ) throws JsonProcessingException { + // At this point we know we are on Facebook channel, and can consume the + // Facebook custom payload + // present in channelData. + try { + FacebookPayload facebookPayload = Serialization.safeGetAs(data, FacebookPayload.class); + + if (facebookPayload != null) { + // PostBack + if (facebookPayload.getPostBack() != null) { + return onFacebookPostBack(turnContext, facebookPayload.getPostBack()).thenApply(result -> true); + } else if (facebookPayload.getOptin() != null) { + // Optin + return onFacebookOptin(turnContext, facebookPayload.getOptin()).thenApply(result -> true); + } else if ( + facebookPayload.getMessage() != null && facebookPayload.getMessage().getQuickReply() != null + ) { + // Quick reply + return onFacebookQuickReply(turnContext, facebookPayload.getMessage().getQuickReply()) + .thenApply(result -> true); + } else if (facebookPayload.getMessage() != null && facebookPayload.getMessage().getIsEcho()) { + // Echo + return onFacebookEcho(turnContext, facebookPayload.getMessage()).thenApply(result -> true); + } else { + return turnContext.sendActivity("This sample is intended to be used with a Facebook bot.") + .thenApply(result -> false); + } + + // TODO: Handle other events that you're interested in... + } + } catch (JsonProcessingException ex) { + if (turnContext.getActivity().getChannelId() != Channels.FACEBOOK) { + turnContext.sendActivity("This sample is intended to be used with a Facebook bot."); + } else { + throw ex; + } + } + + return CompletableFuture.completedFuture(false); + } + + protected CompletableFuture onFacebookOptin(TurnContext turnContext, FacebookOptin optin) { + Logger.info("Optin message received."); + + // TODO: Your optin event handling logic here... + return CompletableFuture.completedFuture(null); + } + + protected CompletableFuture onFacebookEcho(TurnContext turnContext, FacebookMessage facebookMessage) { + Logger.info("Echo message received."); + + // TODO: Your echo event handling logic here... + return CompletableFuture.completedFuture(null); + } + + protected CompletableFuture onFacebookPostBack(TurnContext turnContext, FacebookPostback postBack) { + Logger.info("PostBack message received."); + + // TODO: Your PostBack handling logic here... + + // Answer the postback, and show choices + Activity reply = MessageFactory.text("Are you sure?"); + return turnContext.sendActivity(reply).thenCompose(result -> { + return showChoices(turnContext); + }); + } + + protected CompletableFuture onFacebookQuickReply(TurnContext turnContext, FacebookQuickReply quickReply) { + Logger.info("QuickReply message received."); + + // TODO: Your quick reply event handling logic here... + + // Process the message by checking the Activity.getText(). The + // FacebookQuickReply could also contain a json payload. + + // Initially the bot offers to showcase 3 Facebook features: Quick replies, + // PostBack and getting the Facebook Page Name. + switch (turnContext.getActivity().getText()) { + // Here we showcase how to obtain the Facebook page id. + // This can be useful for the Facebook multi-page support provided by the Bot + // Framework. + // The Facebook page id from which the message comes from instanceof in + // turnContext.getActivity().getRecipient().getId(). + case FacebookPageIdOption: { + Activity reply = MessageFactory.text( + String.format( + "This message comes from the following Facebook Page: %s", + turnContext.getActivity().getRecipient().getId() + ) + ); + return turnContext.sendActivity(reply).thenCompose(result -> { + return showChoices(turnContext); + }); + } + + // Here we send a HeroCard with 2 options that will trigger a Facebook PostBack. + case PostBackOption: { + HeroCard card = new HeroCard(); + card.setText("Is 42 the answer to the ultimate question of Life, the Universe, and Everything?"); + + List buttons = new ArrayList(); + + CardAction yesAction = new CardAction(); + yesAction.setTitle("Yes"); + yesAction.setType(ActionTypes.POST_BACK); + yesAction.setValue("Yes"); + buttons.add(yesAction); + + CardAction noAction = new CardAction(); + noAction.setTitle("No"); + noAction.setType(ActionTypes.POST_BACK); + noAction.setValue("No"); + buttons.add(noAction); + + card.setButtons(buttons); + + Activity reply = MessageFactory.attachment(card.toAttachment()); + return turnContext.sendActivity(reply).thenApply(result -> null); + } + + // By default we offer the users different actions that the bot supports, + // through quick replies. + case QuickRepliesOption: + default: { + return showChoices(turnContext); + } + } + } +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java new file mode 100644 index 000000000..b22d285ab --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java @@ -0,0 +1,97 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +public class FacebookMessage { + + @JsonProperty(value = "mid") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String messageId; + + @JsonProperty(value = "text") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String text; + + @JsonProperty(value = "is_echo") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private boolean isEcho; + + @JsonProperty(value = "quick_reply") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookQuickReply quickReply; + + /** + * Gets the message Id from Facebook. + * @return the MessageId value as a String. + */ + public String getMessageId() { + return this.messageId; + } + + /** + * Sets the message Id from Facebook. + * @param withMessageId The MessageId value. + */ + public void setMessageId(String withMessageId) { + this.messageId = withMessageId; + } + + /** + * Gets the message text. + * @return the Text value as a String. + */ + public String getText() { + return this.text; + } + + /** + * Sets the message text. + * @param withText The Text value. + */ + public void setText(String withText) { + this.text = withText; + } + + /** + * Gets whether the message is an echo message. See + * {@link + * https://developers#facebook#com/docs/messenger-platform/reference/webhook-events/message-echoes/} Echo Message + * @return the IsEcho value as a boolean. + */ + public boolean getIsEcho() { + return this.isEcho; + } + + /** + * Sets whether the message is an echo message. See + * {@link + * https://developers#facebook#com/docs/messenger-platform/reference/webhook-events/message-echoes/} Echo Message + * @param withIsEcho The IsEcho value. + */ + public void setIsEcho(boolean withIsEcho) { + this.isEcho = withIsEcho; + } + + /** + * Gets the quick reply. + * @return the QuickReply value as a FacebookQuickReply. + */ + public FacebookQuickReply getQuickReply() { + return this.quickReply; + } + + /** + * Sets the quick reply. + * @param withQuickReply The QuickReply value. + */ + public void setQuickReply(FacebookQuickReply withQuickReply) { + this.quickReply = withQuickReply; + } + +} + diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java new file mode 100644 index 000000000..3831985cc --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java @@ -0,0 +1,57 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A Facebook optin event payload definition. + * + * See https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_optins/ + * for more information on messaging_optin. + */ +public class FacebookOptin { + + @JsonProperty(value = "ref") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String ref; + + @JsonProperty(value = "user_ref") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String userRef; + + /** + * Gets the optin data ref. + * @return the Ref value as a String. + */ + public String getRef() { + return this.ref; + } + + /** + * Sets the optin data ref. + * @param withRef The Ref value. + */ + public void setRef(String withRef) { + this.ref = withRef; + } + + /** + * Gets the optin user ref. + * @return the UserRef value as a String. + */ + public String getUserRef() { + return this.userRef; + } + + /** + * Sets the optin user ref. + * @param withUserRef The UserRef value. + */ + public void setUserRef(String withUserRef) { + this.userRef = withUserRef; + } + +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java new file mode 100644 index 000000000..060511b5a --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Simple version of the payload received from the Facebook channel. + */ +public class FacebookPayload { + + @JsonProperty(value = "sender") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookSender sender; + + @JsonProperty(value = "recipient") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookRecipient recipient; + + @JsonProperty(value = "message") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookMessage message; + + @JsonProperty(value = "postback") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookPostback postBack; + + @JsonProperty(value = "optin") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private FacebookOptin optin; + + /** + * Gets the sender of the message. + * @return the Sender value as a FacebookSender. + */ + public FacebookSender getSender() { + return this.sender; + } + + /** + * Sets the sender of the message. + * @param withSender The Sender value. + */ + public void setSender(FacebookSender withSender) { + this.sender = withSender; + } + + /** + * Gets the recipient of the message. + * @return the Recipient value as a FacebookRecipient. + */ + public FacebookRecipient getRecipient() { + return this.recipient; + } + + /** + * Sets the recipient of the message. + * @param withRecipient The Recipient value. + */ + public void setRecipient(FacebookRecipient withRecipient) { + this.recipient = withRecipient; + } + + /** + * Gets the message. + * @return the Message value as a FacebookMessage. + */ + public FacebookMessage getMessage() { + return this.message; + } + + /** + * Sets the message. + * @param withMessage The Message value. + */ + public void setMessage(FacebookMessage withMessage) { + this.message = withMessage; + } + + /** + * Gets the postback payload if available. + * @return the PostBack value as a FacebookPostback. + */ + public FacebookPostback getPostBack() { + return this.postBack; + } + + /** + * Sets the postback payload if available. + * @param withPostBack The PostBack value. + */ + public void setPostBack(FacebookPostback withPostBack) { + this.postBack = withPostBack; + } + + /** + * Gets the optin payload if available. + * @return the Optin value as a FacebookOptin. + */ + public FacebookOptin getOptin() { + return this.optin; + } + + /** + * Sets the optin payload if available. + * @param withOptin The Optin value. + */ + public void setOptin(FacebookOptin withOptin) { + this.optin = withOptin; + } + +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java new file mode 100644 index 000000000..0dacaa5f2 --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Definition for Facebook PostBack payload. Present on calls + * frommessaging_postback webhook event. + * + * See + * {@link + * https://developers#getfacebook()#com/docs/messenger-platform/reference/webhook-events/messaging_postbacks/} + * Facebook messaging_postback + */ +public class FacebookPostback { + + @JsonProperty(value = "payload") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String payload; + + @JsonProperty(value = "title") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String title; + + /** + * Gets payload of the PostBack. Could be an Object depending on + * the Object sent. + * @return the Payload value as a String. + */ + public String getPayload() { + return this.payload; + } + + /** + * Sets payload of the PostBack. Could be an Object depending on + * the Object sent. + * @param withPayload The Payload value. + */ + public void setPayload(String withPayload) { + this.payload = withPayload; + } + + /** + * Gets the title of the postback. + * @return the Title value as a String. + */ + public String getTitle() { + return this.title; + } + + /** + * Sets the title of the postback. + * @param withTitle The Title value. + */ + public void setTitle(String withTitle) { + this.title = withTitle; + } +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java new file mode 100644 index 000000000..433524d93 --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java @@ -0,0 +1,36 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * A Facebook quick reply. + * + * See + * {@link + * https://developers#getfacebook()#com/docs/messenger-platform/send-messages/quick-replies/} + * Quick Replies Facebook Documentation + */ +public class FacebookQuickReply { + + @JsonProperty(value = "payload") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String payload; + + /** + * @return the Payload value as a String. + */ + public String getPayload() { + return this.payload; + } + + /** + * @param withPayload The Payload value. + */ + public void setPayload(String withPayload) { + this.payload = withPayload; + } +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java new file mode 100644 index 000000000..34d0527a6 --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines a Facebook recipient. + */ +public class FacebookRecipient { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + /** + * The Facebook Id of the recipient. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * The Facebook Id of the recipient. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } +} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java new file mode 100644 index 000000000..da96fdf4f --- /dev/null +++ b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java @@ -0,0 +1,33 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.facebookevents.facebookmodel; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; + +/** + * Defines a Facebook sender. + */ +public class FacebookSender { + + @JsonProperty(value = "id") + @JsonInclude(JsonInclude.Include.NON_EMPTY) + private String id; + + /** + * The Facebook Id of the sender. + * @return the Id value as a String. + */ + public String getId() { + return this.id; + } + + /** + * The Facebook Id of the sender. + * @param withId The Id value. + */ + public void setId(String withId) { + this.id = withId; + } +} diff --git a/samples/23.facebook-events/src/main/resources/application.properties b/samples/23.facebook-events/src/main/resources/application.properties new file mode 100644 index 000000000..c5c87d7f4 --- /dev/null +++ b/samples/23.facebook-events/src/main/resources/application.properties @@ -0,0 +1,3 @@ +MicrosoftAppId=7455f666-b11d-4ce8-8c81-36370eaae99a +MicrosoftAppPassword=1G_Rv.yT8eDjg.2nd6S6j0__HGxN5T.epz +server.port=3978 diff --git a/samples/23.facebook-events/src/main/resources/log4j2.json b/samples/23.facebook-events/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/23.facebook-events/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF b/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml b/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/23.facebook-events/src/main/webapp/index.html b/samples/23.facebook-events/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/23.facebook-events/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java b/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java new file mode 100644 index 000000000..2113bce51 --- /dev/null +++ b/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.facebookevents; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 3041eca54dde82b37bc838125232d485ee444029 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 7 Apr 2021 11:30:11 -0500 Subject: [PATCH 132/221] Sample Code (#1136) --- .../.vscode/settings.json | 3 + samples/14.nlp-with-dispatch/LICENSE | 21 + samples/14.nlp-with-dispatch/README.md | 103 +++ .../template-with-new-rg.json | 291 +++++++++ .../template-with-preexisting-rg.json | 259 ++++++++ samples/14.nlp-with-dispatch/pom.xml | 248 +++++++ .../sample/nlpwithdispatch/Application.java | 73 +++ .../sample/nlpwithdispatch/BotServices.java | 12 + .../nlpwithdispatch/BotServicesImpl.java | 61 ++ .../bot/sample/nlpwithdispatch/Intent.java | 46 ++ .../nlpwithdispatch/PredictionResult.java | 27 + .../nlpwithdispatch/bots/DispatchBot.java | 229 +++++++ .../src/main/resources/HomeAutomation.json | 605 ++++++++++++++++++ .../src/main/resources/QnAMaker.tsv | 11 + .../src/main/resources/Weather.json | 315 +++++++++ .../src/main/resources/application.properties | 11 + .../src/main/resources/log4j2.json | 18 + .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 418 ++++++++++++ .../nlpwithdispatch/ApplicationTest.java | 19 + 21 files changed, 2785 insertions(+) create mode 100644 samples/14.nlp-with-dispatch/.vscode/settings.json create mode 100644 samples/14.nlp-with-dispatch/LICENSE create mode 100644 samples/14.nlp-with-dispatch/README.md create mode 100644 samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/14.nlp-with-dispatch/pom.xml create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java create mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java create mode 100644 samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json create mode 100644 samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv create mode 100644 samples/14.nlp-with-dispatch/src/main/resources/Weather.json create mode 100644 samples/14.nlp-with-dispatch/src/main/resources/application.properties create mode 100644 samples/14.nlp-with-dispatch/src/main/resources/log4j2.json create mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/index.html create mode 100644 samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java diff --git a/samples/14.nlp-with-dispatch/.vscode/settings.json b/samples/14.nlp-with-dispatch/.vscode/settings.json new file mode 100644 index 000000000..e0f15db2e --- /dev/null +++ b/samples/14.nlp-with-dispatch/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "java.configuration.updateBuildConfiguration": "automatic" +} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/LICENSE b/samples/14.nlp-with-dispatch/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/14.nlp-with-dispatch/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/14.nlp-with-dispatch/README.md b/samples/14.nlp-with-dispatch/README.md new file mode 100644 index 000000000..ef05e09ea --- /dev/null +++ b/samples/14.nlp-with-dispatch/README.md @@ -0,0 +1,103 @@ +# NLP with Dispatch + +Bot Framework v4 NLP with Dispatch bot sample + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that relies on multiple [LUIS.ai](https://www.luis.ai) and [QnAMaker.ai](https://www.qnamaker.ai) models for natural language processing (NLP). + +Use the Dispatch model in cases when: + +- Your bot consists of multiple language modules (LUIS + QnA) and you need assistance in routing user's utterances to these modules in order to integrate the different modules into your bot. +- Evaluate quality of intents classification of a single LUIS model. +- Create a text classification model from text files. + +This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. + +## Prerequisites + +- Java 1.8+ +- Install [Maven](https://maven.apache.org/) +- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. + +### Use Dispatch with Multiple LUIS and QnA Models + +To learn how to configure Dispatch with multiple LUIS models and QnA Maker services, refer to the steps found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-tutorial-dispatch?view=azure-bot-service-4.0). + + +## To try this sample locally +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\nlp-with-dispatch-sample.jar` + +- Test the bot using Bot Framework Emulator + + [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + + - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + + - Connect to the bot using Bot Framework Emulator + + - Launch Bot Framework Emulator + - File -> Open Bot + - Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. + +### 1. Login to Azure +From a command (or PowerShell) prompt in the root of the bot folder, execute: +`az login` + +### 2. Set the subscription +`az account set --subscription ""` + +If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. + +### 3. Create an App registration +`az ad app create --display-name "" --password "" --available-to-other-tenants` + +Replace `` and `` with your own values. + +`` is the unique name of your bot. +`` is a minimum 16 character password for your bot. + +Record the `appid` from the returned JSON + +### 4. Create the Azure resources +Replace the values for ``, ``, ``, and `` in the following commands: + +#### To a new Resource Group +`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` + +#### To an existing Resource Group +`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` + +### 5. Update app id and password +In src/main/resources/application.properties update + - `MicrosoftAppPassword` with the botsecret value + - `MicrosoftAppId` with the appid from the first step + +### 6. Deploy the code +- Execute `mvn clean package` +- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` + +If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. + +After the bot is deployed, you only need to execute #6 if you make changes to the bot. + + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Using LUIS for Language Understanding](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=js) +- [LUIS documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/LUIS/) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) + diff --git a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/14.nlp-with-dispatch/pom.xml b/samples/14.nlp-with-dispatch/pom.xml new file mode 100644 index 000000000..34e336096 --- /dev/null +++ b/samples/14.nlp-with-dispatch/pom.xml @@ -0,0 +1,248 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + nlp-with-dispatch + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java NLP with Dispatch sample using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.nlpwithdispatch.Application + + + + + junit + junit + 4.13.1 + test + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-qna + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.nlpwithdispatch.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java new file mode 100644 index 000000000..5efd67782 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.integration.AdapterWithErrorHandler; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import com.microsoft.bot.sample.nlpwithdispatch.bots.DispatchBot; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +// +// This is the starting point of the Sprint Boot Bot application. +// +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method + * with the @Bean annotation. + *

+ * + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot(BotServices botServices) { + return new DispatchBot(botServices); + } + + @Bean + public BotServices getBotServices(Configuration configuration) { + return new BotServicesImpl(configuration); + } + + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + return new AdapterWithErrorHandler(configuration); + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java new file mode 100644 index 000000000..21d585047 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java @@ -0,0 +1,12 @@ +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.qna.QnAMaker; + +public interface BotServices { + + LuisRecognizer getDispatch(); + + QnAMaker getSampleQnA(); + +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java new file mode 100644 index 000000000..f94fdb927 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java @@ -0,0 +1,61 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.ai.qna.QnAMaker; +import com.microsoft.bot.ai.qna.QnAMakerEndpoint; +import com.microsoft.bot.integration.Configuration; + +public class BotServicesImpl implements BotServices { + + private LuisRecognizer dispatch; + + private QnAMaker sampleQnA; + + public BotServicesImpl(Configuration configuration) { + // Read the setting for cognitive services (LUS, QnA) from the application.properties file. + // If includeApiResults instanceof set to true, the full response from the LUS api (LuisResult) + // will be made available in the properties collection of the RecognizerResult + + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + String.format("https://%s.api.cognitive.microsoft.com", + configuration.getProperty("LuisAPIHostName"))); + + // Set the recognizer options depending on which endpoint version you want to use. + // More details can be found in https://docs.getmicrosoft().com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); + recognizerOptions.setIncludeAPIResults(true); + recognizerOptions.setIncludeAllIntents(true); + recognizerOptions.setIncludeInstanceData(true); + + dispatch = new LuisRecognizer(recognizerOptions); + + QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); + qnaMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); + qnaMakerEndpoint.setEndpointKey(configuration.getProperty("QnAEndpointKey")); + qnaMakerEndpoint.setHost(configuration.getProperty("QnAEndpointHostName")); + + sampleQnA = new QnAMaker(qnaMakerEndpoint, null); + } + + /** + * @return the Dispatch value as a LuisRecognizer. + */ + public LuisRecognizer getDispatch() { + return this.dispatch; + } + + /** + * @return the SampleQnA value as a QnAMaker. + */ + public QnAMaker getSampleQnA() { + return this.sampleQnA; + } +} + diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java new file mode 100644 index 000000000..8f4f36235 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import java.util.List; + +public class Intent { + private String name; + private Double score; + private List childIntents; + private String topIntent; + + public String getName() { + return this.name; + } + + public void setName(String name) { + this.name = name; + } + + public Double getScore() { + return this.score; + } + + public void setScore(Double score) { + this.score = score; + } + + public List getChildIntents() { + return this.childIntents; + } + + public void setChildIntents(List childIntents) { + this.childIntents = childIntents; + } + + public String getTopIntent() { + return this.topIntent; + } + + public void setTopIntent(String topIntent) { + this.topIntent = topIntent; + } + +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java new file mode 100644 index 000000000..f331d57d3 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import java.util.List; + +public class PredictionResult { + private String topIntent; + private List intents; + + public String getTopIntent() { + return this.topIntent; + } + + public void setTopIntent(String topIntent) { + this.topIntent = topIntent; + } + + public List getIntents() { + return this.intents; + } + + public void setIntents(List intents) { + this.intents = intents; + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java new file mode 100644 index 000000000..6efd6de3c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java @@ -0,0 +1,229 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MT License. + +package com.microsoft.bot.sample.nlpwithdispatch.bots; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.fasterxml.jackson.databind.JsonNode; +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.RecognizerResult.NamedIntentScore; +import com.microsoft.bot.sample.nlpwithdispatch.BotServices; +import com.microsoft.bot.sample.nlpwithdispatch.Intent; +import com.microsoft.bot.sample.nlpwithdispatch.PredictionResult; +import com.microsoft.bot.schema.ChannelAccount; + +import org.apache.commons.lang3.StringUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class DispatchBot extends ActivityHandler { + + private final Logger _logger; + private final BotServices _botServices; + + public DispatchBot(BotServices botServices) { + _logger = LoggerFactory.getLogger(DispatchBot.class); + _botServices = botServices; + } + + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + // First, we use the dispatch model to determine which cognitive service (LUS or + // QnA) to use. + RecognizerResult recognizerResult = _botServices.getDispatch().recognize(turnContext).join(); + + // Top intent tell us which cognitive service to use. + NamedIntentScore topIntent = recognizerResult.getTopScoringIntent(); + + // Next, we call the dispatcher with the top intent. + return dispatchToTopIntent(turnContext, topIntent.intent, recognizerResult); + } + + @Override + protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { + final String WelcomeText = "Type a greeting, or a question about the weather to get started."; + + return membersAdded.stream() + .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(member -> { + String msg = String.format("Welcome to Dispatch bot %s. %s", member.getName(), WelcomeText); + return turnContext.sendActivity(MessageFactory.text(msg)); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponses -> null); + } + + private CompletableFuture dispatchToTopIntent( + TurnContext turnContext, + String intent, + RecognizerResult recognizerResult + ) { + switch (intent) { + case "l_HomeAutomation": + return processHomeAutomation(turnContext, recognizerResult); + + case "l_Weather": + return processWeather(turnContext, recognizerResult); + + case "q_sample-qna": + return processSampleQnA(turnContext); + + default: + _logger.info(String.format("Dispatch unrecognized intent: %s.", intent)); + return turnContext + .sendActivity(MessageFactory.text(String.format("Dispatch unrecognized intent: %s.", intent))) + .thenApply(result -> null); + } + } + + private CompletableFuture processHomeAutomation(TurnContext turnContext, RecognizerResult luisResult) { + _logger.info("ProcessHomeAutomationAsync"); + + // Retrieve LUIS result for Process Automation. + PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); + + Intent topIntent = predictionResult.getIntents().get(0); + return turnContext + .sendActivity(MessageFactory.text(String.format("HomeAutomation top intent %s.", topIntent.getTopIntent()))) + .thenCompose(sendResult -> { + List intents = + topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); + return turnContext + .sendActivity( + MessageFactory + .text(String.format("HomeAutomation intents detected:\n\n%s", String.join("\n\n", intents))) + ) + .thenCompose(nextSendResult -> { + if (luisResult.getEntities() != null) { + List entities = mapEntities(luisResult.getEntities()); + if (entities.size() > 0) { + return turnContext + .sendActivity( + MessageFactory.text( + String.format( + "HomeAutomation entities were found in the message:\n\n%s", + String.join("\n\n", entities) + ) + ) + ) + .thenApply(finalSendResult -> null); + } + } + return CompletableFuture.completedFuture(null); + }); + }); + } + + private List mapEntities(JsonNode entityNode) { + List entities = new ArrayList(); + for (Iterator> child = entityNode.fields(); child.hasNext();) { + Map.Entry childIntent = child.next(); + String childName = childIntent.getKey(); + if (!childName.startsWith("$")) { + entities.add(childIntent.getValue().get(0).asText()); + } + } + return entities; + } + + private PredictionResult mapPredictionResult(JsonNode luisResult) { + JsonNode prediction = luisResult.get("prediction"); + JsonNode intentsObject = prediction.get("intents"); + if (intentsObject == null) { + return null; + } + PredictionResult result = new PredictionResult(); + result.setTopIntent(prediction.get("topIntent").asText()); + List intents = new ArrayList(); + for (Iterator> it = intentsObject.fields(); it.hasNext();) { + Map.Entry intent = it.next(); + double score = intent.getValue().get("score").asDouble(); + String intentName = intent.getKey().replace(".", "_").replace(" ", "_"); + Intent newIntent = new Intent(); + newIntent.setName(intentName); + newIntent.setScore(score); + JsonNode childNode = intent.getValue().get("childApp"); + if (childNode != null) { + newIntent.setTopIntent(childNode.get("topIntent").asText()); + List childIntents = new ArrayList(); + JsonNode childIntentNodes = childNode.get("intents"); + for (Iterator> child = childIntentNodes.fields(); child.hasNext();) { + Map.Entry childIntent = child.next(); + double childScore = childIntent.getValue().get("score").asDouble(); + String childIntentName = childIntent.getKey(); + Intent newChildIntent = new Intent(); + newChildIntent.setName(childIntentName); + newChildIntent.setScore(childScore); + childIntents.add(newChildIntent); + } + newIntent.setChildIntents(childIntents); + } + + intents.add(newIntent); + } + result.setIntents(intents); + return result; + } + + private CompletableFuture processWeather(TurnContext turnContext, RecognizerResult luisResult) { + _logger.info("ProcessWeatherAsync"); + + // Retrieve LUIS result for Process Automation. + PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); + + Intent topIntent = predictionResult.getIntents().get(0); + return turnContext + .sendActivity(MessageFactory.text(String.format("ProcessWeather top intent %s.", topIntent.getTopIntent()))) + .thenCompose(sendResult -> { + List intents = + topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); + return turnContext + .sendActivity( + MessageFactory + .text(String.format("ProcessWeather Intents detected:\n\n%s", String.join("\n\n", intents))) + ) + .thenCompose(secondResult -> { + if (luisResult.getEntities() != null) { + List entities = mapEntities(luisResult.getEntities()); + if (entities.size() > 0) { + return turnContext + .sendActivity( + MessageFactory.text( + String.format( + "ProcessWeather entities were found in the message:\n\n%s", + String.join("\n\n", entities) + ) + ) + ) + .thenApply(finalResult -> null); + } + } + return CompletableFuture.completedFuture(null); + }); + }); + } + + private CompletableFuture processSampleQnA(TurnContext turnContext) { + _logger.info("ProcessSampleQnAAsync"); + + return _botServices.getSampleQnA().getAnswers(turnContext, null).thenCompose(results -> { + if (results.length > 0) { + return turnContext.sendActivity(MessageFactory.text(results[0].getAnswer())).thenApply(result -> null); + } else { + return turnContext + .sendActivity(MessageFactory.text("Sorry, could not find an answer in the Q and A system.")) + .thenApply(result -> null); + } + }); + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json b/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json new file mode 100644 index 000000000..245daedce --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json @@ -0,0 +1,605 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "Home Automation", + "desc": "Home Automation LUIS application - Bot Builder Samples", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "HomeAutomation" + }, + { + "name": "None" + } + ], + "entities": [ + { + "name": "Device", + "roles": [] + }, + { + "name": "deviceProperty", + "roles": [] + }, + { + "name": "Room", + "roles": [] + } + ], + "composites": [], + "closedLists": [ + { + "name": "Operation", + "subLists": [ + { + "canonicalForm": "off", + "list": [ + "off", + "turn off", + "switch off", + "lock", + "out", + "shut down", + "stop" + ] + }, + { + "canonicalForm": "on", + "list": [ + "on", + "turn on", + "switch on", + "unlock", + "un lock", + "boot up", + "start" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [ + { + "name": "Device_PatternAny", + "roles": [], + "explicitList": [] + }, + { + "name": "Room_PatternAny", + "roles": [], + "explicitList": [] + } + ], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "number", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [ + { + "pattern": "{Device_PatternAny} in {Room_PatternAny} on [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} on [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "turn off {Device_PatternAny} in {Room_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "turn on {Device_PatternAny} in {Room_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "turn on {Device_PatternAny}", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} in {Room_PatternAny} off [please]", + "intent": "HomeAutomation" + }, + { + "pattern": "{Device_PatternAny} off [please]", + "intent": "HomeAutomation" + } + ], + "utterances": [ + { + "text": "breezeway on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "change temperature to seventy two degrees", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 7, + "endPos": 17 + } + ] + }, + { + "text": "coffee bar on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 9 + } + ] + }, + { + "text": "decrease temperature for me please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 9, + "endPos": 19 + } + ] + }, + { + "text": "dim kitchen lights to 25 .", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 4, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 17 + } + ] + }, + { + "text": "fish pond off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "fish pond on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 8 + } + ] + }, + { + "text": "illuminate please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 0, + "endPos": 9 + } + ] + }, + { + "text": "living room lamp on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 15 + } + ] + }, + { + "text": "living room lamps off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 10 + }, + { + "entity": "Device", + "startPos": 12, + "endPos": 16 + } + ] + }, + { + "text": "lock the doors for me please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 13 + } + ] + }, + { + "text": "lower your volume", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "deviceProperty", + "startPos": 11, + "endPos": 16 + } + ] + }, + { + "text": "make camera 1 off please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 12 + } + ] + }, + { + "text": "make some coffee", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 10, + "endPos": 15 + } + ] + }, + { + "text": "play dvd", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 7 + } + ] + }, + { + "text": "set lights out in bedroom", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "Room", + "startPos": 18, + "endPos": 24 + } + ] + }, + { + "text": "set lights to bright", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "deviceProperty", + "startPos": 14, + "endPos": 19 + } + ] + }, + { + "text": "set lights to concentrate", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 4, + "endPos": 9 + }, + { + "entity": "deviceProperty", + "startPos": 14, + "endPos": 24 + } + ] + }, + { + "text": "shut down my work computer", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 18, + "endPos": 25 + } + ] + }, + { + "text": "snap switch fan fifty percent", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 12, + "endPos": 14 + } + ] + }, + { + "text": "start master bedroom light.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 6, + "endPos": 19 + }, + { + "entity": "Device", + "startPos": 21, + "endPos": 25 + } + ] + }, + { + "text": "theater on please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 0, + "endPos": 6 + } + ] + }, + { + "text": "turn dimmer off", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 10 + } + ] + }, + { + "text": "turn off ac please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 10 + } + ] + }, + { + "text": "turn off foyer lights", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 9, + "endPos": 13 + }, + { + "entity": "Device", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "turn off living room light", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 9, + "endPos": 19 + }, + { + "entity": "Device", + "startPos": 21, + "endPos": 25 + } + ] + }, + { + "text": "turn off staircase", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 17 + } + ] + }, + { + "text": "turn off venice lamp", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 16, + "endPos": 19 + } + ] + }, + { + "text": "turn on bathroom heater", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 8, + "endPos": 15 + }, + { + "entity": "Device", + "startPos": 17, + "endPos": 22 + } + ] + }, + { + "text": "turn on external speaker", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 23 + } + ] + }, + { + "text": "turn on kitchen faucet", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 8, + "endPos": 14 + }, + { + "entity": "Device", + "startPos": 16, + "endPos": 21 + } + ] + }, + { + "text": "turn on light in bedroom", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 12 + }, + { + "entity": "Room", + "startPos": 17, + "endPos": 23 + } + ] + }, + { + "text": "turn on my bedroom lights.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 11, + "endPos": 17 + }, + { + "entity": "Device", + "startPos": 19, + "endPos": 24 + } + ] + }, + { + "text": "turn on the furnace room lights", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 12, + "endPos": 23 + }, + { + "entity": "Device", + "startPos": 25, + "endPos": 30 + } + ] + }, + { + "text": "turn on the internet in my bedroom please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Room", + "startPos": 27, + "endPos": 33 + } + ] + }, + { + "text": "turn on thermostat please", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 8, + "endPos": 17 + } + ] + }, + { + "text": "turn the fan to high", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 9, + "endPos": 11 + } + ] + }, + { + "text": "turn thermostat on 70.", + "intent": "HomeAutomation", + "entities": [ + { + "entity": "Device", + "startPos": 5, + "endPos": 14 + } + ] + } + ], + "settings": [] +} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv b/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv new file mode 100644 index 000000000..5f42eaf85 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv @@ -0,0 +1,11 @@ +Question Answer Source Metadata +hi Hello! QnAMaker.tsv +greetings Hello! QnAMaker.tsv +good morning Hello! QnAMaker.tsv +good evening Hello! QnAMaker.tsv +What are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What do you do? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +Who are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What is your name? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv +What should I call you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/Weather.json b/samples/14.nlp-with-dispatch/src/main/resources/Weather.json new file mode 100644 index 000000000..c532ee35d --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/Weather.json @@ -0,0 +1,315 @@ +{ + "luis_schema_version": "3.2.0", + "name": "Weather", + "versionId": "0.1", + "desc": "Weather LUIS application - Bot Builder Samples", + "culture": "en-us", + "intents": [ + { + "name": "Get Weather Condition" + }, + { + "name": "Get Weather Forecast" + }, + { + "name": "None" + } + ], + "entities": [ + { + "name": "Location", + "roles": [] + } + ], + "closedLists": [], + "composites": [], + "patternAnyEntities": [ + { + "name": "Location_PatternAny", + "explicitList": [], + "roles": [] + } + ], + "regex_entities": [], + "prebuiltEntities": [], + "regex_features": [], + "model_features": [], + "patterns": [ + { + "pattern": "weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "how's the weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "current weather in {Location_PatternAny}", + "intent": "Get Weather Condition" + }, + { + "pattern": "what's the forecast for next week in {Location_PatternAny}", + "intent": "Get Weather Forecast" + }, + { + "pattern": "show me the forecast for {Location_PatternAny}", + "intent": "Get Weather Forecast" + }, + { + "pattern": "what's the forecast for {Location_PatternAny}", + "intent": "Get Weather Forecast" + } + ], + "utterances": [ + { + "text": "current weather ?", + "intent": "Get Weather Condition", + "entities": [] + }, + { + "text": "do florida residents usually need ice scrapers", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 3, + "endPos": 9, + "entity": "Location" + } + ] + }, + { + "text": "forecast in celcius", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "get florence temperature in september", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 4, + "endPos": 11, + "entity": "Location" + } + ] + }, + { + "text": "get for me the weather conditions in sonoma county", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 37, + "endPos": 49, + "entity": "Location" + } + ] + }, + { + "text": "get the daily temperature greenwood indiana", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 26, + "endPos": 42, + "entity": "Location" + } + ] + }, + { + "text": "get the forcast for me", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "get the weather at saint george utah", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 19, + "endPos": 35, + "entity": "Location" + } + ] + }, + { + "text": "how much rain does chambersburg get a year", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 19, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "i want to know the temperature at death valley", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 34, + "endPos": 45, + "entity": "Location" + } + ] + }, + { + "text": "provide me by toronto weather please", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 14, + "endPos": 20, + "entity": "Location" + } + ] + }, + { + "text": "show average rainfall for boise", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 26, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "show me the forecast at alabama", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 24, + "endPos": 30, + "entity": "Location" + } + ] + }, + { + "text": "soliciting today's weather", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "temperature of delhi in celsius please", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 15, + "endPos": 19, + "entity": "Location" + } + ] + }, + { + "text": "was last year about this time as wet as it is now in the south ?", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 57, + "endPos": 61, + "entity": "Location" + } + ] + }, + { + "text": "what is the rain volume in sonoma county ?", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 27, + "endPos": 39, + "entity": "Location" + } + ] + }, + { + "text": "what is the weather in redmond ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 23, + "endPos": 29, + "entity": "Location" + } + ] + }, + { + "text": "what is the weather today at 10 day durham ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 36, + "endPos": 41, + "entity": "Location" + } + ] + }, + { + "text": "what to wear in march in california", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 25, + "endPos": 34, + "entity": "Location" + } + ] + }, + { + "text": "what will the weather be tomorrow in new york ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 37, + "endPos": 44, + "entity": "Location" + } + ] + }, + { + "text": "what's the weather going to be like in hawaii ?", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 39, + "endPos": 44, + "entity": "Location" + } + ] + }, + { + "text": "what's the weather like in minneapolis", + "intent": "Get Weather Condition", + "entities": [ + { + "startPos": 27, + "endPos": 37, + "entity": "Location" + } + ] + }, + { + "text": "will it be raining in ranchi", + "intent": "Get Weather Forecast", + "entities": [ + { + "startPos": 22, + "endPos": 27, + "entity": "Location" + } + ] + }, + { + "text": "will it rain this weekend", + "intent": "Get Weather Forecast", + "entities": [] + }, + { + "text": "will it snow today", + "intent": "Get Weather Forecast", + "entities": [] + } + ] +} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/application.properties b/samples/14.nlp-with-dispatch/src/main/resources/application.properties new file mode 100644 index 000000000..5e1dbd85c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/application.properties @@ -0,0 +1,11 @@ +MicrosoftAppId= +MicrosoftAppPassword= +server.port=3978 + +QnAKnowledgebaseId= +QnAEndpointKey= +QnAEndpointHostName= + +LuisAppId = +LuisAPIKey= +LuisAPIHostName= diff --git a/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json b/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json new file mode 100644 index 000000000..67c0ad530 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json @@ -0,0 +1,18 @@ +{ + "configuration": { + "name": "Default", + "appenders": { + "Console": { + "name": "Console-Appender", + "target": "SYSTEM_OUT", + "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} + } + }, + "loggers": { + "root": { + "level": "debug", + "appender-ref": {"ref": "Console-Appender","level": "debug"} + } + } + } +} diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF b/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml b/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/index.html b/samples/14.nlp-with-dispatch/src/main/webapp/index.html new file mode 100644 index 000000000..d5ba5158e --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/main/webapp/index.html @@ -0,0 +1,418 @@ + + + + + + + EchoBot + + + + + +
+
+
+
Spring Boot Bot
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + + diff --git a/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java b/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java new file mode 100644 index 000000000..b2491036f --- /dev/null +++ b/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.nlpwithdispatch; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 85e9731bd2de777d496f94173b51d7b98063215e Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Wed, 7 Apr 2021 13:48:33 -0300 Subject: [PATCH 133/221] [Samples] Add 21.corebot-app-insights sample (#1138) * Add pom file * Add documentation * Add deploymentTemplates folder * Add cognitiveModels folder * Add webapp folder * Add card and application.properties * Add empty ApplicationTest * Add dialogs * Add AdapterWithErrorHandler * Add bots * Add models * Add startup file * Add package-info file * Bump applicationinsights-core dependency to 2.6.3 * Set includeInstanceData to true * Validate activities properties before adding into a Map * Refactor of the initialization of an ApplicationInsightsBotTelemetryClient to consume instrumentationKey * Populate ApplicationTest class * Fix formats --- .../bot/ai/luis/LuisRecognizerOptionsV3.java | 2 +- libraries/bot-applicationinsights/pom.xml | 2 +- ...ApplicationInsightsBotTelemetryClient.java | 25 +- .../BotTelemetryClientTests.java | 36 +- .../MyBotTelemetryClient.java | 5 +- .../builder/TelemetryLoggerMiddleware.java | 19 +- samples/21.corebot-app-insights/LICENSE | 21 + .../21.corebot-app-insights/README-LUIS.md | 216 +++++++++ samples/21.corebot-app-insights/README.md | 70 +++ .../cognitiveModels/flightbooking.json | 339 ++++++++++++++ .../template-with-new-rg.json | 291 ++++++++++++ .../template-with-preexisting-rg.json | 259 +++++++++++ samples/21.corebot-app-insights/pom.xml | 253 +++++++++++ .../app/insights/AdapterWithErrorHandler.java | 114 +++++ .../corebot/app/insights/Application.java | 118 +++++ .../corebot/app/insights/BookingDetails.java | 67 +++ .../corebot/app/insights/BookingDialog.java | 129 ++++++ .../app/insights/CancelAndHelpDialog.java | 80 ++++ .../app/insights/DateResolverDialog.java | 107 +++++ .../app/insights/DialogAndWelcomeBot.java | 98 ++++ .../corebot/app/insights/DialogBot.java | 127 ++++++ .../app/insights/FlightBookingRecognizer.java | 156 +++++++ .../corebot/app/insights/MainDialog.java | 238 ++++++++++ .../corebot/app/insights/package-info.java | 8 + .../src/main/resources/application.properties | 8 + .../src/main/resources/cards/welcomeCard.json | 46 ++ .../src/main/webapp/META-INF/MANIFEST.MF | 3 + .../src/main/webapp/WEB-INF/web.xml | 12 + .../src/main/webapp/index.html | 417 ++++++++++++++++++ .../corebot/app/insights/ApplicationTest.java | 19 + 30 files changed, 3252 insertions(+), 33 deletions(-) create mode 100644 samples/21.corebot-app-insights/LICENSE create mode 100644 samples/21.corebot-app-insights/README-LUIS.md create mode 100644 samples/21.corebot-app-insights/README.md create mode 100644 samples/21.corebot-app-insights/cognitiveModels/flightbooking.json create mode 100644 samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json create mode 100644 samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json create mode 100644 samples/21.corebot-app-insights/pom.xml create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java create mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java create mode 100644 samples/21.corebot-app-insights/src/main/resources/application.properties create mode 100644 samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json create mode 100644 samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF create mode 100644 samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml create mode 100644 samples/21.corebot-app-insights/src/main/webapp/index.html create mode 100644 samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java diff --git a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java index e22e5c251..a4a59d885 100644 --- a/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java +++ b/libraries/bot-ai-luis-v3/src/main/java/com/microsoft/bot/ai/luis/LuisRecognizerOptionsV3.java @@ -77,7 +77,7 @@ public class LuisRecognizerOptionsV3 extends LuisRecognizerOptions { /** * Value indicating whether or not instance data should be included in response. */ - private boolean includeInstanceData = false; + private boolean includeInstanceData = true; /** * Value indicating whether queries should be logged in LUIS. If queries should diff --git a/libraries/bot-applicationinsights/pom.xml b/libraries/bot-applicationinsights/pom.xml index 1dc45c1cf..649c864a2 100644 --- a/libraries/bot-applicationinsights/pom.xml +++ b/libraries/bot-applicationinsights/pom.xml @@ -57,7 +57,7 @@ com.microsoft.azure applicationinsights-core - 2.4.1 + 2.6.3 diff --git a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java index 6434a7754..92761e155 100644 --- a/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java +++ b/libraries/bot-applicationinsights/src/main/java/com/microsoft/bot/applicationinsights/ApplicationInsightsBotTelemetryClient.java @@ -4,6 +4,7 @@ package com.microsoft.bot.applicationinsights; import com.microsoft.applicationinsights.TelemetryClient; +import com.microsoft.applicationinsights.TelemetryConfiguration; import com.microsoft.applicationinsights.telemetry.EventTelemetry; import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry; import com.microsoft.applicationinsights.telemetry.PageViewTelemetry; @@ -12,6 +13,7 @@ import com.microsoft.applicationinsights.telemetry.TraceTelemetry; import com.microsoft.bot.builder.BotTelemetryClient; import com.microsoft.bot.builder.Severity; +import org.apache.commons.lang3.StringUtils; import java.time.Duration; import java.time.OffsetDateTime; @@ -26,17 +28,30 @@ public class ApplicationInsightsBotTelemetryClient implements BotTelemetryClient { private final TelemetryClient telemetryClient; + private final TelemetryConfiguration telemetryConfiguration; + + /** + * Provides access to the Application Insights configuration that is running here. + * Allows developers to adjust the options. + * @return Application insights configuration. + */ + public TelemetryConfiguration getTelemetryConfiguration() { + return telemetryConfiguration; + } /** * Initializes a new instance of the {@link BotTelemetryClient}. * - * @param withTelemetryClient The telemetry client to forward bot events to. + * @param instrumentationKey The instrumentation key provided to create + * the {@link ApplicationInsightsBotTelemetryClient}. */ - public ApplicationInsightsBotTelemetryClient(TelemetryClient withTelemetryClient) { - if (withTelemetryClient == null) { - throw new IllegalArgumentException("withTelemetry should be provided"); + public ApplicationInsightsBotTelemetryClient(String instrumentationKey) { + if (StringUtils.isBlank(instrumentationKey)) { + throw new IllegalArgumentException("instrumentationKey should be provided"); } - this.telemetryClient = withTelemetryClient; + this.telemetryConfiguration = TelemetryConfiguration.getActive(); + telemetryConfiguration.setInstrumentationKey(instrumentationKey); + this.telemetryClient = new TelemetryClient(telemetryConfiguration); } /** diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java index 855f139f9..a9a2fbb31 100644 --- a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/BotTelemetryClientTests.java @@ -3,8 +3,6 @@ package com.microsoft.bot.applicationinsights; -import com.microsoft.applicationinsights.TelemetryClient; -import com.microsoft.applicationinsights.TelemetryConfiguration; import com.microsoft.applicationinsights.channel.TelemetryChannel; import com.microsoft.applicationinsights.telemetry.EventTelemetry; import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry; @@ -26,19 +24,14 @@ public class BotTelemetryClientTests { - private BotTelemetryClient botTelemetryClient; + private ApplicationInsightsBotTelemetryClient botTelemetryClient; private TelemetryChannel mockTelemetryChannel; @Before public void initialize() { + botTelemetryClient = new ApplicationInsightsBotTelemetryClient("fakeKey"); mockTelemetryChannel = Mockito.mock(TelemetryChannel.class); - - TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration(); - telemetryConfiguration.setInstrumentationKey("UNITTEST-INSTRUMENTATION-KEY"); - telemetryConfiguration.setChannel(mockTelemetryChannel); - TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration); - - botTelemetryClient = new ApplicationInsightsBotTelemetryClient(telemetryClient); + botTelemetryClient.getTelemetryConfiguration().setChannel(mockTelemetryChannel); } @Test @@ -49,16 +42,20 @@ public void nullTelemetryClientThrows() { } @Test - public void nonNullTelemetryClientSucceeds() { - TelemetryClient telemetryClient = new TelemetryClient(); + public void emptyTelemetryClientThrows() { + Assert.assertThrows(IllegalArgumentException.class, () -> { + new ApplicationInsightsBotTelemetryClient(""); + }); + } - BotTelemetryClient botTelemetryClient = new ApplicationInsightsBotTelemetryClient(telemetryClient); + @Test + public void nonNullTelemetryClientSucceeds() { + BotTelemetryClient botTelemetryClient = new ApplicationInsightsBotTelemetryClient("fakeKey"); } @Test public void overrideTest() { - TelemetryClient telemetryClient = new TelemetryClient(); - MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient(telemetryClient); + MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient("fakeKey"); } @Test @@ -180,4 +177,13 @@ public void trackPageViewTest() { Assert.assertEquals(0, Double.compare(0.6, pageViewTelemetry.getMetrics().get("metric"))); }).send(Mockito.any(PageViewTelemetry.class)); } + + @Test + public void flushTest() { + botTelemetryClient.flush(); + + Mockito.verify(mockTelemetryChannel, invocations -> { + Assert.assertEquals(1, invocations.getAllInvocations().size()); + }).send(Mockito.any()); + } } diff --git a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java index 9ccf38495..acc48e9c8 100644 --- a/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java +++ b/libraries/bot-applicationinsights/src/test/java/com/microsoft/bot/applicationinsights/MyBotTelemetryClient.java @@ -3,7 +3,6 @@ package com.microsoft.bot.applicationinsights; -import com.microsoft.applicationinsights.TelemetryClient; import com.microsoft.bot.builder.Severity; import java.time.Duration; @@ -11,8 +10,8 @@ import java.util.Map; public class MyBotTelemetryClient extends ApplicationInsightsBotTelemetryClient { - public MyBotTelemetryClient(TelemetryClient telemetryClient) { - super(telemetryClient); + public MyBotTelemetryClient(String instrumentationKey) { + super(instrumentationKey); } @Override diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java index 9a6670856..850c69c56 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/TelemetryLoggerMiddleware.java @@ -195,14 +195,17 @@ protected CompletableFuture> fillReceiveEventProperties( ) { Map properties = new HashMap(); - properties.put(TelemetryConstants.FROMIDPROPERTY, activity.getFrom().getId()); - properties.put( - TelemetryConstants.CONVERSATIONNAMEPROPERTY, - activity.getConversation().getName() - ); - properties.put(TelemetryConstants.LOCALEPROPERTY, activity.getLocale()); - properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, activity.getRecipient().getId()); - properties.put(TelemetryConstants.RECIPIENTNAMEPROPERTY, activity.getRecipient().getName()); + String fromId = activity.getFrom().getId() != null ? activity.getFrom().getId() : ""; + properties.put(TelemetryConstants.FROMIDPROPERTY, fromId); + String conversationName = + activity.getConversation().getName() != null ? activity.getConversation().getName() : ""; + properties.put(TelemetryConstants.CONVERSATIONNAMEPROPERTY, conversationName); + String activityLocale = activity.getLocale() != null ? activity.getLocale() : ""; + properties.put(TelemetryConstants.LOCALEPROPERTY, activityLocale); + String recipientId = activity.getRecipient().getId() != null ? activity.getRecipient().getId() : ""; + properties.put(TelemetryConstants.RECIPIENTIDPROPERTY, recipientId); + String recipientName = activity.getRecipient().getName() != null ? activity.getRecipient().getName() : ""; + properties.put(TelemetryConstants.RECIPIENTNAMEPROPERTY, recipientName); // Use the LogPersonalInformation flag to toggle logging PII data, text and user // name are common examples diff --git a/samples/21.corebot-app-insights/LICENSE b/samples/21.corebot-app-insights/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/21.corebot-app-insights/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/21.corebot-app-insights/README-LUIS.md b/samples/21.corebot-app-insights/README-LUIS.md new file mode 100644 index 000000000..60a0b8383 --- /dev/null +++ b/samples/21.corebot-app-insights/README-LUIS.md @@ -0,0 +1,216 @@ +# Setting up LUIS via CLI: + +This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. + +> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ +> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ +> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ + +[Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app +[Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app + +## Table of Contents: + +- [Prerequisites](#Prerequisites) +- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) +- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) + +___ + +## [Prerequisites](#Table-of-Contents): + +#### Install Azure CLI >=2.0.61: + +Visit the following page to find the correct installer for your OS: +- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest + +#### Install LUIS CLI >=2.4.0: + +Open a CLI of your choice and type the following: + +```bash +npm i -g luis-apis@^2.4.0 +``` + +#### LUIS portal account: + +You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. + +After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. + +[LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] +[LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key + +___ + +## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) + +### 1. Import the local LUIS application to luis.ai + +```bash +luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" +``` + +Outputs the following JSON: + +```json +{ + "id": "########-####-####-####-############", + "name": "FlightBooking", + "description": "A LUIS model that uses intent and entities.", + "culture": "en-us", + "usageScenario": "", + "domain": "", + "versionsCount": 1, + "createdDateTime": "2019-03-29T18:32:02Z", + "endpoints": {}, + "endpointHitsCount": 0, + "activeVersion": "0.1", + "ownerEmail": "bot@contoso.com", + "tokenizerVersion": "1.0.0" +} +``` + +For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. + +### 2. Train the LUIS Application + +```bash +luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait +``` + +### 3. Publish the LUIS Application + +```bash +luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" +``` + +> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
+> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
+> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
+> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. + +[Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 + +Outputs the following: + +```json + { + "versionId": "0.1", + "isStaging": false, + "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", + "region": "westus", + "assignedEndpointKey": null, + "endpointRegion": "westus", + "failedRegions": "", + "publishedDateTime": "2019-03-29T18:40:32Z", + "directVersionPublish": false +} +``` + +To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. + +[README-LUIS]: ./README-LUIS.md + +___ + +## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) + +### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI + +> _Note:_
+> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ +> ```bash +> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" +> ``` +> _To see a list of valid locations, use `az account list-locations`_ + + +```bash +# Use Azure CLI to create the LUIS Key resource on Azure +az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +The command will output a response similar to the JSON below: + +```json +{ + "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", + "etag": "\"########-####-####-####-############\"", + "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", + "internalId": "################################", + "kind": "luis", + "location": "westus", + "name": "NewLuisResourceName", + "provisioningState": "Succeeded", + "resourceGroup": "ResourceGroupName", + "sku": { + "name": "S0", + "tier": null + }, + "tags": null, + "type": "Microsoft.CognitiveServices/accounts" +} +``` + + + +Take the output from the previous command and create a JSON file in the following format: + +```json +{ + "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", + "resourceGroup": "ResourceGroupName", + "accountName": "NewLuisResourceName" +} +``` + +### 2. Retrieve ARM access token via Azure CLI + +```bash +az account get-access-token --subscription "AzureSubscriptionGuid" +``` + +This will return an object that looks like this: + +```json +{ + "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", + "expiresOn": "2200-12-31 23:59:59.999999", + "subscription": "AzureSubscriptionGuid", + "tenant": "tenant-guid", + "tokenType": "Bearer" +} +``` + +The value needed for the next step is the `"accessToken"`. + +### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application + +```bash +luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" +``` + +If successful, it should yield a response like this: + +```json +{ + "code": "Success", + "message": "Operation Successful" +} +``` + +### 4. See the LUIS Cognitive Services' keys + +```bash +az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" +``` + +This will return an object that looks like this: + +```json +{ + "key1": "9a69####dc8f####8eb4####399f####", + "key2": "####f99e####4b1a####fb3b####6b9f" +} +``` diff --git a/samples/21.corebot-app-insights/README.md b/samples/21.corebot-app-insights/README.md new file mode 100644 index 000000000..3f93f6aeb --- /dev/null +++ b/samples/21.corebot-app-insights/README.md @@ -0,0 +1,70 @@ +# CoreBot with Application Insights + +Bot Framework v4 core bot sample. + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: + +- Use [LUIS](https://www.luis.ai) to implement core AI capabilities +- Implement a multi-turn conversation using Dialogs +- Handle user interruptions for such things as `Help` or `Cancel` +- Prompt for and validate requests for information from the user +- Use [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/cloudservices) to monitor your bot + +## Prerequisites + +This sample **requires** prerequisites in order to run. + +### Overview + +This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding +and [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/cloudservices), an extensible Application Performance Management (APM) service for web developers on multiple platforms. + +### Create a LUIS Application to enable language understanding + +LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). + +If you wish to create a LUIS application via the CLI, these steps can be found in the [README-LUIS.md](README-LUIS.md). + +### Add Application Insights service to enable the bot monitoring + +Application Insights resource creation steps can be found [here](https://docs.microsoft.com/azure/azure-monitor/app/create-new-resource). + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-corebot-app-insights-sample.jar` + +## Testing the bot using Bot Framework Emulator + +[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. + +- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) + +### Connect to the bot using Bot Framework Emulator + +- Launch Bot Framework Emulator +- File -> Open Bot +- Enter a Bot URL of `http://localhost:3978/api/messages` + +## Deploy the bot to Azure + +To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) +- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) +- [Spring Boot](https://spring.io/projects/spring-boot) +- [Application insights Overview](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) +- [Getting Started with Application Insights](https://github.com/Microsoft/ApplicationInsights-aspnetcore/wiki/Getting-Started-with-Application-Insights-for-ASP.NET-Core) +- [Filtering and preprocessing telemetry in the Application Insights SDK](https://docs.microsoft.com/azure/azure-monitor/app/api-filtering-sampling) diff --git a/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json b/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json new file mode 100644 index 000000000..89aad31ae --- /dev/null +++ b/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json @@ -0,0 +1,339 @@ +{ + "luis_schema_version": "3.2.0", + "versionId": "0.1", + "name": "FlightBooking", + "desc": "Luis Model for CoreBot", + "culture": "en-us", + "tokenizerVersion": "1.0.0", + "intents": [ + { + "name": "BookFlight" + }, + { + "name": "Cancel" + }, + { + "name": "GetWeather" + }, + { + "name": "None" + } + ], + "entities": [], + "composites": [ + { + "name": "From", + "children": [ + "Airport" + ], + "roles": [] + }, + { + "name": "To", + "children": [ + "Airport" + ], + "roles": [] + } + ], + "closedLists": [ + { + "name": "Airport", + "subLists": [ + { + "canonicalForm": "Paris", + "list": [ + "paris", + "cdg" + ] + }, + { + "canonicalForm": "London", + "list": [ + "london", + "lhr" + ] + }, + { + "canonicalForm": "Berlin", + "list": [ + "berlin", + "txl" + ] + }, + { + "canonicalForm": "New York", + "list": [ + "new york", + "jfk" + ] + }, + { + "canonicalForm": "Seattle", + "list": [ + "seattle", + "sea" + ] + } + ], + "roles": [] + } + ], + "patternAnyEntities": [], + "regex_entities": [], + "prebuiltEntities": [ + { + "name": "datetimeV2", + "roles": [] + } + ], + "model_features": [], + "regex_features": [], + "patterns": [], + "utterances": [ + { + "text": "book a flight", + "intent": "BookFlight", + "entities": [] + }, + { + "text": "book a flight from new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 26 + } + ] + }, + { + "text": "book a flight from seattle", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 19, + "endPos": 25 + } + ] + }, + { + "text": "book a hotel in new york", + "intent": "None", + "entities": [] + }, + { + "text": "book a restaurant", + "intent": "None", + "entities": [] + }, + { + "text": "book flight from london to paris on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 17, + "endPos": 22 + }, + { + "entity": "To", + "startPos": 27, + "endPos": 31 + } + ] + }, + { + "text": "book flight to berlin on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 15, + "endPos": 20 + } + ] + }, + { + "text": "book me a flight from london to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 22, + "endPos": 27 + }, + { + "entity": "To", + "startPos": 32, + "endPos": 36 + } + ] + }, + { + "text": "bye", + "intent": "Cancel", + "entities": [] + }, + { + "text": "cancel booking", + "intent": "Cancel", + "entities": [] + }, + { + "text": "exit", + "intent": "Cancel", + "entities": [] + }, + { + "text": "find an airport near me", + "intent": "None", + "entities": [] + }, + { + "text": "flight to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "flight to paris from london on feb 14th", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + }, + { + "entity": "From", + "startPos": 21, + "endPos": 26 + } + ] + }, + { + "text": "fly from berlin to paris on may 5th", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 9, + "endPos": 14 + }, + { + "entity": "To", + "startPos": 19, + "endPos": 23 + } + ] + }, + { + "text": "go to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 6, + "endPos": 10 + } + ] + }, + { + "text": "going from paris to berlin", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 11, + "endPos": 15 + }, + { + "entity": "To", + "startPos": 20, + "endPos": 25 + } + ] + }, + { + "text": "i'd like to rent a car", + "intent": "None", + "entities": [] + }, + { + "text": "ignore", + "intent": "Cancel", + "entities": [] + }, + { + "text": "travel from new york to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "From", + "startPos": 12, + "endPos": 19 + }, + { + "entity": "To", + "startPos": 24, + "endPos": 28 + } + ] + }, + { + "text": "travel to new york", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 17 + } + ] + }, + { + "text": "travel to paris", + "intent": "BookFlight", + "entities": [ + { + "entity": "To", + "startPos": 10, + "endPos": 14 + } + ] + }, + { + "text": "what's the forecast for this friday?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like for tomorrow", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like in new york", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "what's the weather like?", + "intent": "GetWeather", + "entities": [] + }, + { + "text": "winter is coming", + "intent": "None", + "entities": [] + } + ], + "settings": [] +} diff --git a/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json b/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json new file mode 100644 index 000000000..ec2460d3a --- /dev/null +++ b/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json @@ -0,0 +1,291 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "groupLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Specifies the location of the Resource Group." + } + }, + "groupName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the Resource Group." + } + }, + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The name of the App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "newAppServicePlanLocation": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "The location of the App Service Plan. Defaults to \"westus\"." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "appServicePlanName": "[parameters('newAppServicePlanName')]", + "resourcesLocation": "[parameters('newAppServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" + }, + "resources": [ + { + "name": "[parameters('groupName')]", + "type": "Microsoft.Resources/resourceGroups", + "apiVersion": "2018-05-01", + "location": "[parameters('groupLocation')]", + "properties": {} + }, + { + "type": "Microsoft.Resources/deployments", + "apiVersion": "2018-05-01", + "name": "storageDeployment", + "resourceGroup": "[parameters('groupName')]", + "dependsOn": [ + "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" + ], + "properties": { + "mode": "Incremental", + "template": { + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": {}, + "variables": {}, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "name": "[variables('appServicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[variables('appServicePlanName')]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ], + "outputs": {} + } + } + } + ] +} diff --git a/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json b/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json new file mode 100644 index 000000000..024dcf08d --- /dev/null +++ b/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json @@ -0,0 +1,259 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "appId": { + "type": "string", + "metadata": { + "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." + } + }, + "appSecret": { + "type": "string", + "metadata": { + "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." + } + }, + "botId": { + "type": "string", + "metadata": { + "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." + } + }, + "botSku": { + "defaultValue": "S1", + "type": "string", + "metadata": { + "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." + } + }, + "newAppServicePlanName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The name of the new App Service Plan." + } + }, + "newAppServicePlanSku": { + "type": "object", + "defaultValue": { + "name": "P1v2", + "tier": "PremiumV2", + "size": "P1v2", + "family": "Pv2", + "capacity": 1 + }, + "metadata": { + "description": "The SKU of the App Service Plan. Defaults to Standard values." + } + }, + "appServicePlanLocation": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The location of the App Service Plan." + } + }, + "existingAppServicePlan": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "Name of the existing App Service Plan used to create the Web App for the bot." + } + }, + "newWebAppName": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." + } + } + }, + "variables": { + "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", + "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", + "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", + "publishingUsername": "[concat('$', parameters('newWebAppName'))]", + "resourcesLocation": "[parameters('appServicePlanLocation')]", + "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", + "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", + "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" + }, + "resources": [ + { + "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", + "type": "Microsoft.Web/serverfarms", + "condition": "[not(variables('useExistingAppServicePlan'))]", + "name": "[variables('servicePlanName')]", + "apiVersion": "2018-02-01", + "location": "[variables('resourcesLocation')]", + "sku": "[parameters('newAppServicePlanSku')]", + "kind": "linux", + "properties": { + "perSiteScaling": false, + "maximumElasticWorkerCount": 1, + "isSpot": false, + "reserved": true, + "isXenon": false, + "hyperV": false, + "targetWorkerCount": 0, + "targetWorkerSizeId": 0 + } + }, + { + "comments": "Create a Web App using a Linux App Service Plan", + "type": "Microsoft.Web/sites", + "apiVersion": "2018-11-01", + "location": "[variables('resourcesLocation')]", + "kind": "app,linux", + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" + ], + "name": "[variables('webAppName')]", + "properties": { + "name": "[variables('webAppName')]", + "hostNameSslStates": [ + { + "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Standard" + }, + { + "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", + "sslState": "Disabled", + "hostType": "Repository" + } + ], + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", + "reserved": true, + "isXenon": false, + "hyperV": false, + "scmSiteAlsoStopped": false, + "clientAffinityEnabled": true, + "clientCertEnabled": false, + "hostNamesDisabled": false, + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "httpsOnly": false, + "redundancyMode": "None", + "siteConfig": { + "appSettings": [ + { + "name": "JAVA_OPTS", + "value": "-Dserver.port=80" + }, + { + "name": "MicrosoftAppId", + "value": "[parameters('appId')]" + }, + { + "name": "MicrosoftAppPassword", + "value": "[parameters('appSecret')]" + } + ], + "cors": { + "allowedOrigins": [ + "https://botservice.hosting.portal.azure.net", + "https://hosting.onecloud.azure-test.net/" + ] + } + } + } + }, + { + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "name": "[concat(variables('webAppName'), '/web')]", + "location": "[variables('resourcesLocation')]", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ], + "properties": { + "numberOfWorkers": 1, + "defaultDocuments": [ + "Default.htm", + "Default.html", + "Default.asp", + "index.htm", + "index.html", + "iisstart.htm", + "default.aspx", + "index.php", + "hostingstart.html" + ], + "netFrameworkVersion": "v4.0", + "linuxFxVersion": "JAVA|8-jre8", + "requestTracingEnabled": false, + "remoteDebuggingEnabled": false, + "httpLoggingEnabled": false, + "logsDirectorySizeLimit": 35, + "detailedErrorLoggingEnabled": false, + "publishingUsername": "[variables('publishingUsername')]", + "scmType": "None", + "use32BitWorkerProcess": true, + "webSocketsEnabled": false, + "alwaysOn": true, + "managedPipelineMode": "Integrated", + "virtualApplications": [ + { + "virtualPath": "/", + "physicalPath": "site\\wwwroot", + "preloadEnabled": true + } + ], + "loadBalancing": "LeastRequests", + "experiments": { + "rampUpRules": [] + }, + "autoHealEnabled": false, + "localMySqlEnabled": false, + "ipSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictions": [ + { + "ipAddress": "Any", + "action": "Allow", + "priority": 1, + "name": "Allow all", + "description": "Allow all access" + } + ], + "scmIpSecurityRestrictionsUseMain": false, + "http20Enabled": false, + "minTlsVersion": "1.2", + "ftpsState": "AllAllowed", + "reservedInstanceCount": 0 + } + }, + { + "apiVersion": "2017-12-01", + "type": "Microsoft.BotService/botServices", + "name": "[parameters('botId')]", + "location": "global", + "kind": "bot", + "sku": { + "name": "[parameters('botSku')]" + }, + "properties": { + "name": "[parameters('botId')]", + "displayName": "[parameters('botId')]", + "endpoint": "[variables('botEndpoint')]", + "msaAppId": "[parameters('appId')]", + "developerAppInsightsApplicationId": null, + "developerAppInsightKey": null, + "publishingCredentials": null, + "storageResourceId": null + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" + ] + } + ] +} diff --git a/samples/21.corebot-app-insights/pom.xml b/samples/21.corebot-app-insights/pom.xml new file mode 100644 index 000000000..ea30a54e4 --- /dev/null +++ b/samples/21.corebot-app-insights/pom.xml @@ -0,0 +1,253 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-corebot-app-insights + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Core Bot sample with Application Insights using Spring Boot. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.corebot.app.insights.Application + + + + + org.springframework.boot + spring-boot-starter-test + 2.4.0 + test + + + junit + junit + 4.13.1 + test + + + org.junit.vintage + junit-vintage-engine + test + + + + org.slf4j + slf4j-api + + + org.apache.logging.log4j + log4j-api + 2.11.0 + + + org.apache.logging.log4j + log4j-core + 2.13.2 + + + + com.microsoft.bot + bot-integration-spring + 4.6.0-preview9 + compile + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + + + com.microsoft.bot + bot-ai-luis-v3 + 4.6.0-preview9 + + + com.microsoft.bot + bot-applicationinsights + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.corebot.app.insights.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java new file mode 100644 index 000000000..fb82e8328 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java @@ -0,0 +1,114 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.connector.Channels; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.slf4j.LoggerFactory; + +import javax.annotation.Nullable; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * An Adapter that provides exception handling. + */ +public class AdapterWithErrorHandler extends BotFrameworkHttpAdapter { + + private static final String ERROR_MSG_ONE = "The bot encountered an error or bug."; + private static final String ERROR_MSG_TWO = + "To continue to run this bot, please fix the bot source code."; + // Create field for telemetry client. Add IBotTelemetryClient parameter to AdapterWithErrorHandler + private BotTelemetryClient adapterBotTelemetryClient; + + /** + * Constructs an error handling BotFrameworkHttpAdapter by providing an + * {@link com.microsoft.bot.builder.OnTurnErrorHandler}. + * + *

+ * For this sample, a simple message is displayed. For a production Bot, a more + * informative message or action is likely preferred. + *

+ * + * @param withConfiguration The Configuration object to use. + * @param telemetryInitializerMiddleware The TelemetryInitializerMiddleware object to use. + * @param botTelemetryClient The BotTelemetryClient object to use. + * @param withConversationState The ConversationState object to use. + */ + public AdapterWithErrorHandler( + Configuration withConfiguration, + TelemetryInitializerMiddleware telemetryInitializerMiddleware, + BotTelemetryClient botTelemetryClient, + @Nullable ConversationState withConversationState) { + super(withConfiguration); + this.use(telemetryInitializerMiddleware); + + // Use telemetry client so that we can trace exceptions into Application Insights + this.adapterBotTelemetryClient = botTelemetryClient; + setOnTurnError((turnContext, exception) -> { + // Track exceptions into Application Insights + // Set up some properties for our exception tracing to give more information + Map properties = new HashMap(); + properties.put("Bot exception caught in", "AdapterWithErrorHandler - OnTurnError"); + + // Send the exception telemetry: + adapterBotTelemetryClient.trackException((Exception) exception, properties, null); + + // Log any leaked exception from the application. + // NOTE: In production environment, you should consider logging this to + // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how + // to add telemetry capture to your bot. + LoggerFactory.getLogger(AdapterWithErrorHandler.class).error("onTurnError", exception); + + // Send a message to the user + return turnContext.sendActivities( + MessageFactory.text(ERROR_MSG_ONE), MessageFactory.text(ERROR_MSG_TWO) + ).thenCompose(resourceResponse -> sendTraceActivity(turnContext, exception)) + .thenCompose(stageResult -> { + if (withConversationState != null) { + // Delete the conversationState for the current conversation to prevent the + // bot from getting stuck in a error-loop caused by being in a bad state. + // ConversationState should be thought of as similar to "cookie-state" in a + // Web pages. + return withConversationState.delete(turnContext) + .exceptionally(deleteException -> { + LoggerFactory.getLogger(AdapterWithErrorHandler.class) + .error("ConversationState.delete", deleteException); + return null; + }); + } + return CompletableFuture.completedFuture(null); + }); + }); + } + + private CompletableFuture sendTraceActivity( + TurnContext turnContext, + Throwable exception + ) { + if (StringUtils.equals(turnContext.getActivity().getChannelId(), Channels.EMULATOR)) { + Activity traceActivity = new Activity(ActivityTypes.TRACE); + traceActivity.setLabel("TurnError"); + traceActivity.setName("OnTurnError Trace"); + traceActivity.setValue(ExceptionUtils.getStackTrace(exception)); + traceActivity.setValueType("https://www.botframework.com/schemas/error"); + + // Send a trace activity, which will be displayed in the Bot Framework Emulator + return turnContext.sendActivity(traceActivity).thenApply(resourceResponse -> null); + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java new file mode 100644 index 000000000..6cc9570e7 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.applicationinsights.ApplicationInsightsBotTelemetryClient; +import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware; +import com.microsoft.bot.builder.Bot; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.NullBotTelemetryClient; +import com.microsoft.bot.builder.Storage; +import com.microsoft.bot.builder.TelemetryLoggerMiddleware; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.integration.BotFrameworkHttpAdapter; +import com.microsoft.bot.integration.Configuration; +import com.microsoft.bot.integration.spring.BotController; +import com.microsoft.bot.integration.spring.BotDependencyConfiguration; +import org.apache.commons.lang3.StringUtils; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Import; + +/** + * This is the starting point of the Sprint Boot Bot application. + */ +@SpringBootApplication + +// Use the default BotController to receive incoming Channel messages. A custom +// controller could be used by eliminating this import and creating a new +// org.springframework.web.bind.annotation.RestController. +// The default controller is created by the Spring Boot container using +// dependency injection. The default route is /api/messages. +@Import({BotController.class}) + +/** + * This class extends the BotDependencyConfiguration which provides the default + * implementations for a Bot application. The Application class should + * override methods in order to provide custom implementations. + */ +public class Application extends BotDependencyConfiguration { + + /** + * The start method. + * + * @param args The args. + */ + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } + + /** + * Returns the Bot for this application. + * + *

+ * The @Component annotation could be used on the Bot class instead of this method with the + * @Bean annotation. + *

+ * + * @param configuration The Configuration object to use. + * @param userState The UserState object to use. + * @param conversationState The ConversationState object to use. + * @return The Bot implementation for this application. + */ + @Bean + public Bot getBot( + Configuration configuration, + UserState userState, + ConversationState conversationState + ) { + BotTelemetryClient botTelemetryClient = getBotTelemetryClient(configuration); + FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration, botTelemetryClient); + MainDialog dialog = new MainDialog(recognizer, new BookingDialog(), botTelemetryClient); + + return new DialogAndWelcomeBot<>(conversationState, userState, dialog); + } + + /** + * Returns a custom Adapter that provides error handling. + * + * @param configuration The Configuration object to use. + * @return An error handling BotFrameworkHttpAdapter. + */ + @Override + public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { + Storage storage = getStorage(); + ConversationState conversationState = getConversationState(storage); + BotTelemetryClient botTelemetryClient = getBotTelemetryClient(configuration); + TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(botTelemetryClient, false); + TelemetryInitializerMiddleware telemetryInitializerMiddleware = + new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, false); + + AdapterWithErrorHandler adapter = new AdapterWithErrorHandler( + configuration, + telemetryInitializerMiddleware, + botTelemetryClient, + conversationState); + + return adapter; + } + + /** + * Returns a Bot Telemetry Client. + * + * @param configuration The Configuration object to use. + * @return A Bot Telemetry Client. + */ + @Bean + public BotTelemetryClient getBotTelemetryClient(Configuration configuration) { + String instrumentationKey = configuration.getProperty("ApplicationInsights.InstrumentationKey"); + if (StringUtils.isNotBlank(instrumentationKey)) { + return new ApplicationInsightsBotTelemetryClient(instrumentationKey); + } + + return new NullBotTelemetryClient(); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java new file mode 100644 index 000000000..a4bd9b202 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +/** + * The model class to retrieve the information of the booking. + */ +public class BookingDetails { + private String destination; + private String origin; + private String travelDate; + + /** + * Gets the destination of the booking. + * + * @return The destination. + */ + public String getDestination() { + return destination; + } + + /** + * Sets the destination of the booking. + * + * @param withDestination The new destination. + */ + public void setDestination(String withDestination) { + destination = withDestination; + } + + /** + * Gets the origin of the booking. + * + * @return The origin. + */ + public String getOrigin() { + return origin; + } + + /** + * Sets the origin of the booking. + * + * @param withOrigin The new origin. + */ + public void setOrigin(String withOrigin) { + origin = withOrigin; + } + + /** + * Gets the travel date of the booking. + * + * @return The travel date. + */ + public String getTravelDate() { + return travelDate; + } + + /** + * Sets the travel date of the booking. + * + * @param withTravelDate The new travel date. + */ + public void setTravelDate(String withTravelDate) { + travelDate = withTravelDate; + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java new file mode 100644 index 000000000..92dc7e1db --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java @@ -0,0 +1,129 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.util.Arrays; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the booking dialogs. + */ +public class BookingDialog extends CancelAndHelpDialog { + private final String destinationStepMsgText = "Where would you like to travel to?"; + private final String originStepMsgText = "Where are you traveling from?"; + + /** + * The constructor of the Booking Dialog class. + */ + public BookingDialog() { + super("BookingDialog"); + + addDialog(new TextPrompt("TextPrompt")); + addDialog(new ConfirmPrompt("ConfirmPrompt")); + addDialog(new DateResolverDialog(null)); + WaterfallStep[] waterfallSteps = { + this::destinationStep, + this::originStep, + this::travelDateStep, + this::confirmStep, + this::finalStep + }; + + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture destinationStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + if (bookingDetails.getDestination().isEmpty()) { + Activity promptMessage = + MessageFactory.text(destinationStepMsgText, destinationStepMsgText, + InputHints.EXPECTING_INPUT + ); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getDestination()); + } + + private CompletableFuture originStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setDestination((String) stepContext.getResult()); + + if (bookingDetails.getOrigin().isEmpty()) { + Activity promptMessage = + MessageFactory + .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + return stepContext.next(bookingDetails.getOrigin()); + } + + private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setOrigin((String) stepContext.getResult()); + + if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { + return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); + } + + return stepContext.next(bookingDetails.getTravelDate()); + } + + private CompletableFuture confirmStep(WaterfallStepContext stepContext) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + + bookingDetails.setTravelDate((String) stepContext.getResult()); + + String messageText = + String.format( + "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", + bookingDetails.getDestination(), bookingDetails.getOrigin(), + bookingDetails.getTravelDate() + ); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); + + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + + return stepContext.prompt("ConfirmPrompt", promptOptions); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + if ((Boolean) stepContext.getResult()) { + BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); + return stepContext.endDialog(bookingDetails); + } + + return stepContext.endDialog(null); + } + + private static boolean isAmbiguous(String timex) { + TimexProperty timexProperty = new TimexProperty(timex); + return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java new file mode 100644 index 000000000..db4c1675b --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java @@ -0,0 +1,80 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogContext; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.DialogTurnStatus; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.InputHints; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of the dialog interruptions. + */ +public class CancelAndHelpDialog extends ComponentDialog { + + private final String helpMsgText = "Show help here"; + private final String cancelMsgText = "Cancelling..."; + + /** + * The constructor of the CancelAndHelpDialog class. + * + * @param id The dialog's Id. + */ + public CancelAndHelpDialog(String id) { + super(id); + } + + /** + * Called when the dialog is _continued_, where it is the active dialog and the user replies + * with a new activity. + * + * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. + * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is + * successful, the result indicates whether the dialog is still active after the turn has been + * processed by the dialog. The result may also contain a return value. + */ + @Override + protected CompletableFuture onContinueDialog(DialogContext innerDc) { + return interrupt(innerDc).thenCompose(result -> { + if (result != null) { + return CompletableFuture.completedFuture(result); + } + return super.onContinueDialog(innerDc); + }); + } + + private CompletableFuture interrupt(DialogContext innerDc) { + if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { + String text = innerDc.getContext().getActivity().getText().toLowerCase(); + + switch (text) { + case "help": + case "?": + Activity helpMessage = MessageFactory + .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); + return innerDc.getContext().sendActivity(helpMessage) + .thenCompose(sendResult -> + CompletableFuture + .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); + case "cancel": + case "quit": + Activity cancelMessage = MessageFactory + .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); + return innerDc.getContext() + .sendActivity(cancelMessage) + .thenCompose(sendResult -> innerDc.cancelAllDialogs()); + default: + break; + } + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java new file mode 100644 index 000000000..cde5d96a0 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java @@ -0,0 +1,107 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.DateTimePrompt; +import com.microsoft.bot.dialogs.prompts.DateTimeResolution; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.Constants; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import javax.annotation.Nullable; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the date resolver dialogs. + */ +public class DateResolverDialog extends CancelAndHelpDialog { + private final String promptMsgText = "When would you like to travel?"; + private final String repromptMsgText = + "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; + + /** + * The constructor of the DateResolverDialog class. + * @param id The dialog's id. + */ + public DateResolverDialog(@Nullable String id) { + super(id != null ? id : "DateResolverDialog"); + + addDialog(new DateTimePrompt("DateTimePrompt", + DateResolverDialog::dateTimePromptValidator, null)); + WaterfallStep[] waterfallSteps = { + this::initialStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + private CompletableFuture initialStep(WaterfallStepContext stepContext) { + String timex = (String) stepContext.getOptions(); + + Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); + Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); + + if (timex == null) { + // We were not given any date at all so prompt the user. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + promptOptions.setRetryPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + // We have a Date we just need to check it is unambiguous. + TimexProperty timexProperty = new TimexProperty(timex); + if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { + // This is essentially a "reprompt" of the data we were given up front. + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(repromptMessage); + return stepContext.prompt("DateTimePrompt", promptOptions); + } + + DateTimeResolution dateTimeResolution = new DateTimeResolution(); + dateTimeResolution.setTimex(timex); + List dateTimeResolutions = new ArrayList(); + dateTimeResolutions.add(dateTimeResolution); + return stepContext.next(dateTimeResolutions); + } + + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); + return stepContext.endDialog(timex); + } + + private static CompletableFuture dateTimePromptValidator( + PromptValidatorContext> promptContext + ) { + if (promptContext.getRecognized().getSucceeded()) { + // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the + // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. + // e.g. missing a Year. + String timex = ((List) promptContext.getRecognized().getValue()) + .get(0).getTimex().split("T")[0]; + + // If this is a definite Date including year, month and day we are good otherwise reprompt. + // A better solution might be to let the user know what part is actually missing. + Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); + + return CompletableFuture.completedFuture(isDefinite); + } + + return CompletableFuture.completedFuture(false); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java new file mode 100644 index 000000000..fd3bf4ce4 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java @@ -0,0 +1,98 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.codepoetics.protonpack.collectors.CompletableFutures; +import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.Attachment; +import com.microsoft.bot.schema.ChannelAccount; +import com.microsoft.bot.schema.Serialization; +import org.apache.commons.lang3.StringUtils; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * The class containing the welcome dialog. + * + * @param is a Dialog. + */ +public class DialogAndWelcomeBot extends DialogBot { + + /** + * Creates a DialogBot. + * + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogAndWelcomeBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { + super(withConversationState, withUserState, withDialog); + } + + /** + * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation + * update activity that indicates one or more users other than the bot are joining the + * conversation, it calls this method. + * + * @param membersAdded A list of all the members added to the conversation, as described by the + * conversation update activity + * @param turnContext The context object for this turn. + * @return A task that represents the work queued to execute. + */ + @Override + protected CompletableFuture onMembersAdded( + List membersAdded, TurnContext turnContext + ) { + return turnContext.getActivity().getMembersAdded().stream() + .filter(member -> !StringUtils + .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) + .map(channel -> { + // Greet anyone that was not the target (recipient) of this message. + // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. + Attachment welcomeCard = createAdaptiveCardAttachment(); + Activity response = MessageFactory + .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); + + return turnContext.sendActivity(response).thenApply(sendResult -> { + return Dialog.run(getDialog(), turnContext, + getConversationState().createProperty("DialogState") + ); + }); + }) + .collect(CompletableFutures.toFutureList()) + .thenApply(resourceResponse -> null); + } + + // Load attachment from embedded resource. + private Attachment createAdaptiveCardAttachment() { + try ( + InputStream inputStream = Thread.currentThread(). + getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") + ) { + String adaptiveCardJson = IOUtils + .toString(inputStream, StandardCharsets.UTF_8.toString()); + + Attachment attachment = new Attachment(); + attachment.setContentType("application/vnd.microsoft.card.adaptive"); + attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); + return attachment; + + } catch (IOException e) { + e.printStackTrace(); + return new Attachment(); + } + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java new file mode 100644 index 000000000..5fe2be5d0 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.builder.ActivityHandler; +import com.microsoft.bot.builder.BotState; +import com.microsoft.bot.builder.ConversationState; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.builder.UserState; +import com.microsoft.bot.dialogs.Dialog; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.CompletableFuture; + +/** + * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow + * multiple different bots to be run at different endpoints within the same project. This can be + * achieved by defining distinct Controller types each with dependency on distinct Bot types. The + * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been + * used in a Dialog implementation, and the requirement is that all BotState objects are saved at + * the end of a turn. + * + * @param parameter of a type inheriting from Dialog + */ +public class DialogBot extends ActivityHandler { + + private Dialog dialog; + private BotState conversationState; + private BotState userState; + + /** + * Gets the dialog in use. + * + * @return instance of dialog + */ + protected Dialog getDialog() { + return dialog; + } + + /** + * Gets the conversation state. + * + * @return instance of conversationState + */ + protected BotState getConversationState() { + return conversationState; + } + + /** + * Gets the user state. + * + * @return instance of userState + */ + protected BotState getUserState() { + return userState; + } + + /** + * Sets the dialog in use. + * + * @param withDialog the dialog (of Dialog type) to be set + */ + protected void setDialog(Dialog withDialog) { + dialog = withDialog; + } + + /** + * Sets the conversation state. + * + * @param withConversationState the conversationState (of BotState type) to be set + */ + protected void setConversationState(BotState withConversationState) { + conversationState = withConversationState; + } + + /** + * Sets the user state. + * + * @param withUserState the userState (of BotState type) to be set + */ + protected void setUserState(BotState withUserState) { + userState = withUserState; + } + + /** + * Creates a DialogBot. + * + * @param withConversationState ConversationState to use in the bot + * @param withUserState UserState to use + * @param withDialog Param inheriting from Dialog class + */ + public DialogBot( + ConversationState withConversationState, UserState withUserState, T withDialog + ) { + this.conversationState = withConversationState; + this.userState = withUserState; + this.dialog = withDialog; + } + + /** + * Saves the BotState objects at the end of each turn. + * + * @param turnContext + * @return + */ + @Override + public CompletableFuture onTurn(TurnContext turnContext) { + return super.onTurn(turnContext) + .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) + .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); + } + + /** + * This method is executed when the turnContext receives a message activity. + * + * @param turnContext + * @return + */ + @Override + protected CompletableFuture onMessageActivity(TurnContext turnContext) { + LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); + + // Run the Dialog with the new message Activity. + return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java new file mode 100644 index 000000000..0693ab236 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java @@ -0,0 +1,156 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; +import com.microsoft.bot.ai.luis.LuisApplication; +import com.microsoft.bot.ai.luis.LuisRecognizer; +import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; +import com.microsoft.bot.builder.BotTelemetryClient; +import com.microsoft.bot.builder.Recognizer; +import com.microsoft.bot.builder.RecognizerResult; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.integration.Configuration; + +import java.util.concurrent.CompletableFuture; + +/** + * The class in charge of recognizing the booking information. + */ +public class FlightBookingRecognizer implements Recognizer { + private LuisRecognizer recognizer = null; + + /** + * The constructor of the FlightBookingRecognizer class. + * + * @param configuration The Configuration object to use. + * @param telemetryClient The BotTelemetryClient to use. + */ + public FlightBookingRecognizer(Configuration configuration, BotTelemetryClient telemetryClient) { + Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) + && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); + + if (luisIsConfigured) { + LuisApplication luisApplication = new LuisApplication( + configuration.getProperty("LuisAppId"), + configuration.getProperty("LuisAPIKey"), + "https://".concat(configuration.getProperty("LuisAPIHostName")) + ); + + // Set the recognizer options depending on which endpoint version you want to use. + // More details can be found in + // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 + LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); + recognizerOptions.setTelemetryClient(telemetryClient); + + recognizer = new LuisRecognizer(recognizerOptions); + } + } + + /** + * Verify if the recognizer is configured. + * + * @return True if it's configured, False if it's not. + */ + public Boolean isConfigured() { + return this.recognizer != null; + } + + /** + * Return an object with preformatted LUIS results for the bot's dialogs to consume. + * + * @param context A {link TurnContext} + * @return A {link RecognizerResult} + */ + public CompletableFuture executeLuisQuery(TurnContext context) { + // Returns true if luis is configured in the application.properties and initialized. + return this.recognizer.recognize(context); + } + + /** + * Gets the From data from the entities which is part of the result. + * + * @param result The recognizer result. + * @return The object node representing the From data. + */ + public ObjectNode getFromEntities(RecognizerResult result) { + String fromValue = "", fromAirportValue = ""; + if (result.getEntities().get("$instance").get("From") != null) { + fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") + .asText(); + } + if (!fromValue.isEmpty() + && result.getEntities().get("From").get(0).get("Airport") != null) { + fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) + .asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("from", fromValue); + entitiesNode.put("airport", fromAirportValue); + return entitiesNode; + } + + /** + * Gets the To data from the entities which is part of the result. + * + * @param result The recognizer result. + * @return The object node representing the To data. + */ + public ObjectNode getToEntities(RecognizerResult result) { + String toValue = "", toAirportValue = ""; + if (result.getEntities().get("$instance").get("To") != null) { + toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); + } + if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { + toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) + .asText(); + } + + ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); + ObjectNode entitiesNode = mapper.createObjectNode(); + entitiesNode.put("to", toValue); + entitiesNode.put("airport", toAirportValue); + return entitiesNode; + } + + /** + * This value will be a TIMEX. And we are only interested in a Date so grab the first result and + * drop the Time part. TIMEX is a format that represents DateTime expressions that include some + * ambiguity. e.g. missing a Year. + * + * @param result A {link RecognizerResult} + * @return The Timex value without the Time model + */ + public String getTravelDate(RecognizerResult result) { + JsonNode datetimeEntity = result.getEntities().get("datetime"); + if (datetimeEntity == null || datetimeEntity.get(0) == null) { + return null; + } + + JsonNode timex = datetimeEntity.get(0).get("timex"); + if (timex == null || timex.get(0) == null) { + return null; + } + + String datetime = timex.get(0).asText().split("T")[0]; + return datetime; + } + + /** + * Runs an utterance through a recognizer and returns a generic recognizer result. + * + * @param turnContext Turn context. + * @return Analysis of utterance. + */ + @Override + public CompletableFuture recognize(TurnContext turnContext) { + return this.recognizer.recognize(turnContext); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java new file mode 100644 index 000000000..841de553c --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java @@ -0,0 +1,238 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import com.microsoft.bot.builder.MessageFactory; +import com.microsoft.bot.builder.TurnContext; +import com.microsoft.bot.dialogs.ComponentDialog; +import com.microsoft.bot.dialogs.DialogTurnResult; +import com.microsoft.bot.dialogs.WaterfallDialog; +import com.microsoft.bot.dialogs.WaterfallStep; +import com.microsoft.bot.dialogs.WaterfallStepContext; +import com.microsoft.bot.dialogs.prompts.PromptOptions; +import com.microsoft.bot.dialogs.prompts.TextPrompt; +import com.microsoft.bot.schema.Activity; +import com.microsoft.bot.schema.InputHints; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.microsoft.bot.builder.BotTelemetryClient; +import org.apache.commons.lang3.StringUtils; + +/** + * The class containing the main dialog for the sample. + */ +public class MainDialog extends ComponentDialog { + + private final FlightBookingRecognizer luisRecognizer; + private final Integer plusDayValue = 7; + + /** + * The constructor of the Main Dialog class. + * + * @param withLuisRecognizer The FlightBookingRecognizer object. + * @param bookingDialog The BookingDialog object with booking dialogs. + * @param withTelemetryClient The BotTelemetryClient to use. + */ + public MainDialog(FlightBookingRecognizer withLuisRecognizer, + BookingDialog bookingDialog, + BotTelemetryClient withTelemetryClient) { + super("MainDialog"); + + luisRecognizer = withLuisRecognizer; + + // Set the telemetry client for this and all child dialogs + this.setTelemetryClient(withTelemetryClient); + + addDialog(new TextPrompt("TextPrompt")); + addDialog(bookingDialog); + WaterfallStep[] waterfallSteps = { + this::introStep, + this::actStep, + this::finalStep + }; + addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); + + // The initial child Dialog to run. + setInitialDialogId("WaterfallDialog"); + } + + /** + * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a + * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the + * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture introStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + Activity text = MessageFactory.text("NOTE: LUIS is not configured. " + + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " + + "to the application.properties file.", null, InputHints.IGNORING_INPUT); + return stepContext.getContext().sendActivity(text) + .thenCompose(sendResult -> stepContext.next(null)); + } + + // Use the text provided in FinalStepAsync or the default if it is the first time. + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); + String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); + String messageText = stepContext.getOptions() != null + ? stepContext.getOptions().toString() + : String.format("What can I help you with today?\n" + + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); + Activity promptMessage = MessageFactory + .text(messageText, messageText, InputHints.EXPECTING_INPUT); + PromptOptions promptOptions = new PromptOptions(); + promptOptions.setPrompt(promptMessage); + return stepContext.prompt("TextPrompt", promptOptions); + } + + /** + * Second step in the waterfall. This will use LUIS to attempt to extract the origin, + * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect + * any remaining details. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture actStep(WaterfallStepContext stepContext) { + if (!luisRecognizer.isConfigured()) { + // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. + return stepContext.beginDialog("BookingDialog", new BookingDetails()); + } + + // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) + return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { + switch (luisResult.getTopScoringIntent().intent) { + case "BookFlight": + // Extract the values for the composite entities from the LUIS result. + ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); + ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); + + // Show a warning for Origin and Destination if we can't resolve them. + return showWarningForUnsupportedCities( + stepContext.getContext(), fromEntities, toEntities) + .thenCompose(showResult -> { + // Initialize BookingDetails with any entities we may have found in the response. + BookingDetails bookingDetails = new BookingDetails(); + bookingDetails.setDestination(toEntities.get("airport").asText()); + bookingDetails.setOrigin(fromEntities.get("airport").asText()); + bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); + // Run the BookingDialog giving it whatever details we have from the LUIS call, + // it will fill out the remainder. + return stepContext.beginDialog("BookingDialog", bookingDetails); + } + ); + case "GetWeather": + // We haven't implemented the GetWeatherDialog so we just display a TODO message. + String getWeatherMessageText = "TODO: get weather flow here"; + Activity getWeatherMessage = MessageFactory + .text( + getWeatherMessageText, getWeatherMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(getWeatherMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); + + default: + // Catch all for unhandled intents + String didntUnderstandMessageText = String.format( + "Sorry, I didn't get that. Please " + + " try asking in a different way (intent was %s)", + luisResult.getTopScoringIntent().intent + ); + Activity didntUnderstandMessage = MessageFactory + .text( + didntUnderstandMessageText, didntUnderstandMessageText, + InputHints.IGNORING_INPUT + ); + return stepContext.getContext().sendActivity(didntUnderstandMessage) + .thenCompose(resourceResponse -> stepContext.next(null)); + } + }); + } + + /** + * Shows a warning if the requested From or To cities are recognized as entities but they are + * not in the Airport entity list. In some cases LUIS will recognize the From and To composite + * entities as a valid cities but the From and To Airport values will be empty if those entity + * values can't be mapped to a canonical item in the Airport. + * + * @param turnContext A {@link WaterfallStepContext} + * @param fromEntities An ObjectNode with the entities of From object + * @param toEntities An ObjectNode with the entities of To object + * @return A task + */ + private static CompletableFuture showWarningForUnsupportedCities( + TurnContext turnContext, + ObjectNode fromEntities, + ObjectNode toEntities + ) { + List unsupportedCities = new ArrayList(); + + if (StringUtils.isNotBlank(fromEntities.get("from").asText()) + && StringUtils.isBlank(fromEntities.get("airport").asText())) { + unsupportedCities.add(fromEntities.get("from").asText()); + } + + if (StringUtils.isNotBlank(toEntities.get("to").asText()) + && StringUtils.isBlank(toEntities.get("airport").asText())) { + unsupportedCities.add(toEntities.get("to").asText()); + } + + if (!unsupportedCities.isEmpty()) { + String messageText = String.format( + "Sorry but the following airports are not supported: %s", + String.join(", ", unsupportedCities) + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + return turnContext.sendActivity(message) + .thenApply(sendResult -> null); + } + + return CompletableFuture.completedFuture(null); + } + + /** + * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" + * interaction with a simple confirmation. + * + * @param stepContext A {@link WaterfallStepContext} + * @return A {@link DialogTurnResult} + */ + private CompletableFuture finalStep(WaterfallStepContext stepContext) { + CompletableFuture stepResult = CompletableFuture.completedFuture(null); + + // If the child dialog ("BookingDialog") was cancelled, + // the user failed to confirm or if the intent wasn't BookFlight + // the Result here will be null. + if (stepContext.getResult() instanceof BookingDetails) { + // Now we have all the booking details call the booking service. + // If the call to the booking service was successful tell the user. + BookingDetails result = (BookingDetails) stepContext.getResult(); + TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); + String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); + String messageText = String.format("I have you booked to %s from %s on %s", + result.getDestination(), result.getOrigin(), travelDateMsg + ); + Activity message = MessageFactory + .text(messageText, messageText, InputHints.IGNORING_INPUT); + stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); + } + + // Restart the main dialog with a different message the second time around + String promptMessage = "What else can I do for you?"; + return stepResult + .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); + } +} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java new file mode 100644 index 000000000..037f6f74c --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the corebot-app-insights sample. + */ +package com.microsoft.bot.sample.corebot.app.insights; diff --git a/samples/21.corebot-app-insights/src/main/resources/application.properties b/samples/21.corebot-app-insights/src/main/resources/application.properties new file mode 100644 index 000000000..cd458c966 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/resources/application.properties @@ -0,0 +1,8 @@ +MicrosoftAppId= +MicrosoftAppPassword= +LuisAppId= +LuisAPIKey= +LuisAPIHostName= +# Specifies my instrumentation key for Application Insights. +ApplicationInsights.InstrumentationKey= +server.port=3978 diff --git a/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json b/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json new file mode 100644 index 000000000..9b6389e39 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json @@ -0,0 +1,46 @@ +{ + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "type": "AdaptiveCard", + "version": "1.0", + "body": [ + { + "type": "Image", + "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", + "size": "stretch" + }, + { + "type": "TextBlock", + "spacing": "medium", + "size": "default", + "weight": "bolder", + "text": "Welcome to Bot Framework!", + "wrap": true, + "maxLines": 0 + }, + { + "type": "TextBlock", + "size": "default", + "isSubtle": true, + "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", + "wrap": true, + "maxLines": 0 + } + ], + "actions": [ + { + "type": "Action.OpenUrl", + "title": "Get an overview", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" + }, + { + "type": "Action.OpenUrl", + "title": "Ask a question", + "url": "https://stackoverflow.com/questions/tagged/botframework" + }, + { + "type": "Action.OpenUrl", + "title": "Learn how to deploy", + "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" + } + ] +} diff --git a/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF b/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF new file mode 100644 index 000000000..254272e1c --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF @@ -0,0 +1,3 @@ +Manifest-Version: 1.0 +Class-Path: + diff --git a/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml b/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 000000000..383c19004 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,12 @@ + + + dispatcher + + org.springframework.web.servlet.DispatcherServlet + + + contextConfigLocation + /WEB-INF/spring/dispatcher-config.xml + + 1 + \ No newline at end of file diff --git a/samples/21.corebot-app-insights/src/main/webapp/index.html b/samples/21.corebot-app-insights/src/main/webapp/index.html new file mode 100644 index 000000000..730d2c453 --- /dev/null +++ b/samples/21.corebot-app-insights/src/main/webapp/index.html @@ -0,0 +1,417 @@ + + + + + + + Core Bot Sample + + + + + +
+
+
+
Core Bot Sample
+
+
+
+
+
Your bot is ready!
+
You can test your bot in the Bot Framework Emulator
+ by connecting to http://localhost:3978/api/messages.
+ +
Visit Azure + Bot Service to register your bot and add it to
+ various channels. The bot's endpoint URL typically looks + like this:
+
https://your_bots_hostname/api/messages
+
+
+
+
+ +
+ + diff --git a/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java b/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java new file mode 100644 index 000000000..de794a90c --- /dev/null +++ b/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java @@ -0,0 +1,19 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.corebot.app.insights; + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; + +@RunWith(SpringRunner.class) +@SpringBootTest +public class ApplicationTest { + + @Test + public void contextLoads() { + } + +} From 678ab1d692d3ca1e1c34ec2ec791d8605c204fe7 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 7 Apr 2021 12:30:18 -0500 Subject: [PATCH 134/221] Set Includesnapshot to false (#1137) Co-authored-by: tracyboehrer --- .../microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java index 2b96d9138..74c5b1553 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/memory/scopes/TurnMemoryScope.java @@ -16,7 +16,7 @@ public class TurnMemoryScope extends MemoryScope { * Initializes a new instance of the TurnMemoryScope class. */ public TurnMemoryScope() { - super(ScopePath.TURN, true); + super(ScopePath.TURN, false); } /** From 08b70aa58f02f65f2aa6e4358b56570aa2f372c2 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Wed, 7 Apr 2021 12:52:52 -0500 Subject: [PATCH 135/221] Updates for Issue 1134 (#1139) --- .../com/microsoft/bot/dialogs/Dialog.java | 14 ++-- .../microsoft/bot/dialogs/DialogManager.java | 2 +- .../bot/dialogs/DialogManagerTests.java | 71 +++++++++++++++++++ 3 files changed, 80 insertions(+), 7 deletions(-) diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java index 339c86972..b17c46897 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/Dialog.java @@ -19,6 +19,7 @@ import com.microsoft.bot.connector.authentication.GovernmentAuthenticationConstants; import com.microsoft.bot.connector.authentication.SkillValidation; import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; import com.microsoft.bot.schema.EndOfConversationCodes; @@ -322,24 +323,25 @@ public static CompletableFuture run(Dialog dialog, TurnContext turnContext dialogSet.add(dialog); return dialogSet.createContext(turnContext) - .thenAccept(dialogContext -> innerRun(turnContext, dialog.getId(), dialogContext)); + .thenAccept(dialogContext -> innerRun(turnContext, dialog.getId(), dialogContext, null)); } /** * Shared implementation of run with Dialog and DialogManager. * - * @param turnContext The turnContext. - * @param dialogId The Id of the Dialog. - * @param dialogContext The DialogContext. + * @param turnContext The turnContext. + * @param dialogId The Id of the Dialog. + * @param dialogContext The DialogContext. + * @param stateConfiguration The DialogStateManagerConfiguration. * @return A DialogTurnResult. */ protected static CompletableFuture innerRun(TurnContext turnContext, String dialogId, - DialogContext dialogContext) { + DialogContext dialogContext, DialogStateManagerConfiguration stateConfiguration) { for (Entry entry : turnContext.getTurnState().getTurnStateServices().entrySet()) { dialogContext.getServices().replace(entry.getKey(), entry.getValue()); } - DialogStateManager dialogStateManager = new DialogStateManager(dialogContext); + DialogStateManager dialogStateManager = new DialogStateManager(dialogContext, stateConfiguration); return dialogStateManager.loadAllScopes().thenCompose(result -> { dialogContext.getContext().getTurnState().add(dialogStateManager); DialogTurnResult dialogTurnResult = null; diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java index d5f566797..071aeb6b8 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/DialogManager.java @@ -254,7 +254,7 @@ public CompletableFuture onTurn(TurnContext context) { // Create DialogContext DialogContext dc = new DialogContext(dialogs, context, dialogState); - return Dialog.innerRun(context, rootDialogId, dc).thenCompose(turnResult -> { + return Dialog.innerRun(context, rootDialogId, dc, getStateManagerConfiguration()).thenCompose(turnResult -> { return botStateSet.saveAllChanges(dc.getContext(), false).thenCompose(saveResult -> { DialogManagerResult result = new DialogManagerResult(); result.setTurnResult(turnResult); diff --git a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java index 431631e98..1a80b3514 100644 --- a/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java +++ b/libraries/bot-dialogs/src/test/java/com/microsoft/bot/dialogs/DialogManagerTests.java @@ -31,14 +31,20 @@ import com.microsoft.bot.builder.UserState; import com.microsoft.bot.builder.adapters.TestAdapter; import com.microsoft.bot.builder.adapters.TestFlow; +import com.microsoft.bot.dialogs.memory.DialogStateManager; +import com.microsoft.bot.dialogs.memory.DialogStateManagerConfiguration; +import com.microsoft.bot.dialogs.memory.PathResolver; +import com.microsoft.bot.dialogs.memory.scopes.MemoryScope; import com.microsoft.bot.dialogs.prompts.PromptOptions; import com.microsoft.bot.dialogs.prompts.TextPrompt; import com.microsoft.bot.schema.Activity; import com.microsoft.bot.schema.ActivityTypes; +import com.microsoft.bot.schema.ChannelAccount; import com.microsoft.bot.schema.ConversationAccount; import com.microsoft.bot.schema.ResourceResponse; import com.microsoft.bot.schema.ResultPair; +import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; @@ -462,6 +468,44 @@ public void SkillShouldReturnEmptyOnRepromptWithNoDialog() { Assert.assertEquals(DialogTurnStatus.EMPTY, _dmTurnResult.getTurnResult().getStatus()); } + @Test + public void DialogManager_StateConfigurationTest() { + // Arrange + WaterfallDialog dialog = new WaterfallDialog("test-dialog", null); + + CustomMemoryScope memoryScope = new CustomMemoryScope(); + CustomPathResolver pathResolver = new CustomPathResolver(); + + DialogManager dialogManager = new DialogManager(dialog, null); + dialogManager.setStateManagerConfiguration(new DialogStateManagerConfiguration()); + dialogManager.getStateManagerConfiguration().getMemoryScopes().add(memoryScope); + dialogManager.getStateManagerConfiguration().getPathResolvers().add(pathResolver); + + // The test dialog being used here happens to not send anything so we only need to mock the type. + TestAdapter adapter = new TestAdapter(); + + // ChannelId and Conversation.Id are required for ConversationState and + // ChannelId and From.Id are required for UserState. + Activity activity = new Activity(ActivityTypes.MESSAGE); + activity.setChannelId("test-channel"); + ConversationAccount conversation = new ConversationAccount(); + conversation.setId("test-conversation-id"); + ChannelAccount channelAccount = new ChannelAccount(); + channelAccount.setId("test-id"); + activity.setConversation(conversation); + activity.setFrom(channelAccount); + + // Act + TurnContext turnContext = new TurnContextImpl(adapter, activity); + turnContext.getTurnState().add(new ConversationState(new MemoryStorage())); + dialogManager.onTurn(turnContext).join(); + DialogStateManager actual = turnContext.getTurnState().get(DialogStateManager.class); + + // Assert + Assert.assertTrue(actual.getConfiguration().getMemoryScopes().contains(memoryScope)); + Assert.assertTrue(actual.getConfiguration().getPathResolvers().contains(pathResolver)); + } + private Dialog CreateTestDialog(String property) { return new AskForNameDialog(property.replace(".", ""), property); } @@ -539,6 +583,33 @@ public CompletableFuture invoke(TurnContext context, List Date: Thu, 8 Apr 2021 12:21:22 -0500 Subject: [PATCH 136/221] Add Pom.xml to compile all samples. (#1142) --- samples/54.teams-task-module/pom.xml | 5 - samples/pom.xml | 672 +++++++++++++++++++++++++++ 2 files changed, 672 insertions(+), 5 deletions(-) create mode 100644 samples/pom.xml diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index ea430758f..f98df3f05 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -88,11 +88,6 @@ json 20190722
- - io.adaptivecards - adaptivecards-android - 1.2.8 - diff --git a/samples/pom.xml b/samples/pom.xml new file mode 100644 index 000000000..411e6aa0a --- /dev/null +++ b/samples/pom.xml @@ -0,0 +1,672 @@ + + + 4.0.0 + + com.microsoft.bot + bot-java + 4.6.0-preview9 + pom + + Microsoft BotBuilder Java SDK Parent + This package contains the parent module of Microsoft BotBuilder Java SDK. + https://github.com/Microsoft/botbuilder-java + + + UTF-8 + true + 1.8 + 1.8 + 1.8 + + 3.14.0 + MyGet + https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + scm:git:https://github.com/Microsoft/botbuilder-java + scm:git:https://github.com/Microsoft/botbuilder-java + https://github.com/Microsoft/botbuilder-java + + + + + build + + true + + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-pmd-plugin + + + + org.jacoco + jacoco-maven-plugin + + + default-prepare-agent + + prepare-agent + + + + default-report + + report + + + + + + + + + + + + + + + + + + + + + + + + + + + + + devops + + + + src/main/resources + true + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-pmd-plugin + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + Licensed under the MIT License. See LICENSE in the project root for license information.]]> + + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.jacoco + jacoco-maven-plugin + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + publish + + + + org.apache.maven.plugins + maven-compiler-plugin + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + + + + + + + + + junit + junit + 4.13.1 + test + + + org.mockito + mockito-core + 3.8.0 + test + + + + com.fasterxml.jackson.module + jackson-module-parameter-names + 2.12.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jdk8 + 2.12.1 + + + com.fasterxml.jackson.datatype + jackson-datatype-jsr310 + 2.12.1 + + + com.fasterxml.jackson.core + jackson-databind + 2.12.1 + + + + com.codepoetics + protonpack + 1.13 + + + com.auth0 + java-jwt + 3.13.0 + + + com.auth0 + jwks-rsa + 0.15.0 + + + org.slf4j + slf4j-api + 1.7.22 + + + org.apache.commons + commons-lang3 + 3.9 + + + commons-io + commons-io + 2.8.0 + + + com.google.guava + guava + 30.1-jre + + + + org.apache.logging.log4j + log4j-api + 2.11.0 + test + + + org.slf4j + slf4j-log4j12 + 1.7.25 + test + + + org.apache.logging.log4j + log4j-core + 2.13.2 + test + + + + com.squareup.okhttp3 + okhttp + 3.12.2 + + + com.squareup.okhttp3 + logging-interceptor + 3.12.2 + + + com.squareup.okhttp3 + okhttp-urlconnection + 3.12.2 + + + com.squareup.okhttp3 + mockwebserver + 3.12.2 + test + + + + com.microsoft.bot + bot-schema + ${project.version} + + + com.microsoft.bot + bot-connector + ${project.version} + + + com.microsoft.bot + bot-integration-core + ${project.version} + + + com.microsoft.bot + bot-integration-spring + ${project.version} + + + com.microsoft.bot + bot-builder + ${project.version} + + + com.microsoft.bot + bot-dialogs + ${project.version} + + + com.microsoft.bot + bot-ai-luis-v3 + ${project.version} + + + com.microsoft.bot + bot-applicationinsights + ${project.version} + + + com.microsoft.bot + bot-ai-qna + ${project.version} + + + + + + + ${repo.id} + ${repo.url} + + true + + + true + + + + + + + + + + + ${repo.id} + ${repo.url} + + + + + 02.echo-bot + 03.welcome-user + 05.multi-turn-prompt + 06.using-cards + 07.using-adaptive-cards + 08.suggested-actions + 11.qnamaker + 13.core-bot + 14.nlp-with-dispatch + 15.handling-attachments + 16.proactive-messages + 17.multilingual-bot + 18.bot-authentication + 19.custom-dialogs + 21.corebot-app-insights + 23.facebook-events + 24.bot-authentication-msgraph + 25.message-reaction + 43.complex-dialog + 44.prompt-users-for-input + 45.state-management + 46.teams-auth + 47.inspection + 49.qnamaker-all-features + 50.teams-messaging-extensions-search + 51.teams-messaging-extensions-action + 52.teams-messaging-extensions-search-auth-config + 53.teams-messaging-extensions-action-preview + 54.teams-task-module + 55.teams-link-unfurling + 56.teams-file-upload + 57.teams-conversation-bot + 58.teams-start-new-thread-in-channel + 80.skills-simple-bot-to-bot\DialogRootBot + 80.skills-simple-bot-to-bot\DialogSkillBot + 81.skills-skilldialog\dialog-root-bot + 81.skills-skilldialog\dialog-skill-bot + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.7.0 + + ${jdk.version} + ${jdk.version} + + -Xpkginfo:always + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.1 + + + + true + true + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.version} + + true + true + false + + + + validate + + check + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.22.2 + + + **/*Test*.java + + + + + + org.jacoco + jacoco-maven-plugin + 0.8.6 + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + ossrh + https://oss.sonatype.org/ + true + + + + org.apache.maven.plugins + maven-gpg-plugin + 1.6 + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + 3.1.0 + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + 3.1.1 + + + attach-javadocs + + jar + + + + + + + org.apache.maven.plugins + maven-site-plugin + 3.9.1 + + + org.apache.maven.plugins + maven-project-info-reports-plugin + 3.1.1 + + + + + + + + + org.apache.maven.plugins + maven-pmd-plugin + ${pmd.version} + + + + org.jacoco + jacoco-maven-plugin + + + + + report + + + + + + + + From a72d4c486cc2d5a7d8bcb1e642233c46cfd4795d Mon Sep 17 00:00:00 2001 From: Josh Gummersall <1235378+joshgummersall@users.noreply.github.com> Date: Thu, 8 Apr 2021 13:22:25 -0700 Subject: [PATCH 137/221] Add pr style linting (#1144) --- .github/workflows/pr-style.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 .github/workflows/pr-style.yml diff --git a/.github/workflows/pr-style.yml b/.github/workflows/pr-style.yml new file mode 100644 index 000000000..51dbf531a --- /dev/null +++ b/.github/workflows/pr-style.yml @@ -0,0 +1,18 @@ +name: pr-style.yml + +on: + pull_request: + types: [opened, edited, synchronize] + +jobs: + prStyle: + name: pr-style + runs-on: ubuntu-latest + + steps: + - uses: joshgummersall/pr-style@main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + with: + require_issue: "true" + skip_authors: "dependabot" From 1248b4561d581d4763d59e470f4d65f2f5a7acf9 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Thu, 8 Apr 2021 16:08:36 -0500 Subject: [PATCH 138/221] Implemented ResumeDialog in Skills (#1140) Co-authored-by: tracyboehrer --- .../skills/SkillConversationIdFactory.java | 5 ++-- .../SkillConversationIdFactoryTests.java | 28 +++++++++++++++++++ .../microsoft/bot/dialogs/SkillDialog.java | 8 ++++++ 3 files changed, 38 insertions(+), 3 deletions(-) diff --git a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java index bee7c0fe1..a38214d25 100644 --- a/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java +++ b/libraries/bot-builder/src/main/java/com/microsoft/bot/builder/skills/SkillConversationIdFactory.java @@ -5,6 +5,7 @@ import java.util.HashMap; import java.util.Map; +import java.util.UUID; import java.util.concurrent.CompletableFuture; import com.microsoft.bot.builder.Storage; @@ -51,9 +52,7 @@ public CompletableFuture createSkillConversationId(SkillConversationIdFa Async.completeExceptionally(new IllegalArgumentException("options cannot be null.")); } ConversationReference conversationReference = options.getActivity().getConversationReference(); - String skillConversationId = String.format("%s-%s-%s-skillconvo", - conversationReference.getConversation().getId(), options.getBotFrameworkSkill().getId(), - conversationReference.getChannelId()); + String skillConversationId = UUID.randomUUID().toString(); SkillConversationReference skillConversationReference = new SkillConversationReference(); skillConversationReference.setConversationReference(conversationReference); diff --git a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java index 864b66ff0..db67c97b0 100644 --- a/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java +++ b/libraries/bot-builder/src/test/java/com/microsoft/bot/builder/SkillConversationIdFactoryTests.java @@ -64,6 +64,34 @@ public void SkillConversationIdFactoryHappyPath() { Assert.assertNull(deletedConversationReference); } + @Test + public void IdIsUniqueEachTime() { + ConversationReference conversationReference = buildConversationReference(); + + // Create skill conversation + SkillConversationIdFactoryOptions options1 = new SkillConversationIdFactoryOptions(); + options1.setActivity(buildMessageActivity(conversationReference)); + options1.setBotFrameworkSkill(buildBotFrameworkSkill()); + options1.setFromBotId(botId); + options1.setFromBotOAuthScope(botId); + + String firstId = skillConversationIdFactory.createSkillConversationId(options1).join(); + + + SkillConversationIdFactoryOptions options2 = new SkillConversationIdFactoryOptions(); + options2.setActivity(buildMessageActivity(conversationReference)); + options2.setBotFrameworkSkill(buildBotFrameworkSkill()); + options2.setFromBotId(botId); + options2.setFromBotOAuthScope(botId); + + String secondId = skillConversationIdFactory.createSkillConversationId(options2).join(); + + // Ensure that we get a different conversationId each time we call CreateSkillConversationIdAsync + Assert.assertNotEquals(firstId, secondId); + } + + + private static ConversationReference buildConversationReference() { ConversationReference conversationReference = new ConversationReference(); conversationReference.setConversation(new ConversationAccount(UUID.randomUUID().toString())); diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java index 3295c03f8..0c74a9cc4 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/bot/dialogs/SkillDialog.java @@ -116,6 +116,14 @@ public CompletableFuture beginDialog(DialogContext dc, Object */ @Override public CompletableFuture continueDialog(DialogContext dc) { + + Boolean interrupted = dc.getState().getValue(TurnPath.INTERRUPTED, false, Boolean.class); + if (interrupted) { + dc.getState().setValue(TurnPath.INTERRUPTED, false); + return resumeDialog(dc, DialogReason.END_CALLED); + } + + if (!onValidateActivity(dc.getContext().getActivity())) { return CompletableFuture.completedFuture(END_OF_TURN); } From 8341c5c38ccacdbacfb40f3fc79ead476921210e Mon Sep 17 00:00:00 2001 From: Martin Battaglino Date: Fri, 9 Apr 2021 18:05:07 -0300 Subject: [PATCH 139/221] [Samples] Add 40.timex-resolution sample (#1145) * Add pom file * Add root files * Add Startup class * Add package-info file * Add documentation files * Change HashMap to LinkedHashMap in Recognizers-Text Datetime due to an issue * Add sample as module in pom.xml of samples folder --- .../parsers/BaseMergedDateTimeParser.java | 2 +- .../text/datetime/utilities/TimexUtility.java | 3 +- samples/40.timex-resolution/LICENSE | 21 ++ samples/40.timex-resolution/README.md | 357 ++++++++++++++++++ samples/40.timex-resolution/pom.xml | 204 ++++++++++ .../sample/timex/resolution/Ambiguity.java | 123 ++++++ .../sample/timex/resolution/Application.java | 32 ++ .../sample/timex/resolution/Constraints.java | 43 +++ .../timex/resolution/LanguageGeneration.java | 40 ++ .../bot/sample/timex/resolution/Parsing.java | 49 +++ .../bot/sample/timex/resolution/Ranges.java | 76 ++++ .../sample/timex/resolution/Resolutions.java | 47 +++ .../sample/timex/resolution/package-info.java | 8 + samples/pom.xml | 1 + 14 files changed, 1004 insertions(+), 2 deletions(-) create mode 100644 samples/40.timex-resolution/LICENSE create mode 100644 samples/40.timex-resolution/README.md create mode 100644 samples/40.timex-resolution/pom.xml create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ambiguity.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Application.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Constraints.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/LanguageGeneration.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Parsing.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ranges.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Resolutions.java create mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/package-info.java diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java index 7039f98b1..434d5372b 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/parsers/BaseMergedDateTimeParser.java @@ -438,7 +438,7 @@ public SortedMap dateTimeResolution(DateTimeParseResult slot) { } List> resolutions = new ArrayList<>(); - Map res = new HashMap<>(); + LinkedHashMap res = new LinkedHashMap<>(); DateTimeResolutionResult val = (DateTimeResolutionResult)slot.getValue(); if (val == null) { diff --git a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java index 7c6e79aa5..ce68c0bb3 100644 --- a/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java +++ b/libraries/bot-dialogs/src/main/java/com/microsoft/recognizers/text/datetime/utilities/TimexUtility.java @@ -13,6 +13,7 @@ import java.time.temporal.ChronoUnit; import java.util.ArrayList; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -281,7 +282,7 @@ public static String mergeTimexAlternatives(String timex1, String timex2) { return timex1 + Constants.CompositeTimexDelimiter + timex2; } - public static Map processDoubleTimex(Map resolutionDic, String futureKey, String pastKey, String originTimex) { + public static LinkedHashMap processDoubleTimex(LinkedHashMap resolutionDic, String futureKey, String pastKey, String originTimex) { String[] timexes = originTimex.split(Constants.CompositeTimexSplit); if (!resolutionDic.containsKey(futureKey) || !resolutionDic.containsKey(pastKey) || timexes.length != 2) { diff --git a/samples/40.timex-resolution/LICENSE b/samples/40.timex-resolution/LICENSE new file mode 100644 index 000000000..21071075c --- /dev/null +++ b/samples/40.timex-resolution/LICENSE @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. All rights reserved. + + 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 diff --git a/samples/40.timex-resolution/README.md b/samples/40.timex-resolution/README.md new file mode 100644 index 000000000..ec8099c95 --- /dev/null +++ b/samples/40.timex-resolution/README.md @@ -0,0 +1,357 @@ +# Timex Resolution + +This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use TIMEX expressions. + +A number of topics are covered within this readme. + +* [Concepts introduced in this sample](#Concepts-introduced-in-this-sample) +* [To try this sample](#to-try-this-sample) +* [Testing the bot using Bot Framework Emulator](#Testing-the-bot-using-Bot-Framework-Emulator) +* [Experimenting with Recognizers](#experimenting-with-recognizers) +* [Representing ambiguity](#representing-ambiguity) +* [Representing duration](#representing-duration) +* [Representing ranges](#representing-ranges) +* [Special concepts](#special-concepts) +* [Limitations](#limitations) +* [Resolution](#resolution) +* [TIMEX resolution using the TimexExpressions library](#TIMEX-resolution-using-the-TimexExpressions-library) + +## Concepts introduced in this sample + +### What is a TIMEX expression? + +Natural language has many different ways to express ambiguous dates, ranges of dates and times and durations. However, these concepts cannot be represented with the regular [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) date time representation. This is why TIMEX exists. TIMEX takes ISO 8601 as a starting point and provides additional mechanisms for these concepts. The resulting format normalizes and formalizes these concepts such that they can be processed with regular program logic. + +Whenever the ISO 8601 representation is sufficient the TIMEX value is identical. TIMEX is often refered to as an expression because unlike a discrete data time value it can represent a set of values. This will be clear when we look at some examples. + +TIMEX expressions can be additionally described with a type. The notion of type is more descriptive than it is constraining. You can, for instance, look at a TIMEX expression and infer its type. This can be useful in application logic. For example, if a bot is expecting a datetimerange but only has a daterange then it can prompt the user according. Again this will be clear when when we look at some examples. + +## To try this sample + +- From the root of this project folder: + - Build the sample using `mvn package` + - Run it by using `java -jar .\target\bot-timex-resolution-sample.jar` + +## Experimenting with Recognizers + +TIMEX has been introduced into the bot authoring platform by the [Text Recognizers](https://github.com/Microsoft/Recognizers-Text) package. One of the best ways to understand the TIMEX behavior is to experiment directly with the Rcognizers. You can do this by install the appropriate Recognizer package, instantiating the date time recognizer and calling it with your test string. In Node you can use the following 3 lines of code, but the behavior is identical in C#, Python and Java. + +``` + const Recognizer = require('@microsoft/recognizers-text-date-time') + const result = Recognizer.recognizeDateTime("next Wednesday 4pm", Recognizer.Culture.English); + console.log(JSON.stringify(result, null, 2)); +``` + +Alternatively you can use [LUIS](https://www.luis.ai/home). When you add a datetime2 datatype to your LUIS model you are asking LUIS to run this exact same recognizer on the input. + +## Representing ambiguity + +We'll start with an example. The computer scientist Alan Turning was born on June 23rd in 1912. The ISO 8601 representation of this date would be: + +``` +1912-06-23 +``` +and this happens to also be the correct TIMEX respresentation. However, with TIMEX we can also represent Alan's birthday: + +``` +XXXX-06-23 +``` + +Here the XXXX means "any." In other words we can represent the concept of June 23rd without being specific about the year. There are many June 23rds, one every year. This is what we mean here when we say "June 23rd" is ambiguous; we know its June 23rd we just don't know which June 23rd. + +TIMEX introduces its own syntax here as an extension to the ISO 8601 format. The XXXX in the previous example, appears in place of the 4 characters that make up the year component. This at first appears to resemble the approach of regular expressions or COBOL pictures but there is less flexibility. In the case of the year component you only ever see XXXX and never a partial pattern such as 19XX. + +A time can also be included, for example, 4pm: + +``` +XXXX-06-23T16 +``` + +This wildcard mechanism is only defined for the date part and not the time part. If you want to represent ambiguity in the time part then you will need multiple TIMEX expressions as we will see later. + +In terms of type, all the examples so far have been dates and date-times. (When we say "type" it is just something we have inferred from the underlying TIMEX expression - its does not add any additional semantics, it just might be a classificaton application logic could make use of. For example, if you know you have a date when you wanted a datetime then you know you are missing the time component.) It is possible to also just have the time component by itself, for example 4pm would be represented as: + +``` +T16 +``` + +The use of X to blank out the characters is also applies to the representation of weeks, for example, "Monday," blanks out both the year and the ISO week number: + +``` +XXXX-WXX-1 +``` + +Here XXXX means *any* year and WXX means *any* week, so what remains is the day component. And with the ISO week starting on a Monday and indexing from 1 we get Monday. + +The Text.Recognizers are written to attempt to resolve to a specific date if they can. This can result in some surprisingly subtle behavior. For exampe, if we had said "this Monday" it would have resolved to a specific date such as this: + +``` +2020-06-15 +``` + +Although we onlt had a small change in the text, we ended out with quite a significant change in the resulting TIMEX. + +Similarly for weeks, the Text.Recognizers will attempt to resolve when they see a word like "this" that grounds things. For example, the phrase "this week" might result to a TIMEX like this: + +``` +2020-W25 +``` + +In general care should be taken with logic based around weeks of the year as this can be a source of bugs in application code. How the ISO standard defines this is well documented online. Its important to note the ISO week (and therefore the TIMEX week) starts on a Monday with days of the week being numbered from 1. + +Note TIMEX also applies the ISO 8601 syntax of week number to describe the week *within* a month. Representing "the first week of June" as follows: + +``` +XXXX-06-W01 +``` + +## Representing Duration + +TIMEX expressions can represent duration. There are both date durations and time durations. A date duration is indicated by a leading P and a time duration is indicated by a leading PT. Durations have units, formatted as a letter following the value, and for date durations these are D, W, M and Y for day, week, month and year respectively. For time durations these are S, M and H for seconds, minutes and hours. The value is not padded and may contain a decimal point. + +Here are some examples: + +1 week is: +``` +P1W +``` +16 years is: +``` +P16Y +``` +Decimal is supported, so the recognizers would take the string "three and a half months" and return the TIMEX: + +``` +P3.5M +``` + +30 second is: +``` +PT30S +``` +and 4 hours is: +``` +PT4H +``` + +Given the nature of the problem and the fact that decimal is supported, means there are many different ways to express the same duration, for example PT0.5H is the same as PT30M just as P1W is the same as P7D. The Text.Recognizers make no attempt to normalize such things, instead they just try to render the original natural language utterance as directly as possible. The Text.Recognizers also don't try to merge separate durations they find in the input. For example, the text "4 hours 30 minutes" will result in two results. + +## Representing Ranges + +### Date Ranges + +TIMEX expressions often represent ranges. For example the TIMEX expression: + +``` +1912 +``` + +is intended to represents all the possible dates in the year 1912. As such it is described as a daterange. The month of June in that year is represented as follows: + +``` +1912-06 +``` + +This is also a daterange and intended to represent all the dates of June in 1912. The year could be blanked out here too, as follows: + +``` +XXXX-06 +``` + +Where this means all the dates in June and that's any June in any year not one particular June. + +TIMEX breaks with the ISO 8601 pattern when it comes to representing seasons and uses a special two letter codes for Summer, Fall, Winter and Spring: SU, FA, WI and SP respectively. Again these are all date ranges. + +More complex data ranges are captured with a begin, end and duration structure. Each of these components in themselves is a TIMEX expression. For example, teh American President JFK was born on May 29th 1917 and died November 22nd 1963, his lifespan can can be captured with the following TIMEX: + +``` +(1917-05-29,1963-11-22,P16978D) +``` + +The start and end dates should be read as inclusive. + +Natural language contains many different ways to refer to date ranges, for example a Text Recognizer can take the natural language phrase "next two weeks" and assuming today as the start would produce the following TIMEX: + +``` +(2020-06-19,2020-07-03,P2W) +``` + +Expressing the duration in addition to the begin and end seems redundant but that is the way it is done. The duration unit used is somewhat arbitrary, in this last example, P14D would have been equivalent. + +### Time Ranges + +Time ranges are very similar, for example given the text "9am to 5pm" the Text.Recognizers would produce the following: + +``` +(T09,T17,PT8H) +``` + +There are a number of well known time range constants in TIMEX. These represent the concepts of morning, afternoon, evening, daytime and nighttime, encoded as two characters MO, AF, EV, DT and NI respectively. Although these are time ranges they don't bother with the (start, end, duration) syntax, they simply start with a T followed by the two character code. For example, morning is respresents like this: + +``` +TMO +``` + +### Date Time Range + +Date and time can be combined in the same range and the Text.Recognizers would recognize the text "this friday 9am to next friday 5pm" as the TIMEX: + +``` +(2020-06-19T09,2020-06-26T17,PT176H) +``` + +When the time component of a date time range can be represented with the shortened 2 characters the resulting expression is simplified appropriately. For example "this afternoon" is a date time range that would resemble this: + +``` +2020-06-19TAF +``` + +If there is ambiguity in the date portion that will follow the blanking out approach described earlier. For example, "friday evening" because it doesn't say which Friday is ambiguous and evening is a time range, so we end up with a date time range TIMEX that looks like this: + +``` +XXXX-WXX-5TEV +``` + +## Special concepts + +TIMEX includes a special representation "now" meaning the present moment. It looks like this: + +``` +PRESENT_REF +``` + +## Limitations + +TIMEX as it is currently defined and implemented has some inherent limitations. Perhaps the most obvious is that although TIMEX manages to capture ambiguity in the date component it can't in the time component. + +Consider the text "Wednesday 4 O'clock," we are not saying which particular Wednesday, it could be *any* Wednesday. As we saw in the earlier examples TIMEX deals with this by blanking out the parts of a date that could vary, specifically for Wednesday we have: + +``` +XXXX-WXX-3 +``` + +Meaning the 3 day of any week of any year. + +However, the language used for the time component is also ambiguous. When we see "4 O'clock" it could mean either 4am or 4pm. + +The solution in TIMEX is to provide multiple TIMEX expressions. In fact if you hand this string to the Text.Recognizers it will return two distinct TIMEX expressions in its results: + +``` +XXXX-WXX-3T04 +XXXX-WXX-3T16 +``` + +It is instructive to compare that with the result if teh language had not beed ambiguous. Trying the Text.Recognizer with the text "next Wednesday 4pm" and we will see a result resembling this: + +``` +2020-06-24T16 +``` + +Note the Text.Recognizers generally look for "last", "this" and "next" and include that in the scope when they can. + +This leads us to another conceptual limitation of the current TIMEX implementation and that is the concept of *relative time* is not captured. Specifically, looking at the last example, we said *next* Wednesday and this was recognized *and resolved* to a specific date. This is often what we want, however, sometimes we actually wanted to recognize the concept of *next* in and of itself rather than it being resolved. For example in a booking application we might want to travel out on a particular date and return on the "next Friday." We would want to calculate exactly which Friday the input refered to relative to the particular departure date and almost definitely not the not the current booking date! (Perhaps it would be more correct to have said "following Friday" but applying such strictness to our human users is a challenge. And anyhow, ultimately natural language is always understood by common usage in particular context rather than strict logical structures.) + +## Resolution + +A TIMEX expression such as: + +``` +XXXX-WXX-3T16 +``` + +Means Wednesday 4PM, however, something like a travel booking scenario would generally require something less ambiguous, that is to say, we need to know exactly which Wednesday. That is, we need to *resolve* the TIMEX expression to a specific date time. + +This can be achieved by constraining the orignal TIMEX expression with a date range, naturally expressed in TIMEX. So for example, applying the TIMEX date range: + +``` +2020-W27 +``` + +Meaning the 27th week of the year. + +Will result in the specific or definite date time: + +``` +2020-07-01T16 +``` + +This idea generalizes to dealing with collections. Both the original TIMEX could have been a set of TIMEX expressions and the constraint could have been a set of TIMEX ranges. + +As we could now have various differing time parts it makes sense to also have time ranges amongst the constraints. + +All the TIMEX expressions in the original data should be read as *or* and the TIMEX expressions in the constrains should be read as *and*. + +For example give the original set of TIMEX expressions: + +``` +XXXX-WXX-3T04 +XXXX-WXX-3T16 +``` + +Meaning Wednesday at 4AM *or* Wednesday ay 4PM. + +And applying the constraints: + +``` +2020-W27 +TAF +``` + +Meaning the 27th week of the year *and* in the afternoon. + +And we get to the specific date: + +``` +2020-07-01T16 +``` + +Another aspect to resolution is whether the original TIMEX expression is missing some part. For example, we might have just been given a date part to resolve. In this case the resolution is the process of adding the time part. For example, the original TIMEX expression: + +``` +2020-07-01 +``` + +Can be resolved with the constraint of T14 to get to the specific date time of: + +``` +2020-07-01T14 +``` + +## TIMEX resolution using the TimexExpressions library + +The approach to dealing with TIMEX expressions outlines in above is implemented in the [TimexExpressions](https://github.com/microsoft/Recognizers-Text/tree/master/Java/libraries/recognizers-text-datatypes-timex-expression) library current available for .NET, Java and JavaScript. And examples of its usage are included in this sample. + +This library includes support for parsing TIMEX expressions to extract their component parts. As described above, the datatype can be inferred from the underlying TIMEX expression. This notion of datatype has more in common with tagging. That is, a particular TIMEX instance can often be multiple types. For example, a datetime is also a date and also a time. This allows application code (and the resolution logic itself) to switch appropriately gives the content of the underlying TIMEX. + +It also includes support for generating natural language from the underlying TIMEX. Its behavior is to act as the reverse of the Text Recognizer. + +This library can also be used to generate locally the examples that LUIS provides in its results. In outline, this is basically forward and backward in time. So "Wednesday" would result in two examples, the date of last Wednesday and the date of this Wednesday. This way of resolution works perfectly well for many simple scenarios. However, its not as flexible nor as complete as the application of ranges described above. + +So in summary the library can: + +- Parse TIMEX expressions to give you the properties contained there in. +- Generate TIMEX expressions based on setting raw properties. +- Generate natural language from the TIMEX expression. (This is logically the reverse of the Recognizer.) +- Resolve TIMEX expressions to produce example date-times. (This produces the same result as the Recognizer (and therefore LUIS)). +- Evaluate TIMEX expressions against constraints such that new more precise TIMEX expressions are produced. +- It make take several steps, but ultimately you can resolve to a datetime instance, which is probably what your application is looking for. + +The code of sample 40 includes examples of all these different features. + +### Where is the source code? + +The TIMEX expression library is contained in the same GitHub repo as the recognizers. Refer to the further reading section below. + +## Further reading + +- [Bot Framework Documentation](https://docs.botframework.com) +- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) +- [TIMEX](https://en.wikipedia.org/wiki/TimeML#TIMEX3) +- [ISO 8601](https://en.wikipedia.org/wiki/ISO_8601) +- [Recognizers Text](https://github.com/Microsoft/recognizers-text) +- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) +- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) +- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) +- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) +- [Azure Portal](https://portal.azure.com) +- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/40.timex-resolution/pom.xml b/samples/40.timex-resolution/pom.xml new file mode 100644 index 000000000..9327e53d1 --- /dev/null +++ b/samples/40.timex-resolution/pom.xml @@ -0,0 +1,204 @@ + + + + 4.0.0 + + com.microsoft.bot.sample + bot-timex-resolution + sample + jar + + ${project.groupId}:${project.artifactId} + This package contains a Java Timex Resolution sample. + http://maven.apache.org + + + org.springframework.boot + spring-boot-starter-parent + 2.4.0 + + + + + + MIT License + http://www.opensource.org/licenses/mit-license.php + + + + + + Bot Framework Development + + Microsoft + https://dev.botframework.com/ + + + + + 1.8 + 1.8 + 1.8 + com.microsoft.bot.sample.timex.resolution.Application + + + + + com.microsoft.bot + bot-dialogs + 4.6.0-preview9 + + + + + + build + + true + + + + + src/main/resources + false + + + + + maven-compiler-plugin + 3.8.1 + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + org.springframework.boot + spring-boot-maven-plugin + + + + repackage + + + com.microsoft.bot.sample.timex.resolution.Application + + + + + + com.microsoft.azure + azure-webapp-maven-plugin + 1.12.0 + + V2 + ${groupname} + ${botname} + + + JAVA_OPTS + -Dserver.port=80 + + + + linux + Java 8 + Java SE + + + + + ${project.basedir}/target + + *.jar + + + + + + + + + + + + publish + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + maven-war-plugin + 3.2.3 + + src/main/webapp + + + + + org.sonatype.plugins + nexus-staging-maven-plugin + 1.6.8 + true + + true + ossrh + https://oss.sonatype.org/ + true + + + + + org.apache.maven.plugins + maven-gpg-plugin + + + sign-artifacts + verify + + sign + + + + + + org.apache.maven.plugins + maven-source-plugin + + + attach-sources + + jar + + + + + + org.apache.maven.plugins + maven-javadoc-plugin + + 8 + false + + + + attach-javadocs + + jar + + + + + + + + + diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ambiguity.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ambiguity.java new file mode 100644 index 000000000..1f40a6848 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ambiguity.java @@ -0,0 +1,123 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; + +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; + +/** + * TIMEX expressions are designed to represent ambiguous rather than definite dates. + * For example: + * "Monday" could be any Monday ever. + * "May 5th" could be any one of the possible May 5th in the past or the future. + * TIMEX does not represent ambiguous times. So if the natural language mentioned 4 o'clock + * it could be either 4AM or 4PM. For that the recognizer (and by extension LUIS) would return two TIMEX expressions. + * A TIMEX expression can include a date and time parts. So ambiguity of date can be combined with multiple results. + * Code that deals with TIMEX expressions is frequently dealing with sets of TIMEX expressions. + */ +public final class Ambiguity { + + private Ambiguity() { + } + + /** + * This method avoid ambiguity obtaining 2 values, backwards and forwards in the calendar. + */ + public static void dateAmbiguity() { + // Run the recognizer. + List results = DateTimeRecognizer.recognizeDateTime( + "Either Saturday or Sunday would work.", + Culture.English); + + // We should find two results in this example. + for (ModelResult result : results) { + // The resolution includes two example values: going backwards and forwards from NOW in the calendar. + LinkedHashSet distinctTimexExpressions = new LinkedHashSet<>(); + List> values = (List>) result.resolution.get("values"); + for (Map value : values) { + // Each result includes a TIMEX expression that captures the inherent date but not time ambiguity. + // We are interested in the distinct set of TIMEX expressions. + String timex = value.get("timex"); + if (timex != null) { + distinctTimexExpressions.add(timex); + } + + // There is also either a "value" property on each value or "start" and "end". + // If you use ToString() on a TimeProperty object you will get same "value". + } + + // The TIMEX expression captures date ambiguity so there will be a single distinct + // expression for each result. + String output = String.format("%s ( %s )", result.text, String.join(",", distinctTimexExpressions)); + System.out.println(output); + + // The result also includes a reference to the original string + // but note the start and end index are both inclusive. + } + } + + /** + * This method avoid ambiguity obtaining 2 values, one for AM and one for PM. + */ + public static void timeAmbiguity() { + // Run the recognizer. + List results = DateTimeRecognizer.recognizeDateTime( + "We would like to arrive at 4 o'clock or 5 o'clock.", + Culture.English); + + // We should find two results in this example. + for (ModelResult result : results) { + // The resolution includes two example values: one for AM and one for PM. + LinkedHashSet distinctTimexExpressions = new LinkedHashSet<>(); + List> values = (List>) result.resolution.get("values"); + for (Map value : values) { + // Each result includes a TIMEX expression that captures the inherent date but not time ambiguity. + // We are interested in the distinct set of TIMEX expressions. + String timex = value.get("timex"); + if (timex != null) { + distinctTimexExpressions.add(timex); + } + } + + // TIMEX expressions don't capture time ambiguity so there will be two distinct expressions for each result. + String output = String.format("%s ( %s )", result.text, String.join(",", distinctTimexExpressions)); + System.out.println(output); + } + } + + /** + * This method avoid ambiguity obtaining 4 different values, + * backwards and forwards in the calendar and then AM and PM. + */ + public static void dateTimeAmbiguity() { + // Run the recognizer. + List results = DateTimeRecognizer.recognizeDateTime( + "It will be ready Wednesday at 5 o'clock.", + Culture.English); + + // We should find a single result in this example. + for (ModelResult result : results) { + // The resolution includes four example values: backwards and forward in the calendar and then AM and PM. + LinkedHashSet distinctTimexExpressions = new LinkedHashSet<>(); + List> values = (List>) result.resolution.get("values"); + for (Map value : values) { + // Each result includes a TIMEX expression that captures the inherent date but not time ambiguity. + // We are interested in the distinct set of TIMEX expressions. + String timex = value.get("timex"); + if (timex != null) { + distinctTimexExpressions.add(timex); + } + } + + // TIMEX expressions don't capture time ambiguity so there will be two distinct expressions for each result. + String output = String.format("%s ( %s )", result.text, String.join(",", distinctTimexExpressions)); + System.out.println(output); + } + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Application.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Application.java new file mode 100644 index 000000000..df4645b51 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Application.java @@ -0,0 +1,32 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +/** + * This is the main program class. + */ +public final class Application { + + private Application() { + } + + /** + * This is the entry point method. + * @param args String array to capture any command line parameters. + */ + public static void main(String[] args) { + // Creating TIMEX expressions from natural language using the Recognizer package. + Ambiguity.dateAmbiguity(); + Ambiguity.timeAmbiguity(); + Ambiguity.dateTimeAmbiguity(); + Ranges.dateRange(); + Ranges.timeRange(); + + // Manipulating TIMEX expressions in code using the TIMEX Datatype package. + Parsing.examples(); + LanguageGeneration.examples(); + Resolutions.examples(); + Constraints.examples(); + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Constraints.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Constraints.java new file mode 100644 index 000000000..d21b6a3ab --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Constraints.java @@ -0,0 +1,43 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.datatypes.timex.expression.TimexCreator; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; +import com.microsoft.recognizers.datatypes.timex.expression.TimexRangeResolver; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; + +/** + * The TimexRangeResolved can be used in application logic to apply constraints to a set of TIMEX expressions. + * The constraints themselves are TIMEX expressions. This is designed to appear a little like a database join, + * of course its a little less generic than that because dates can be complicated things. + */ +public final class Constraints { + + private Constraints() { + } + + /** + * This method runs the resolver examples. + */ + public static void examples() { + // When you give the recognizer the text "Wednesday 4 o'clock" you get these distinct TIMEX values back. + + // But our bot logic knows that whatever the user says it should be evaluated against the constraints of + // a week from today with respect to the date part and in the evening with respect to the time part. + + List resolutions = TimexRangeResolver.evaluate( + new HashSet(Arrays.asList("XXXX-WXX-3T04", "XXXX-WXX-3T16")), + new ArrayList(Arrays.asList(TimexCreator.weekFromToday(null), TimexCreator.EVENING))); + + LocalDateTime today = LocalDateTime.now(); + for (TimexProperty resolution : resolutions) { + System.out.println(resolution.toNaturalLanguage(today)); + } + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/LanguageGeneration.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/LanguageGeneration.java new file mode 100644 index 000000000..44e101763 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/LanguageGeneration.java @@ -0,0 +1,40 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + +import java.time.LocalDateTime; + +/** + * This language generation capabilities are the logical opposite of what the recognizer does. + * As an experiment try feeding the result of language generation back into a recognizer. + * You should get back the same TIMEX expression in the result. + */ +public final class LanguageGeneration { + + private LanguageGeneration() { + } + + private static void describe(TimexProperty t) { + // Note natural language is often relative, + // for example the sentence "Yesterday all my troubles seemed so far away." + // Having your bot say something like "next Wednesday" in a response can make it sound more natural. + LocalDateTime referenceDate = LocalDateTime.now(); + String output = String.format("%s %s", t.getTimexValue(), t.toNaturalLanguage(referenceDate)); + System.out.println(output); + } + + /** + * This method runs the resolver examples. + */ + public static void examples() { + describe(new TimexProperty("2019-05-29")); + describe(new TimexProperty("XXXX-WXX-6")); + describe(new TimexProperty("XXXX-WXX-6T16")); + describe(new TimexProperty("T12")); + describe(TimexProperty.fromDate(LocalDateTime.now())); + describe(TimexProperty.fromDate(LocalDateTime.now().plusDays(1))); + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Parsing.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Parsing.java new file mode 100644 index 000000000..621322ed1 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Parsing.java @@ -0,0 +1,49 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.datatypes.timex.expression.Constants.TimexTypes; +import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; + + /** The TimexProperty class takes a TIMEX expression as a string argument in its constructor. + * This pulls all the component parts of the expression into properties on this object. You can + * then manipulate the TIMEX expression via those properties. + * The "Types" property infers a datetimeV2 type from the underlying set of properties. + * If you take a TIMEX with date components and add time components you add the + * inferred type datetime (its still a date). + * Logic can be written against the inferred type, perhaps to have the bot ask the user for disambiguation. + */ +public final class Parsing { + + private Parsing() { + } + + private static void describe(TimexProperty t) { + System.out.print(t.getTimexValue() + " "); + + if (t.getTypes().contains(TimexTypes.DATE)) { + if (t.getTypes().contains(TimexTypes.DEFINITE)) { + System.out.print("We have a definite calendar date. "); + } else { + System.out.print("We have a date but there is some ambiguity. "); + } + } + + if (t.getTypes().contains(TimexTypes.TIME)) { + System.out.print("We have a time."); + } + + System.out.println(); + } + + /** + * This method runs the parsing examples. + */ + public static void examples() { + describe(new TimexProperty("2017-05-29")); + describe(new TimexProperty("XXXX-WXX-6")); + describe(new TimexProperty("XXXX-WXX-6T16")); + describe(new TimexProperty("T12")); + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ranges.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ranges.java new file mode 100644 index 000000000..efc14f35f --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ranges.java @@ -0,0 +1,76 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.text.Culture; +import com.microsoft.recognizers.text.ModelResult; +import com.microsoft.recognizers.text.datetime.DateTimeRecognizer; +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.List; + +/** + * Class with date and time ranges examples. + */ +public final class Ranges { + + private Ranges() { + } + + /** + * TIMEX expressions can represent date and time ranges. Here are a couple of examples. + */ + public static void dateRange() { + // Run the recognizer. + List results = + DateTimeRecognizer.recognizeDateTime("Some time in the next two weeks.", + Culture.English); + + // We should find a single result in this example. + for (ModelResult result : results) { + // The resolution includes a single value because there is no ambiguity. + LinkedHashSet distinctTimexExpressions = new LinkedHashSet<>(); + List> values = (List>) result.resolution.get("values"); + for (HashMap value : values) { + // We are interested in the distinct set of TIMEX expressions. + String timex = value.get("timex"); + if (timex != null) { + distinctTimexExpressions.add(timex); + } + } + + // The TIMEX expression can also capture the notion of range. + String output = String.format("%s ( %s )", result.text, String.join(",", distinctTimexExpressions)); + System.out.println(output); + } + } + + /** + * This method has examples of time ranges. + */ + public static void timeRange() { + // Run the recognizer. + List results = + DateTimeRecognizer.recognizeDateTime("Some time between 6pm and 6:30pm.", + Culture.English); + + // We should find a single result in this example. + for (ModelResult result : results) { + // The resolution includes a single value because there is no ambiguity. + LinkedHashSet distinctTimexExpressions = new LinkedHashSet<>(); + List> values = (List>) result.resolution.get("values"); + for (HashMap value : values) { + // We are interested in the distinct set of TIMEX expressions. + String timex = value.get("timex"); + if (timex != null) { + distinctTimexExpressions.add(timex); + } + } + + // The TIMEX expression can also capture the notion of range. + String output = String.format("%s ( %s )", result.text, String.join(",", distinctTimexExpressions)); + System.out.println(output); + } + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Resolutions.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Resolutions.java new file mode 100644 index 000000000..b8edf9700 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Resolutions.java @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.bot.sample.timex.resolution; + +import com.microsoft.recognizers.datatypes.timex.expression.Resolution; +import com.microsoft.recognizers.datatypes.timex.expression.TimexResolver; +import java.time.LocalDateTime; + +/** + * Given the TIMEX expressions it is easy to create the computed example values that the recognizer gives. + */ +public final class Resolutions { + + private static final Integer THREE = 3; + + private Resolutions() { + } + + /** + * This method runs the resolver examples. + */ + public static void examples() { + // When you give the recognizer the text "Wednesday 4 o'clock" you get these distinct TIMEX values back. + + LocalDateTime today = LocalDateTime.now(); + Resolution resolution = TimexResolver.resolve(new String[] {"XXXX-WXX-3T04", "XXXX-WXX-3T16"}, today); + + System.out.println(resolution.getValues().size()); + + System.out.println(resolution.getValues().get(0).getTimex()); + System.out.println(resolution.getValues().get(0).getType()); + System.out.println(resolution.getValues().get(0).getValue()); + + System.out.println(resolution.getValues().get(1).getTimex()); + System.out.println(resolution.getValues().get(1).getType()); + System.out.println(resolution.getValues().get(1).getValue()); + + System.out.println(resolution.getValues().get(2).getTimex()); + System.out.println(resolution.getValues().get(2).getType()); + System.out.println(resolution.getValues().get(2).getValue()); + + System.out.println(resolution.getValues().get(THREE).getTimex()); + System.out.println(resolution.getValues().get(THREE).getType()); + System.out.println(resolution.getValues().get(THREE).getValue()); + } +} diff --git a/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/package-info.java b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/package-info.java new file mode 100644 index 000000000..41c20f0c7 --- /dev/null +++ b/samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/package-info.java @@ -0,0 +1,8 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. See License.txt in the project root for +// license information. + +/** + * This package contains the classes for the timex-resolution sample. + */ +package com.microsoft.bot.sample.timex.resolution; diff --git a/samples/pom.xml b/samples/pom.xml index 411e6aa0a..c8f23ac82 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -462,6 +462,7 @@ 23.facebook-events 24.bot-authentication-msgraph 25.message-reaction + 40.timex-resolution 43.complex-dialog 44.prompt-users-for-input 45.state-management From b8e0b3a5786dbb4f627fe4811ad8b3c42b9770ec Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 12 Apr 2021 10:40:25 -0500 Subject: [PATCH 140/221] Update version to 4.13.0 (#1148) * Update version to 4.13.0-SNAPSHOT * Update sample dependencies and log4j version --- .../generators/app/templates/core/project/pom.xml | 6 +++--- libraries/bot-ai-luis-v3/pom.xml | 2 +- libraries/bot-ai-qna/pom.xml | 2 +- libraries/bot-applicationinsights/pom.xml | 2 +- libraries/bot-azure/pom.xml | 2 +- libraries/bot-builder/pom.xml | 2 +- libraries/bot-connector/pom.xml | 2 +- libraries/bot-dialogs/pom.xml | 2 +- libraries/bot-integration-core/pom.xml | 2 +- libraries/bot-integration-spring/pom.xml | 2 +- libraries/bot-schema/pom.xml | 2 +- pom.xml | 2 +- samples/02.echo-bot/pom.xml | 2 +- samples/03.welcome-user/pom.xml | 2 +- samples/05.multi-turn-prompt/pom.xml | 4 ++-- samples/06.using-cards/pom.xml | 4 ++-- samples/07.using-adaptive-cards/pom.xml | 6 +++--- samples/08.suggested-actions/pom.xml | 2 +- samples/11.qnamaker/pom.xml | 4 ++-- samples/13.core-bot/pom.xml | 6 +++--- samples/14.nlp-with-dispatch/pom.xml | 6 +++--- samples/15.handling-attachments/pom.xml | 2 +- samples/16.proactive-messages/pom.xml | 2 +- samples/17.multilingual-bot/pom.xml | 2 +- samples/18.bot-authentication/pom.xml | 4 ++-- samples/19.custom-dialogs/pom.xml | 4 ++-- samples/21.corebot-app-insights/pom.xml | 8 ++++---- samples/23.facebook-events/pom.xml | 4 ++-- samples/24.bot-authentication-msgraph/pom.xml | 4 ++-- samples/25.message-reaction/pom.xml | 4 ++-- samples/40.timex-resolution/pom.xml | 2 +- samples/43.complex-dialog/pom.xml | 4 ++-- samples/44.prompt-users-for-input/pom.xml | 4 ++-- samples/45.state-management/pom.xml | 2 +- samples/46.teams-auth/pom.xml | 6 +++--- samples/47.inspection/pom.xml | 2 +- samples/49.qnamaker-all-features/pom.xml | 4 ++-- samples/50.teams-messaging-extensions-search/pom.xml | 4 ++-- samples/51.teams-messaging-extensions-action/pom.xml | 4 ++-- .../pom.xml | 4 ++-- .../53.teams-messaging-extensions-action-preview/pom.xml | 4 ++-- samples/54.teams-task-module/pom.xml | 4 ++-- samples/55.teams-link-unfurling/pom.xml | 4 ++-- samples/56.teams-file-upload/pom.xml | 4 ++-- samples/57.teams-conversation-bot/pom.xml | 4 ++-- samples/58.teams-start-new-thread-in-channel/pom.xml | 4 ++-- samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml | 4 ++-- .../80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml | 2 +- samples/81.skills-skilldialog/dialog-root-bot/pom.xml | 6 +++--- samples/81.skills-skilldialog/dialog-skill-bot/pom.xml | 6 +++--- samples/pom.xml | 2 +- samples/servlet-echo/pom.xml | 2 +- 52 files changed, 90 insertions(+), 90 deletions(-) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml index ad9649da6..d299aea02 100644 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml +++ b/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml @@ -81,18 +81,18 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT com.microsoft.bot bot-ai-luis-v3 - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/libraries/bot-ai-luis-v3/pom.xml b/libraries/bot-ai-luis-v3/pom.xml index 5ce30ee1c..a88fecebf 100644 --- a/libraries/bot-ai-luis-v3/pom.xml +++ b/libraries/bot-ai-luis-v3/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-ai-qna/pom.xml b/libraries/bot-ai-qna/pom.xml index 4b7ceaba6..a44b34365 100644 --- a/libraries/bot-ai-qna/pom.xml +++ b/libraries/bot-ai-qna/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-applicationinsights/pom.xml b/libraries/bot-applicationinsights/pom.xml index 649c864a2..60c199d16 100644 --- a/libraries/bot-applicationinsights/pom.xml +++ b/libraries/bot-applicationinsights/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-azure/pom.xml b/libraries/bot-azure/pom.xml index 9ef60875d..a7cdfeeb4 100644 --- a/libraries/bot-azure/pom.xml +++ b/libraries/bot-azure/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-builder/pom.xml b/libraries/bot-builder/pom.xml index c69a98262..7ffc14edb 100644 --- a/libraries/bot-builder/pom.xml +++ b/libraries/bot-builder/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-connector/pom.xml b/libraries/bot-connector/pom.xml index b0999bc41..6be249203 100644 --- a/libraries/bot-connector/pom.xml +++ b/libraries/bot-connector/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-dialogs/pom.xml b/libraries/bot-dialogs/pom.xml index 38aee0c8f..078b32912 100644 --- a/libraries/bot-dialogs/pom.xml +++ b/libraries/bot-dialogs/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-integration-core/pom.xml b/libraries/bot-integration-core/pom.xml index 0f7251890..c5805d47b 100644 --- a/libraries/bot-integration-core/pom.xml +++ b/libraries/bot-integration-core/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-integration-spring/pom.xml b/libraries/bot-integration-spring/pom.xml index 8b3a76590..3a89eab76 100644 --- a/libraries/bot-integration-spring/pom.xml +++ b/libraries/bot-integration-spring/pom.xml @@ -5,7 +5,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/libraries/bot-schema/pom.xml b/libraries/bot-schema/pom.xml index a1df159c8..64f52e46c 100644 --- a/libraries/bot-schema/pom.xml +++ b/libraries/bot-schema/pom.xml @@ -6,7 +6,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT ../../pom.xml diff --git a/pom.xml b/pom.xml index 6832defc3..af474fcee 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT pom Microsoft BotBuilder Java SDK Parent diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml index 4b7e651d9..3547ec69a 100644 --- a/samples/02.echo-bot/pom.xml +++ b/samples/02.echo-bot/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml index 7990cd3bb..95b42ef00 100644 --- a/samples/03.welcome-user/pom.xml +++ b/samples/03.welcome-user/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/05.multi-turn-prompt/pom.xml b/samples/05.multi-turn-prompt/pom.xml index cb24b9838..17a72ca91 100644 --- a/samples/05.multi-turn-prompt/pom.xml +++ b/samples/05.multi-turn-prompt/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/06.using-cards/pom.xml b/samples/06.using-cards/pom.xml index aa473979b..23a004d35 100644 --- a/samples/06.using-cards/pom.xml +++ b/samples/06.using-cards/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/07.using-adaptive-cards/pom.xml b/samples/07.using-adaptive-cards/pom.xml index ece6a3907..1de1703ee 100644 --- a/samples/07.using-adaptive-cards/pom.xml +++ b/samples/07.using-adaptive-cards/pom.xml @@ -81,19 +81,19 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-builder - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml index 7fedd3af9..943a8a74d 100644 --- a/samples/08.suggested-actions/pom.xml +++ b/samples/08.suggested-actions/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/11.qnamaker/pom.xml b/samples/11.qnamaker/pom.xml index fcad9860c..5b58dce77 100644 --- a/samples/11.qnamaker/pom.xml +++ b/samples/11.qnamaker/pom.xml @@ -66,7 +66,7 @@ com.microsoft.bot bot-ai-qna - 4.6.0-preview9 + 4.13.0-SNAPSHOT @@ -87,7 +87,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/13.core-bot/pom.xml b/samples/13.core-bot/pom.xml index a59ab13e3..9ea745588 100644 --- a/samples/13.core-bot/pom.xml +++ b/samples/13.core-bot/pom.xml @@ -81,18 +81,18 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT com.microsoft.bot bot-ai-luis-v3 - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/samples/14.nlp-with-dispatch/pom.xml b/samples/14.nlp-with-dispatch/pom.xml index 34e336096..ef763c809 100644 --- a/samples/14.nlp-with-dispatch/pom.xml +++ b/samples/14.nlp-with-dispatch/pom.xml @@ -81,18 +81,18 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-ai-luis-v3 - 4.6.0-preview9 + 4.13.0-SNAPSHOT com.microsoft.bot bot-ai-qna - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/samples/15.handling-attachments/pom.xml b/samples/15.handling-attachments/pom.xml index bce856172..01266ebe2 100644 --- a/samples/15.handling-attachments/pom.xml +++ b/samples/15.handling-attachments/pom.xml @@ -86,7 +86,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml index 82677cb16..274d081ac 100644 --- a/samples/16.proactive-messages/pom.xml +++ b/samples/16.proactive-messages/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/17.multilingual-bot/pom.xml b/samples/17.multilingual-bot/pom.xml index 4ad0080c2..183a19d9e 100644 --- a/samples/17.multilingual-bot/pom.xml +++ b/samples/17.multilingual-bot/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/18.bot-authentication/pom.xml b/samples/18.bot-authentication/pom.xml index 81f811205..b811dd8c3 100644 --- a/samples/18.bot-authentication/pom.xml +++ b/samples/18.bot-authentication/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/19.custom-dialogs/pom.xml b/samples/19.custom-dialogs/pom.xml index 0840bc88b..519a93bfe 100644 --- a/samples/19.custom-dialogs/pom.xml +++ b/samples/19.custom-dialogs/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/21.corebot-app-insights/pom.xml b/samples/21.corebot-app-insights/pom.xml index ea30a54e4..48e11bbec 100644 --- a/samples/21.corebot-app-insights/pom.xml +++ b/samples/21.corebot-app-insights/pom.xml @@ -81,23 +81,23 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT com.microsoft.bot bot-ai-luis-v3 - 4.6.0-preview9 + 4.13.0-SNAPSHOT com.microsoft.bot bot-applicationinsights - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/samples/23.facebook-events/pom.xml b/samples/23.facebook-events/pom.xml index c96875812..f37d7eb75 100644 --- a/samples/23.facebook-events/pom.xml +++ b/samples/23.facebook-events/pom.xml @@ -80,13 +80,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/24.bot-authentication-msgraph/pom.xml b/samples/24.bot-authentication-msgraph/pom.xml index d603f3041..2f4218c43 100644 --- a/samples/24.bot-authentication-msgraph/pom.xml +++ b/samples/24.bot-authentication-msgraph/pom.xml @@ -86,13 +86,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/25.message-reaction/pom.xml b/samples/25.message-reaction/pom.xml index e8ac589ed..439143ff1 100644 --- a/samples/25.message-reaction/pom.xml +++ b/samples/25.message-reaction/pom.xml @@ -62,7 +62,7 @@ junit-vintage-engine test - + org.slf4j slf4j-api @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/40.timex-resolution/pom.xml b/samples/40.timex-resolution/pom.xml index 9327e53d1..a74885529 100644 --- a/samples/40.timex-resolution/pom.xml +++ b/samples/40.timex-resolution/pom.xml @@ -48,7 +48,7 @@ com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/samples/43.complex-dialog/pom.xml b/samples/43.complex-dialog/pom.xml index b6fbb75af..cc14de7c0 100644 --- a/samples/43.complex-dialog/pom.xml +++ b/samples/43.complex-dialog/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/44.prompt-users-for-input/pom.xml b/samples/44.prompt-users-for-input/pom.xml index 393ec4f92..acb010479 100644 --- a/samples/44.prompt-users-for-input/pom.xml +++ b/samples/44.prompt-users-for-input/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/45.state-management/pom.xml b/samples/45.state-management/pom.xml index 348c0fdae..7bf85bb32 100644 --- a/samples/45.state-management/pom.xml +++ b/samples/45.state-management/pom.xml @@ -80,7 +80,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/46.teams-auth/pom.xml b/samples/46.teams-auth/pom.xml index bd19e33a9..3342672a7 100644 --- a/samples/46.teams-auth/pom.xml +++ b/samples/46.teams-auth/pom.xml @@ -81,19 +81,19 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/47.inspection/pom.xml b/samples/47.inspection/pom.xml index 23cac3e85..467c98e13 100644 --- a/samples/47.inspection/pom.xml +++ b/samples/47.inspection/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/49.qnamaker-all-features/pom.xml b/samples/49.qnamaker-all-features/pom.xml index d0045273f..2f4327fa1 100644 --- a/samples/49.qnamaker-all-features/pom.xml +++ b/samples/49.qnamaker-all-features/pom.xml @@ -66,7 +66,7 @@ com.microsoft.bot bot-ai-qna - 4.6.0-preview9 + 4.13.0-SNAPSHOT @@ -87,7 +87,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/50.teams-messaging-extensions-search/pom.xml b/samples/50.teams-messaging-extensions-search/pom.xml index 272e754dd..e5ab1f310 100644 --- a/samples/50.teams-messaging-extensions-search/pom.xml +++ b/samples/50.teams-messaging-extensions-search/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/51.teams-messaging-extensions-action/pom.xml b/samples/51.teams-messaging-extensions-action/pom.xml index c5d7005ed..85a64c1b4 100644 --- a/samples/51.teams-messaging-extensions-action/pom.xml +++ b/samples/51.teams-messaging-extensions-action/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml index 482605205..b7b5a4bba 100644 --- a/samples/52.teams-messaging-extensions-search-auth-config/pom.xml +++ b/samples/52.teams-messaging-extensions-search-auth-config/pom.xml @@ -80,13 +80,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/53.teams-messaging-extensions-action-preview/pom.xml b/samples/53.teams-messaging-extensions-action-preview/pom.xml index 02715ee11..3d0870081 100644 --- a/samples/53.teams-messaging-extensions-action-preview/pom.xml +++ b/samples/53.teams-messaging-extensions-action-preview/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/54.teams-task-module/pom.xml b/samples/54.teams-task-module/pom.xml index f98df3f05..92d9a9352 100644 --- a/samples/54.teams-task-module/pom.xml +++ b/samples/54.teams-task-module/pom.xml @@ -75,12 +75,12 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/55.teams-link-unfurling/pom.xml b/samples/55.teams-link-unfurling/pom.xml index 18f777889..97795b010 100644 --- a/samples/55.teams-link-unfurling/pom.xml +++ b/samples/55.teams-link-unfurling/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/56.teams-file-upload/pom.xml b/samples/56.teams-file-upload/pom.xml index 33b064d17..0983bd737 100644 --- a/samples/56.teams-file-upload/pom.xml +++ b/samples/56.teams-file-upload/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/57.teams-conversation-bot/pom.xml b/samples/57.teams-conversation-bot/pom.xml index 97c27515f..9a54543f5 100644 --- a/samples/57.teams-conversation-bot/pom.xml +++ b/samples/57.teams-conversation-bot/pom.xml @@ -75,13 +75,13 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/58.teams-start-new-thread-in-channel/pom.xml b/samples/58.teams-start-new-thread-in-channel/pom.xml index 190413083..a31b95a10 100644 --- a/samples/58.teams-start-new-thread-in-channel/pom.xml +++ b/samples/58.teams-start-new-thread-in-channel/pom.xml @@ -75,12 +75,12 @@ org.apache.logging.log4j log4j-core - 2.11.0 + 2.14.1 com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml index f0ca5da0e..7c2c2df6b 100644 --- a/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml +++ b/samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml @@ -81,13 +81,13 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-builder - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml index 16077c086..d11ca9470 100644 --- a/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml +++ b/samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml @@ -81,7 +81,7 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/81.skills-skilldialog/dialog-root-bot/pom.xml b/samples/81.skills-skilldialog/dialog-root-bot/pom.xml index 071b39b52..d33861b31 100644 --- a/samples/81.skills-skilldialog/dialog-root-bot/pom.xml +++ b/samples/81.skills-skilldialog/dialog-root-bot/pom.xml @@ -81,19 +81,19 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-builder - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile diff --git a/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml b/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml index 06077f356..61730c481 100644 --- a/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml +++ b/samples/81.skills-skilldialog/dialog-skill-bot/pom.xml @@ -81,19 +81,19 @@ com.microsoft.bot bot-integration-spring - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-dialogs - 4.6.0-preview9 + 4.13.0-SNAPSHOT compile com.microsoft.bot bot-ai-luis-v3 - 4.6.0-preview9 + 4.13.0-SNAPSHOT diff --git a/samples/pom.xml b/samples/pom.xml index c8f23ac82..f343e10eb 100644 --- a/samples/pom.xml +++ b/samples/pom.xml @@ -4,7 +4,7 @@ com.microsoft.bot bot-java - 4.6.0-preview9 + 4.13.0-SNAPSHOT pom Microsoft BotBuilder Java SDK Parent diff --git a/samples/servlet-echo/pom.xml b/samples/servlet-echo/pom.xml index 62ffadb67..de3255304 100644 --- a/samples/servlet-echo/pom.xml +++ b/samples/servlet-echo/pom.xml @@ -87,7 +87,7 @@ com.microsoft.bot bot-integration-core - 4.6.0-preview9 + 4.13.0-SNAPSHOT From 802f0c6af6437cb64825a54a637b86ce95f1a369 Mon Sep 17 00:00:00 2001 From: Lee Parrish <30470292+LeeParrishMSFT@users.noreply.github.com> Date: Mon, 12 Apr 2021 13:28:07 -0500 Subject: [PATCH 141/221] Remove samples and generator (#1150) --- .../generator-botbuilder-java/.gitignore | 2 - .../generator-botbuilder-java/LICENSE.md | 21 - Generator/generator-botbuilder-java/README.md | 126 - .../generators/app/index.js | 276 -- .../app/templates/core/project/README-LUIS.md | 216 -- .../app/templates/core/project/README.md | 67 - .../cognitiveModels/FlightBooking.json | 339 -- .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../app/templates/core/project/pom.xml | 248 -- .../src/main/resources/application.properties | 6 - .../src/main/resources/cards/welcomeCard.json | 46 - .../project/src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../project/src/main/webapp/WEB-INF/web.xml | 12 - .../core/project/src/main/webapp/index.html | 417 --- .../core/src/main/java/Application.java | 79 - .../core/src/main/java/BookingDetails.java | 69 - .../core/src/main/java/BookingDialog.java | 135 - .../src/main/java/CancelAndHelpDialog.java | 80 - .../src/main/java/DateResolverDialog.java | 109 - .../src/main/java/DialogAndWelcomeBot.java | 98 - .../core/src/main/java/DialogBot.java | 127 - .../main/java/FlightBookingRecognizer.java | 153 - .../core/src/main/java/MainDialog.java | 232 -- .../core/src/main/java/package-info.java | 8 - .../core/src/test/java/ApplicationTest.java | 19 - .../app/templates/echo/project/README.md | 85 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../app/templates/echo/project/pom.xml | 253 -- .../src/main/resources/application.properties | 3 - .../project/src/main/resources/log4j2.json | 21 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../project/src/main/webapp/WEB-INF/web.xml | 12 - .../echo/project/src/main/webapp/index.html | 418 --- .../echo/src/main/java/Application.java | 65 - .../templates/echo/src/main/java/EchoBot.java | 46 - .../echo/src/test/java/ApplicationTests.java | 19 - .../app/templates/empty/project/README.md | 85 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../app/templates/empty/project/pom.xml | 253 -- .../src/main/resources/application.properties | 3 - .../project/src/main/resources/log4j2.json | 21 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../project/src/main/webapp/WEB-INF/web.xml | 12 - .../empty/project/src/main/webapp/index.html | 418 --- .../empty/src/main/java/Application.java | 65 - .../empty/src/main/java/EmptyBot.java | 26 - .../empty/src/test/java/ApplicationTests.java | 19 - .../package-lock.json | 3170 ----------------- .../generator-botbuilder-java/package.json | 32 - samples/02.echo-bot/LICENSE | 21 - samples/02.echo-bot/README.md | 85 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/02.echo-bot/pom.xml | 238 -- .../bot/sample/echo/Application.java | 65 - .../microsoft/bot/sample/echo/EchoBot.java | 46 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../02.echo-bot/src/main/webapp/index.html | 418 --- .../bot/sample/echo/ApplicationTest.java | 19 - samples/03.welcome-user/LICENSE | 21 - samples/03.welcome-user/README.md | 85 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/03.welcome-user/pom.xml | 238 -- .../bot/sample/welcomeuser/Application.java | 65 - .../sample/welcomeuser/WelcomeUserBot.java | 221 -- .../sample/welcomeuser/WelcomeUserState.java | 28 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/welcomeuser/ApplicationTest.java | 19 - samples/05.multi-turn-prompt/LICENSE | 21 - samples/05.multi-turn-prompt/README.md | 90 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/05.multi-turn-prompt/pom.xml | 244 -- .../sample/multiturnprompt/Application.java | 86 - .../bot/sample/multiturnprompt/DialogBot.java | 53 - .../sample/multiturnprompt/UserProfile.java | 13 - .../multiturnprompt/UserProfileDialog.java | 222 -- .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../multiturnprompt/ApplicationTest.java | 19 - samples/06.using-cards/LICENSE | 21 - samples/06.using-cards/README.md | 102 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/06.using-cards/pom.xml | 244 -- .../bot/sample/usingcards/Application.java | 86 - .../bot/sample/usingcards/Cards.java | 153 - .../bot/sample/usingcards/DialogBot.java | 53 - .../bot/sample/usingcards/MainDialog.java | 135 - .../bot/sample/usingcards/RichCardsBot.java | 47 - .../src/main/resources/adaptiveCard.json | 204 -- .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../06.using-cards/src/main/webapp/index.html | 418 --- .../sample/usingcards/ApplicationTest.java | 19 - samples/07.using-adaptive-cards/LICENSE | 21 - samples/07.using-adaptive-cards/README.md | 59 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/07.using-adaptive-cards/pom.xml | 249 -- .../usingadaptivecards/Application.java | 67 - .../bots/AdaptiveCardsBot.java | 98 - .../main/resources/FlightItineraryCard.json | 204 -- .../src/main/resources/ImageGalleryCard.json | 60 - .../src/main/resources/LargeWeatherCard.json | 205 -- .../src/main/resources/RestaurantCard.json | 60 - .../src/main/resources/SolitaireCard.json | 39 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../usingadaptivecards/ApplicationTest.java | 19 - samples/08.suggested-actions/LICENSE | 21 - samples/08.suggested-actions/README.md | 85 - samples/08.suggested-actions/bin/LICENSE | 21 - samples/08.suggested-actions/bin/README.md | 90 - .../new-rg-parameters.json | 42 - .../preexisting-rg-parameters.json | 39 - .../template-with-new-rg.json | 191 - .../template-with-preexisting-rg.json | 158 - samples/08.suggested-actions/bin/pom.xml | 191 - .../src/main/resources/application.properties | 2 - .../bin/src/main/webapp/META-INF/MANIFEST.MF | 3 - .../bin/src/main/webapp/WEB-INF/web.xml | 12 - .../bin/src/main/webapp/index.html | 418 --- .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/08.suggested-actions/pom.xml | 238 -- .../sample/suggestedactions/Application.java | 64 - .../suggestedactions/SuggestedActionsBot.java | 124 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../suggestedactions/ApplicationTest.java | 19 - samples/11.qnamaker/LICENSE | 21 - samples/11.qnamaker/README.md | 64 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/11.qnamaker/pom.xml | 244 -- .../bot/sample/qnamaker/Application.java | 66 - .../microsoft/bot/sample/qnamaker/QnABot.java | 50 - .../src/main/resources/application.properties | 6 - .../src/main/resources/log4j2.json | 18 - .../src/main/resources/smartLightFAQ.tsv | 15 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../11.qnamaker/src/main/webapp/index.html | 417 --- .../bot/sample/qnamaker/ApplicationTest.java | 19 - samples/13.core-bot/LICENSE | 21 - samples/13.core-bot/README-LUIS.md | 216 -- samples/13.core-bot/README.md | 67 - .../cognitiveModels/FlightBooking.json | 339 -- .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/13.core-bot/pom.xml | 248 -- .../bot/sample/core/Application.java | 79 - .../bot/sample/core/BookingDetails.java | 69 - .../bot/sample/core/BookingDialog.java | 135 - .../bot/sample/core/CancelAndHelpDialog.java | 80 - .../bot/sample/core/DateResolverDialog.java | 109 - .../bot/sample/core/DialogAndWelcomeBot.java | 98 - .../microsoft/bot/sample/core/DialogBot.java | 127 - .../sample/core/FlightBookingRecognizer.java | 153 - .../microsoft/bot/sample/core/MainDialog.java | 232 -- .../bot/sample/core/package-info.java | 8 - .../src/main/resources/application.properties | 6 - .../src/main/resources/cards/welcomeCard.json | 46 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../13.core-bot/src/main/webapp/index.html | 417 --- .../bot/sample/core/ApplicationTest.java | 19 - .../.vscode/settings.json | 3 - samples/14.nlp-with-dispatch/LICENSE | 21 - samples/14.nlp-with-dispatch/README.md | 103 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/14.nlp-with-dispatch/pom.xml | 248 -- .../sample/nlpwithdispatch/Application.java | 73 - .../sample/nlpwithdispatch/BotServices.java | 12 - .../nlpwithdispatch/BotServicesImpl.java | 61 - .../bot/sample/nlpwithdispatch/Intent.java | 46 - .../nlpwithdispatch/PredictionResult.java | 27 - .../nlpwithdispatch/bots/DispatchBot.java | 229 -- .../src/main/resources/HomeAutomation.json | 605 ---- .../src/main/resources/QnAMaker.tsv | 11 - .../src/main/resources/Weather.json | 315 -- .../src/main/resources/application.properties | 11 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../nlpwithdispatch/ApplicationTest.java | 19 - samples/15.handling-attachments/LICENSE | 21 - samples/15.handling-attachments/README.md | 97 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/15.handling-attachments/pom.xml | 243 -- .../bot/sample/attachments/Application.java | 65 - .../sample/attachments/AttachmentsBot.java | 254 -- .../src/main/resources/application.properties | 3 - .../main/resources/architecture-resize.png | Bin 137666 -> 0 bytes .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/attachments/ApplicationTest.java | 19 - samples/16.proactive-messages/LICENSE | 21 - samples/16.proactive-messages/README.md | 94 - samples/16.proactive-messages/bin/LICENSE | 21 - samples/16.proactive-messages/bin/README.md | 90 - .../new-rg-parameters.json | 42 - .../preexisting-rg-parameters.json | 39 - .../template-with-new-rg.json | 191 - .../template-with-preexisting-rg.json | 158 - samples/16.proactive-messages/bin/pom.xml | 191 - .../src/main/resources/application.properties | 2 - .../bin/src/main/webapp/META-INF/MANIFEST.MF | 3 - .../bin/src/main/webapp/WEB-INF/web.xml | 12 - .../bin/src/main/webapp/index.html | 418 --- .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/16.proactive-messages/pom.xml | 238 -- .../bot/sample/proactive/Application.java | 77 - .../proactive/ConversationReferences.java | 17 - .../sample/proactive/NotifyController.java | 71 - .../bot/sample/proactive/ProactiveBot.java | 82 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../bot/sample/proactive/ApplicationTest.java | 19 - samples/17.multilingual-bot/LICENSE | 21 - samples/17.multilingual-bot/README.md | 126 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/17.multilingual-bot/pom.xml | 238 -- .../bot/sample/multilingual/Application.java | 99 - .../sample/multilingual/MultiLingualBot.java | 179 - .../translation/MicrosoftTranslator.java | 96 - .../translation/TranslationMiddleware.java | 126 - .../translation/TranslationSettings.java | 11 - .../translation/model/TranslatorResponse.java | 34 - .../translation/model/TranslatorResult.java | 49 - .../src/main/resources/application.properties | 4 - .../src/main/resources/cards/welcomeCard.json | 46 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/multilingual/ApplicationTest.java | 19 - samples/18.bot-authentication/LICENSE | 21 - samples/18.bot-authentication/README.md | 94 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/18.bot-authentication/pom.xml | 244 -- .../sample/authentication/Application.java | 72 - .../bot/sample/authentication/AuthBot.java | 49 - .../bot/sample/authentication/DialogBot.java | 54 - .../sample/authentication/LogoutDialog.java | 73 - .../bot/sample/authentication/MainDialog.java | 103 - .../src/main/resources/application.properties | 4 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../authentication/ApplicationTest.java | 19 - samples/19.custom-dialogs/LICENSE | 21 - samples/19.custom-dialogs/README.md | 102 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/19.custom-dialogs/pom.xml | 244 -- .../bot/sample/customdialogs/Application.java | 86 - .../bot/sample/customdialogs/DialogBot.java | 53 - .../bot/sample/customdialogs/RootDialog.java | 132 - .../bot/sample/customdialogs/SlotDetails.java | 75 - .../customdialogs/SlotFillingDialog.java | 170 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/customdialogs/ApplicationTest.java | 19 - samples/21.corebot-app-insights/LICENSE | 21 - .../21.corebot-app-insights/README-LUIS.md | 216 -- samples/21.corebot-app-insights/README.md | 70 - .../cognitiveModels/flightbooking.json | 339 -- .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/21.corebot-app-insights/pom.xml | 253 -- .../app/insights/AdapterWithErrorHandler.java | 114 - .../corebot/app/insights/Application.java | 118 - .../corebot/app/insights/BookingDetails.java | 67 - .../corebot/app/insights/BookingDialog.java | 129 - .../app/insights/CancelAndHelpDialog.java | 80 - .../app/insights/DateResolverDialog.java | 107 - .../app/insights/DialogAndWelcomeBot.java | 98 - .../corebot/app/insights/DialogBot.java | 127 - .../app/insights/FlightBookingRecognizer.java | 156 - .../corebot/app/insights/MainDialog.java | 238 -- .../corebot/app/insights/package-info.java | 8 - .../src/main/resources/application.properties | 8 - .../src/main/resources/cards/welcomeCard.json | 46 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 417 --- .../corebot/app/insights/ApplicationTest.java | 19 - samples/23.facebook-events/LICENSE | 21 - samples/23.facebook-events/README.md | 91 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/23.facebook-events/pom.xml | 243 -- .../sample/facebookevents/Application.java | 67 - .../facebookevents/bot/FacebookBot.java | 253 -- .../facebookmodel/FacebookMessage.java | 97 - .../facebookmodel/FacebookOptin.java | 57 - .../facebookmodel/FacebookPayload.java | 114 - .../facebookmodel/FacebookPostback.java | 61 - .../facebookmodel/FacebookQuickReply.java | 36 - .../facebookmodel/FacebookRecipient.java | 33 - .../facebookmodel/FacebookSender.java | 33 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../facebookevents/ApplicationTest.java | 19 - samples/24.bot-authentication-msgraph/LICENSE | 21 - .../24.bot-authentication-msgraph/README.md | 118 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/24.bot-authentication-msgraph/pom.xml | 249 -- .../sample/authentication/Application.java | 71 - .../bot/sample/authentication/AuthBot.java | 49 - .../bot/sample/authentication/DialogBot.java | 54 - .../sample/authentication/LogoutDialog.java | 73 - .../bot/sample/authentication/MainDialog.java | 130 - .../sample/authentication/OAuthHelpers.java | 37 - .../authentication/SimpleGraphClient.java | 43 - .../src/main/resources/application.properties | 4 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../authentication/ApplicationTest.java | 19 - samples/25.message-reaction/README.md | 77 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/25.message-reaction/pom.xml | 238 -- .../sample/messagereaction/ActivityLog.java | 64 - .../sample/messagereaction/Application.java | 72 - .../messagereaction/MessageReactionBot.java | 110 - .../sample/messagereaction/package-info.java | 8 - .../src/main/resources/application.properties | 3 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 293 -- .../messagereaction/ApplicationTest.java | 20 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 43 - samples/40.timex-resolution/LICENSE | 21 - samples/40.timex-resolution/README.md | 357 -- samples/40.timex-resolution/pom.xml | 204 -- .../sample/timex/resolution/Ambiguity.java | 123 - .../sample/timex/resolution/Application.java | 32 - .../sample/timex/resolution/Constraints.java | 43 - .../timex/resolution/LanguageGeneration.java | 40 - .../bot/sample/timex/resolution/Parsing.java | 49 - .../bot/sample/timex/resolution/Ranges.java | 76 - .../sample/timex/resolution/Resolutions.java | 47 - .../sample/timex/resolution/package-info.java | 8 - samples/43.complex-dialog/LICENSE | 21 - samples/43.complex-dialog/README.md | 90 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/43.complex-dialog/pom.xml | 244 -- .../bot/sample/complexdialog/Application.java | 86 - .../complexdialog/DialogAndWelcome.java | 45 - .../bot/sample/complexdialog/DialogBot.java | 53 - .../bot/sample/complexdialog/MainDialog.java | 55 - .../complexdialog/ReviewSelectionDialog.java | 99 - .../sample/complexdialog/TopLevelDialog.java | 95 - .../bot/sample/complexdialog/UserProfile.java | 18 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/complexdialog/ApplicationTest.java | 19 - samples/44.prompt-users-for-input/LICENSE | 21 - samples/44.prompt-users-for-input/README.md | 90 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/44.prompt-users-for-input/pom.xml | 244 -- .../promptusersforinput/Application.java | 69 - .../promptusersforinput/ConversationFlow.java | 32 - .../promptusersforinput/CustomPromptBot.java | 222 -- .../promptusersforinput/UserProfile.java | 10 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../promptusersforinput/ApplicationTest.java | 19 - samples/45.state-management/LICENSE | 21 - samples/45.state-management/README.md | 88 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/45.state-management/pom.xml | 237 -- .../sample/statemanagement/Application.java | 69 - .../statemanagement/ConversationData.java | 46 - .../statemanagement/StateManagementBot.java | 166 - .../sample/statemanagement/UserProfile.java | 28 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../statemanagement/ApplicationTest.java | 19 - samples/46.teams-auth/LICENSE | 21 - samples/46.teams-auth/README.md | 78 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/46.teams-auth/pom.xml | 250 -- .../bot/sample/teamsauth/Application.java | 86 - .../bot/sample/teamsauth/DialogBot.java | 53 - .../bot/sample/teamsauth/LogoutDialog.java | 69 - .../bot/sample/teamsauth/MainDialog.java | 109 - .../sample/teamsauth/SimpleGraphClient.java | 46 - .../bot/sample/teamsauth/TeamsBot.java | 53 - .../src/main/resources/application.properties | 4 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../46.teams-auth/src/main/webapp/index.html | 418 --- .../bot/sample/teamsauth/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 44 - samples/47.inspection/LICENSE | 21 - samples/47.inspection/README.md | 100 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/47.inspection/pom.xml | 238 -- .../bot/sample/inspection/Application.java | 98 - .../bot/sample/inspection/CustomState.java | 32 - .../bot/sample/inspection/EchoBot.java | 97 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../47.inspection/src/main/webapp/index.html | 418 --- .../sample/inspection/ApplicationTest.java | 19 - samples/49.qnamaker-all-features/LICENSE | 21 - samples/49.qnamaker-all-features/README.md | 126 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/49.qnamaker-all-features/pom.xml | 244 -- .../qnamaker/all/features/Application.java | 78 - .../qnamaker/all/features/BotServices.java | 19 - .../all/features/BotServicesImpl.java | 81 - .../sample/qnamaker/all/features/QnABot.java | 79 - .../all/features/QnAMakerBaseDialog.java | 85 - .../qnamaker/all/features/RootDialog.java | 57 - .../qnamaker/all/features/package-info.java | 8 - .../src/main/resources/application.properties | 7 - .../src/main/resources/log4j2.json | 18 - .../src/main/resources/smartLightFAQ.tsv | 23 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 420 --- .../all/features/ApplicationTest.java | 19 - .../LICENSE | 21 - .../README.md | 69 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../pom.xml | 248 -- .../bot/sample/teamssearch/Application.java | 64 - .../TeamsMessagingExtensionsSearchBot.java | 156 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/teamssearch/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 49 - .../LICENSE | 21 - .../README.md | 66 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../pom.xml | 238 -- .../bot/sample/teamsaction/Application.java | 65 - .../TeamsMessagingExtensionsActionBot.java | 108 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/teamsaction/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 78 - .../LICENSE | 21 - .../README.md | 66 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../pom.xml | 243 -- .../sample/teamssearchauth/Application.java | 69 - .../teamssearchauth/SimpleGraphClient.java | 52 - ...essagingExtensionsSearchAuthConfigBot.java | 386 -- .../src/main/resources/adaptiveCard.json | 18 - .../src/main/resources/application.properties | 5 - .../src/main/resources/log4j2.json | 18 - .../main/resources/public/searchSettings.html | 54 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../teamssearchauth/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 81 - .../LICENSE | 21 - .../README.md | 61 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../pom.xml | 238 -- .../teamsactionpreview/Application.java | 64 - ...msMessagingExtensionsActionPreviewBot.java | 239 -- .../models/AdaptiveCard.java | 40 - .../teamsactionpreview/models/Body.java | 136 - .../teamsactionpreview/models/Choice.java | 51 - .../main/resources/adaptiveCardEditor.json | 69 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/resources/submitCard.json | 50 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../teamsactionpreview/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 67 - samples/54.teams-task-module/LICENSE | 21 - samples/54.teams-task-module/README.md | 62 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/54.teams-task-module/pom.xml | 242 -- .../sample/teamstaskmodule/Application.java | 65 - .../teamstaskmodule/TeamsTaskModuleBot.java | 200 -- .../models/AdaptiveCardTaskFetchValue.java | 49 - .../models/CardTaskFetchValue.java | 30 - .../teamstaskmodule/models/TaskModuleIds.java | 14 - .../models/TaskModuleResponseFactory.java | 31 - .../models/TaskModuleUIConstants.java | 34 - .../teamstaskmodule/models/UISettings.java | 66 - .../src/main/resources/adaptiveTemplate.json | 14 - .../src/main/resources/adaptivecard.json | 25 - .../src/main/resources/application.properties | 4 - .../src/main/resources/log4j2.json | 18 - .../src/main/resources/static/CustomForm.html | 59 - .../src/main/resources/static/YouTube.html | 39 - .../src/main/resources/static/css/Custom.css | 8 - .../src/main/resources/static/css/Site.css | 8 - .../main/resources/static/css/msteams-16.css | 1272 ------- .../teamstaskmodule/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 43 - samples/55.teams-link-unfurling/LICENSE | 21 - samples/55.teams-link-unfurling/README.md | 60 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/55.teams-link-unfurling/pom.xml | 238 -- .../bot/sample/teamsunfurl/Application.java | 65 - .../sample/teamsunfurl/LinkUnfurlingBot.java | 84 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/teamsunfurl/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 58 - samples/56.teams-file-upload/LICENSE | 21 - samples/56.teams-file-upload/README.md | 64 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../56.teams-file-upload/files/teams-logo.png | Bin 6412 -> 0 bytes samples/56.teams-file-upload/pom.xml | 238 -- .../sample/teamsfileupload/Application.java | 65 - .../teamsfileupload/TeamsFileUploadBot.java | 255 -- .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../teamsfileupload/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 38 - samples/57.teams-conversation-bot/LICENSE | 21 - samples/57.teams-conversation-bot/README.md | 76 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- samples/57.teams-conversation-bot/pom.xml | 238 -- .../sample/teamsconversation/Application.java | 64 - .../TeamsConversationBot.java | 215 -- .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../teamsconversation/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 66 - .../LICENSE | 21 - .../README.md | 61 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../pom.xml | 242 -- .../teamsstartnewthread/Application.java | 65 - .../TeamsStartNewThreadBot.java | 80 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../teamsstartnewthread/ApplicationTest.java | 19 - .../teamsAppManifest/icon-color.png | Bin 1229 -> 0 bytes .../teamsAppManifest/icon-outline.png | Bin 383 -> 0 bytes .../teamsAppManifest/manifest.json | 41 - .../DialogRootBot/LICENSE | 21 - .../DialogRootBot/README.md | 3 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../DialogRootBot/pom.xml | 244 -- .../bot/sample/simplerootbot/Application.java | 133 - .../bot/sample/simplerootbot/RootBot.java | 191 - .../SkillAdapterWithErrorHandler.java | 129 - .../simplerootbot/SkillsConfiguration.java | 77 - .../AllowedSkillsClaimsValidator.java | 58 - .../controller/SkillController.java | 16 - .../src/main/resources/application.properties | 8 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../DialogRootBot/src/main/webapp/index.html | 418 --- .../sample/simplerootbot/ApplicationTest.java | 19 - .../DialogSkillBot/LICENSE | 21 - .../DialogSkillBot/README.md | 3 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../DialogSkillBot/pom.xml | 238 -- .../bot/sample/echoskillbot/Application.java | 80 - .../bot/sample/echoskillbot/EchoBot.java | 53 - .../SkillAdapterWithErrorHandler.java | 82 - .../src/main/resources/application.properties | 9 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../DialogSkillBot/src/main/webapp/index.html | 418 --- .../manifest/echoskillbot-manifest-1.0.json | 25 - .../sample/echoskillbot/ApplicationTest.java | 19 - samples/80.skills-simple-bot-to-bot/README.md | 59 - samples/81.skills-skilldialog/README.md | 97 - .../dialog-root-bot/.vscode/settings.json | 3 - .../dialog-root-bot/LICENSE | 21 - .../dialog-root-bot/README.md | 3 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../dialog-root-bot/pom.xml | 249 -- .../AdapterWithErrorHandler.java | 137 - .../sample/dialogrootbot/AdaptiveCard.java | 83 - .../bot/sample/dialogrootbot/Application.java | 159 - .../bot/sample/dialogrootbot/Body.java | 135 - .../sample/dialogrootbot/Bots/RootBot.java | 96 - .../dialogrootbot/SkillsConfiguration.java | 77 - .../AllowedSkillsClaimsValidator.java | 58 - .../controller/SkillController.java | 16 - .../dialogrootbot/dialogs/MainDialog.java | 365 -- .../middleware/ConsoleLogger.java | 11 - .../dialogrootbot/middleware/Logger.java | 11 - .../middleware/LoggerMiddleware.java | 70 - .../src/main/resources/application.properties | 8 - .../src/main/resources/log4j2.json | 18 - .../src/main/resources/welcomeCard.json | 29 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../sample/dialogrootbot/ApplicationTest.java | 19 - .../dialog-skill-bot/.vscode/settings.json | 3 - .../dialog-skill-bot/LICENSE | 21 - .../dialog-skill-bot/README.md | 3 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg.json | 259 -- .../dialog-skill-bot/pom.xml | 248 -- .../sample/dialogskillbot/Application.java | 88 - .../SkillAdapterWithErrorHandler.java | 82 - .../sample/dialogskillbot/bots/SkillBot.java | 31 - .../cognitivemodels/flightbooking.json | 339 -- .../dialogs/ActivityRouterDialog.java | 232 -- .../dialogs/BookingDetails.java | 66 - .../dialogskillbot/dialogs/BookingDialog.java | 112 - .../dialogs/CancelAndHelpDialog.java | 62 - .../dialogs/DateResolverDialog.java | 100 - .../dialogs/DialogSkillBotRecognizer.java | 54 - .../dialogskillbot/dialogs/Location.java | 64 - .../src/main/resources/application.properties | 13 - .../src/main/resources/log4j2.json | 18 - .../src/main/webapp/META-INF/MANIFEST.MF | 3 - .../src/main/webapp/WEB-INF/web.xml | 12 - .../src/main/webapp/index.html | 418 --- .../manifest/echoskillbot-manifest-1.0.json | 25 - .../dialogskillbot/ApplicationTest.java | 19 - samples/README.md | 14 - samples/pom.xml | 673 ---- samples/servlet-echo/LICENSE | 21 - samples/servlet-echo/README.md | 63 - .../template-with-new-rg-gov.json | 193 - .../template-with-new-rg.json | 291 -- .../template-with-preexisting-rg-gov.json | 158 - .../template-with-preexisting-rg.json | 259 -- samples/servlet-echo/pom.xml | 234 -- .../bot/sample/servlet/BotController.java | 23 - .../bot/sample/servlet/ControllerBase.java | 93 - .../microsoft/bot/sample/servlet/EchoBot.java | 50 - .../servlet/ServletWithBotConfiguration.java | 137 - .../src/main/resources/application.properties | 3 - .../src/main/resources/log4j2.json | 18 - 755 files changed, 86671 deletions(-) delete mode 100644 Generator/generator-botbuilder-java/.gitignore delete mode 100644 Generator/generator-botbuilder-java/LICENSE.md delete mode 100644 Generator/generator-botbuilder-java/README.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/index.js delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTests.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java delete mode 100644 Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTests.java delete mode 100644 Generator/generator-botbuilder-java/package-lock.json delete mode 100644 Generator/generator-botbuilder-java/package.json delete mode 100644 samples/02.echo-bot/LICENSE delete mode 100644 samples/02.echo-bot/README.md delete mode 100644 samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/02.echo-bot/pom.xml delete mode 100644 samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java delete mode 100644 samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java delete mode 100644 samples/02.echo-bot/src/main/resources/application.properties delete mode 100644 samples/02.echo-bot/src/main/resources/log4j2.json delete mode 100644 samples/02.echo-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/02.echo-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/02.echo-bot/src/main/webapp/index.html delete mode 100644 samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java delete mode 100644 samples/03.welcome-user/LICENSE delete mode 100644 samples/03.welcome-user/README.md delete mode 100644 samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/03.welcome-user/pom.xml delete mode 100644 samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java delete mode 100644 samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java delete mode 100644 samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserState.java delete mode 100644 samples/03.welcome-user/src/main/resources/application.properties delete mode 100644 samples/03.welcome-user/src/main/resources/log4j2.json delete mode 100644 samples/03.welcome-user/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/03.welcome-user/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/03.welcome-user/src/main/webapp/index.html delete mode 100644 samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java delete mode 100644 samples/05.multi-turn-prompt/LICENSE delete mode 100644 samples/05.multi-turn-prompt/README.md delete mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/05.multi-turn-prompt/pom.xml delete mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java delete mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java delete mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java delete mode 100644 samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java delete mode 100644 samples/05.multi-turn-prompt/src/main/resources/application.properties delete mode 100644 samples/05.multi-turn-prompt/src/main/resources/log4j2.json delete mode 100644 samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/05.multi-turn-prompt/src/main/webapp/index.html delete mode 100644 samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java delete mode 100644 samples/06.using-cards/LICENSE delete mode 100644 samples/06.using-cards/README.md delete mode 100644 samples/06.using-cards/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/06.using-cards/pom.xml delete mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java delete mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java delete mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java delete mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java delete mode 100644 samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java delete mode 100644 samples/06.using-cards/src/main/resources/adaptiveCard.json delete mode 100644 samples/06.using-cards/src/main/resources/application.properties delete mode 100644 samples/06.using-cards/src/main/resources/log4j2.json delete mode 100644 samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/06.using-cards/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/06.using-cards/src/main/webapp/index.html delete mode 100644 samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java delete mode 100644 samples/07.using-adaptive-cards/LICENSE delete mode 100644 samples/07.using-adaptive-cards/README.md delete mode 100644 samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/07.using-adaptive-cards/pom.xml delete mode 100644 samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java delete mode 100644 samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/application.properties delete mode 100644 samples/07.using-adaptive-cards/src/main/resources/log4j2.json delete mode 100644 samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/07.using-adaptive-cards/src/main/webapp/index.html delete mode 100644 samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java delete mode 100644 samples/08.suggested-actions/LICENSE delete mode 100644 samples/08.suggested-actions/README.md delete mode 100644 samples/08.suggested-actions/bin/LICENSE delete mode 100644 samples/08.suggested-actions/bin/README.md delete mode 100644 samples/08.suggested-actions/bin/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/08.suggested-actions/bin/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/08.suggested-actions/bin/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/08.suggested-actions/bin/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/08.suggested-actions/bin/pom.xml delete mode 100644 samples/08.suggested-actions/bin/src/main/resources/application.properties delete mode 100644 samples/08.suggested-actions/bin/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/08.suggested-actions/bin/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/08.suggested-actions/bin/src/main/webapp/index.html delete mode 100644 samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/08.suggested-actions/pom.xml delete mode 100644 samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java delete mode 100644 samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java delete mode 100644 samples/08.suggested-actions/src/main/resources/application.properties delete mode 100644 samples/08.suggested-actions/src/main/resources/log4j2.json delete mode 100644 samples/08.suggested-actions/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/08.suggested-actions/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/08.suggested-actions/src/main/webapp/index.html delete mode 100644 samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java delete mode 100644 samples/11.qnamaker/LICENSE delete mode 100644 samples/11.qnamaker/README.md delete mode 100644 samples/11.qnamaker/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/11.qnamaker/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/11.qnamaker/pom.xml delete mode 100644 samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/Application.java delete mode 100644 samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java delete mode 100644 samples/11.qnamaker/src/main/resources/application.properties delete mode 100644 samples/11.qnamaker/src/main/resources/log4j2.json delete mode 100644 samples/11.qnamaker/src/main/resources/smartLightFAQ.tsv delete mode 100644 samples/11.qnamaker/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/11.qnamaker/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/11.qnamaker/src/main/webapp/index.html delete mode 100644 samples/11.qnamaker/src/test/java/com/microsoft/bot/sample/qnamaker/ApplicationTest.java delete mode 100644 samples/13.core-bot/LICENSE delete mode 100644 samples/13.core-bot/README-LUIS.md delete mode 100644 samples/13.core-bot/README.md delete mode 100644 samples/13.core-bot/cognitiveModels/FlightBooking.json delete mode 100644 samples/13.core-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/13.core-bot/pom.xml delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java delete mode 100644 samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java delete mode 100644 samples/13.core-bot/src/main/resources/application.properties delete mode 100644 samples/13.core-bot/src/main/resources/cards/welcomeCard.json delete mode 100644 samples/13.core-bot/src/main/resources/log4j2.json delete mode 100644 samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/13.core-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/13.core-bot/src/main/webapp/index.html delete mode 100644 samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java delete mode 100644 samples/14.nlp-with-dispatch/.vscode/settings.json delete mode 100644 samples/14.nlp-with-dispatch/LICENSE delete mode 100644 samples/14.nlp-with-dispatch/README.md delete mode 100644 samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/14.nlp-with-dispatch/pom.xml delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java delete mode 100644 samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json delete mode 100644 samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv delete mode 100644 samples/14.nlp-with-dispatch/src/main/resources/Weather.json delete mode 100644 samples/14.nlp-with-dispatch/src/main/resources/application.properties delete mode 100644 samples/14.nlp-with-dispatch/src/main/resources/log4j2.json delete mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/14.nlp-with-dispatch/src/main/webapp/index.html delete mode 100644 samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java delete mode 100644 samples/15.handling-attachments/LICENSE delete mode 100644 samples/15.handling-attachments/README.md delete mode 100644 samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/15.handling-attachments/pom.xml delete mode 100644 samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java delete mode 100644 samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java delete mode 100644 samples/15.handling-attachments/src/main/resources/application.properties delete mode 100644 samples/15.handling-attachments/src/main/resources/architecture-resize.png delete mode 100644 samples/15.handling-attachments/src/main/resources/log4j2.json delete mode 100644 samples/15.handling-attachments/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/15.handling-attachments/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/15.handling-attachments/src/main/webapp/index.html delete mode 100644 samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java delete mode 100644 samples/16.proactive-messages/LICENSE delete mode 100644 samples/16.proactive-messages/README.md delete mode 100644 samples/16.proactive-messages/bin/LICENSE delete mode 100644 samples/16.proactive-messages/bin/README.md delete mode 100644 samples/16.proactive-messages/bin/deploymentTemplates/new-rg-parameters.json delete mode 100644 samples/16.proactive-messages/bin/deploymentTemplates/preexisting-rg-parameters.json delete mode 100644 samples/16.proactive-messages/bin/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/16.proactive-messages/bin/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/16.proactive-messages/bin/pom.xml delete mode 100644 samples/16.proactive-messages/bin/src/main/resources/application.properties delete mode 100644 samples/16.proactive-messages/bin/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/16.proactive-messages/bin/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/16.proactive-messages/bin/src/main/webapp/index.html delete mode 100644 samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/16.proactive-messages/pom.xml delete mode 100644 samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java delete mode 100644 samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ConversationReferences.java delete mode 100644 samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/NotifyController.java delete mode 100644 samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java delete mode 100644 samples/16.proactive-messages/src/main/resources/application.properties delete mode 100644 samples/16.proactive-messages/src/main/resources/log4j2.json delete mode 100644 samples/16.proactive-messages/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/16.proactive-messages/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/16.proactive-messages/src/main/webapp/index.html delete mode 100644 samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java delete mode 100644 samples/17.multilingual-bot/LICENSE delete mode 100644 samples/17.multilingual-bot/README.md delete mode 100644 samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/17.multilingual-bot/pom.xml delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java delete mode 100644 samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java delete mode 100644 samples/17.multilingual-bot/src/main/resources/application.properties delete mode 100644 samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json delete mode 100644 samples/17.multilingual-bot/src/main/resources/log4j2.json delete mode 100644 samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/17.multilingual-bot/src/main/webapp/index.html delete mode 100644 samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java delete mode 100644 samples/18.bot-authentication/LICENSE delete mode 100644 samples/18.bot-authentication/README.md delete mode 100644 samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/18.bot-authentication/pom.xml delete mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java delete mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java delete mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java delete mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java delete mode 100644 samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java delete mode 100644 samples/18.bot-authentication/src/main/resources/application.properties delete mode 100644 samples/18.bot-authentication/src/main/resources/log4j2.json delete mode 100644 samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/18.bot-authentication/src/main/webapp/index.html delete mode 100644 samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java delete mode 100644 samples/19.custom-dialogs/LICENSE delete mode 100644 samples/19.custom-dialogs/README.md delete mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/19.custom-dialogs/pom.xml delete mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java delete mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java delete mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java delete mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java delete mode 100644 samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java delete mode 100644 samples/19.custom-dialogs/src/main/resources/application.properties delete mode 100644 samples/19.custom-dialogs/src/main/resources/log4j2.json delete mode 100644 samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/19.custom-dialogs/src/main/webapp/index.html delete mode 100644 samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java delete mode 100644 samples/21.corebot-app-insights/LICENSE delete mode 100644 samples/21.corebot-app-insights/README-LUIS.md delete mode 100644 samples/21.corebot-app-insights/README.md delete mode 100644 samples/21.corebot-app-insights/cognitiveModels/flightbooking.json delete mode 100644 samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/21.corebot-app-insights/pom.xml delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java delete mode 100644 samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java delete mode 100644 samples/21.corebot-app-insights/src/main/resources/application.properties delete mode 100644 samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json delete mode 100644 samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/21.corebot-app-insights/src/main/webapp/index.html delete mode 100644 samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java delete mode 100644 samples/23.facebook-events/LICENSE delete mode 100644 samples/23.facebook-events/README.md delete mode 100644 samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/23.facebook-events/pom.xml delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java delete mode 100644 samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java delete mode 100644 samples/23.facebook-events/src/main/resources/application.properties delete mode 100644 samples/23.facebook-events/src/main/resources/log4j2.json delete mode 100644 samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/23.facebook-events/src/main/webapp/index.html delete mode 100644 samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java delete mode 100644 samples/24.bot-authentication-msgraph/LICENSE delete mode 100644 samples/24.bot-authentication-msgraph/README.md delete mode 100644 samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/24.bot-authentication-msgraph/pom.xml delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java delete mode 100644 samples/24.bot-authentication-msgraph/src/main/resources/application.properties delete mode 100644 samples/24.bot-authentication-msgraph/src/main/resources/log4j2.json delete mode 100644 samples/24.bot-authentication-msgraph/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/24.bot-authentication-msgraph/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/24.bot-authentication-msgraph/src/main/webapp/index.html delete mode 100644 samples/24.bot-authentication-msgraph/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java delete mode 100644 samples/25.message-reaction/README.md delete mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/25.message-reaction/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/25.message-reaction/pom.xml delete mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/ActivityLog.java delete mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/Application.java delete mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/MessageReactionBot.java delete mode 100644 samples/25.message-reaction/src/main/java/com/microsoft/bot/sample/messagereaction/package-info.java delete mode 100644 samples/25.message-reaction/src/main/resources/application.properties delete mode 100644 samples/25.message-reaction/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/25.message-reaction/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/25.message-reaction/src/main/webapp/index.html delete mode 100644 samples/25.message-reaction/src/test/java/com/microsoft/bot/sample/messagereaction/ApplicationTest.java delete mode 100644 samples/25.message-reaction/teamsAppManifest/icon-color.png delete mode 100644 samples/25.message-reaction/teamsAppManifest/icon-outline.png delete mode 100644 samples/25.message-reaction/teamsAppManifest/manifest.json delete mode 100644 samples/40.timex-resolution/LICENSE delete mode 100644 samples/40.timex-resolution/README.md delete mode 100644 samples/40.timex-resolution/pom.xml delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ambiguity.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Application.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Constraints.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/LanguageGeneration.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Parsing.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Ranges.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/Resolutions.java delete mode 100644 samples/40.timex-resolution/src/main/java/com/microsoft/bot/sample/timex/resolution/package-info.java delete mode 100644 samples/43.complex-dialog/LICENSE delete mode 100644 samples/43.complex-dialog/README.md delete mode 100644 samples/43.complex-dialog/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/43.complex-dialog/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/43.complex-dialog/pom.xml delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/Application.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogAndWelcome.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/DialogBot.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/MainDialog.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/ReviewSelectionDialog.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/TopLevelDialog.java delete mode 100644 samples/43.complex-dialog/src/main/java/com/microsoft/bot/sample/complexdialog/UserProfile.java delete mode 100644 samples/43.complex-dialog/src/main/resources/application.properties delete mode 100644 samples/43.complex-dialog/src/main/resources/log4j2.json delete mode 100644 samples/43.complex-dialog/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/43.complex-dialog/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/43.complex-dialog/src/main/webapp/index.html delete mode 100644 samples/43.complex-dialog/src/test/java/com/microsoft/bot/sample/complexdialog/ApplicationTest.java delete mode 100644 samples/44.prompt-users-for-input/LICENSE delete mode 100644 samples/44.prompt-users-for-input/README.md delete mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/44.prompt-users-for-input/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/44.prompt-users-for-input/pom.xml delete mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/Application.java delete mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/ConversationFlow.java delete mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/CustomPromptBot.java delete mode 100644 samples/44.prompt-users-for-input/src/main/java/com/microsoft/bot/sample/promptusersforinput/UserProfile.java delete mode 100644 samples/44.prompt-users-for-input/src/main/resources/application.properties delete mode 100644 samples/44.prompt-users-for-input/src/main/resources/log4j2.json delete mode 100644 samples/44.prompt-users-for-input/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/44.prompt-users-for-input/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/44.prompt-users-for-input/src/main/webapp/index.html delete mode 100644 samples/44.prompt-users-for-input/src/test/java/com/microsoft/bot/sample/promptusersforinput/ApplicationTest.java delete mode 100644 samples/45.state-management/LICENSE delete mode 100644 samples/45.state-management/README.md delete mode 100644 samples/45.state-management/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/45.state-management/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/45.state-management/pom.xml delete mode 100644 samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/Application.java delete mode 100644 samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/ConversationData.java delete mode 100644 samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/StateManagementBot.java delete mode 100644 samples/45.state-management/src/main/java/com/microsoft/bot/sample/statemanagement/UserProfile.java delete mode 100644 samples/45.state-management/src/main/resources/application.properties delete mode 100644 samples/45.state-management/src/main/resources/log4j2.json delete mode 100644 samples/45.state-management/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/45.state-management/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/45.state-management/src/main/webapp/index.html delete mode 100644 samples/45.state-management/src/test/java/com/microsoft/bot/sample/statemanagement/ApplicationTest.java delete mode 100644 samples/46.teams-auth/LICENSE delete mode 100644 samples/46.teams-auth/README.md delete mode 100644 samples/46.teams-auth/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/46.teams-auth/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/46.teams-auth/pom.xml delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/Application.java delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/DialogBot.java delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/LogoutDialog.java delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/MainDialog.java delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/SimpleGraphClient.java delete mode 100644 samples/46.teams-auth/src/main/java/com/microsoft/bot/sample/teamsauth/TeamsBot.java delete mode 100644 samples/46.teams-auth/src/main/resources/application.properties delete mode 100644 samples/46.teams-auth/src/main/resources/log4j2.json delete mode 100644 samples/46.teams-auth/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/46.teams-auth/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/46.teams-auth/src/main/webapp/index.html delete mode 100644 samples/46.teams-auth/src/test/java/com/microsoft/bot/sample/teamsauth/ApplicationTest.java delete mode 100644 samples/46.teams-auth/teamsAppManifest/icon-color.png delete mode 100644 samples/46.teams-auth/teamsAppManifest/icon-outline.png delete mode 100644 samples/46.teams-auth/teamsAppManifest/manifest.json delete mode 100644 samples/47.inspection/LICENSE delete mode 100644 samples/47.inspection/README.md delete mode 100644 samples/47.inspection/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/47.inspection/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/47.inspection/pom.xml delete mode 100644 samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/Application.java delete mode 100644 samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/CustomState.java delete mode 100644 samples/47.inspection/src/main/java/com/microsoft/bot/sample/inspection/EchoBot.java delete mode 100644 samples/47.inspection/src/main/resources/application.properties delete mode 100644 samples/47.inspection/src/main/resources/log4j2.json delete mode 100644 samples/47.inspection/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/47.inspection/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/47.inspection/src/main/webapp/index.html delete mode 100644 samples/47.inspection/src/test/java/com/microsoft/bot/sample/inspection/ApplicationTest.java delete mode 100644 samples/49.qnamaker-all-features/LICENSE delete mode 100644 samples/49.qnamaker-all-features/README.md delete mode 100644 samples/49.qnamaker-all-features/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/49.qnamaker-all-features/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/49.qnamaker-all-features/pom.xml delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/Application.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServices.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/BotServicesImpl.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnABot.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/QnAMakerBaseDialog.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/RootDialog.java delete mode 100644 samples/49.qnamaker-all-features/src/main/java/com/microsoft/bot/sample/qnamaker/all/features/package-info.java delete mode 100644 samples/49.qnamaker-all-features/src/main/resources/application.properties delete mode 100644 samples/49.qnamaker-all-features/src/main/resources/log4j2.json delete mode 100644 samples/49.qnamaker-all-features/src/main/resources/smartLightFAQ.tsv delete mode 100644 samples/49.qnamaker-all-features/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/49.qnamaker-all-features/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/49.qnamaker-all-features/src/main/webapp/index.html delete mode 100644 samples/49.qnamaker-all-features/src/test/java/com/microsoft/bot/sample/qnamaker/all/features/ApplicationTest.java delete mode 100644 samples/50.teams-messaging-extensions-search/LICENSE delete mode 100644 samples/50.teams-messaging-extensions-search/README.md delete mode 100644 samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/50.teams-messaging-extensions-search/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/50.teams-messaging-extensions-search/pom.xml delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/Application.java delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/java/com/microsoft/bot/sample/teamssearch/TeamsMessagingExtensionsSearchBot.java delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/resources/application.properties delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/resources/log4j2.json delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/50.teams-messaging-extensions-search/src/main/webapp/index.html delete mode 100644 samples/50.teams-messaging-extensions-search/src/test/java/com/microsoft/bot/sample/teamssearch/ApplicationTest.java delete mode 100644 samples/50.teams-messaging-extensions-search/teamsAppManifest/icon-color.png delete mode 100644 samples/50.teams-messaging-extensions-search/teamsAppManifest/icon-outline.png delete mode 100644 samples/50.teams-messaging-extensions-search/teamsAppManifest/manifest.json delete mode 100644 samples/51.teams-messaging-extensions-action/LICENSE delete mode 100644 samples/51.teams-messaging-extensions-action/README.md delete mode 100644 samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/51.teams-messaging-extensions-action/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/51.teams-messaging-extensions-action/pom.xml delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/Application.java delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/java/com/microsoft/bot/sample/teamsaction/TeamsMessagingExtensionsActionBot.java delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/resources/application.properties delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/resources/log4j2.json delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/51.teams-messaging-extensions-action/src/main/webapp/index.html delete mode 100644 samples/51.teams-messaging-extensions-action/src/test/java/com/microsoft/bot/sample/teamsaction/ApplicationTest.java delete mode 100644 samples/51.teams-messaging-extensions-action/teamsAppManifest/icon-color.png delete mode 100644 samples/51.teams-messaging-extensions-action/teamsAppManifest/icon-outline.png delete mode 100644 samples/51.teams-messaging-extensions-action/teamsAppManifest/manifest.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/LICENSE delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/README.md delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/pom.xml delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/Application.java delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/SimpleGraphClient.java delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/java/com/microsoft/bot/sample/teamssearchauth/TeamsMessagingExtensionsSearchAuthConfigBot.java delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/adaptiveCard.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/application.properties delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/log4j2.json delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/resources/public/searchSettings.html delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/main/webapp/index.html delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/src/test/java/com/microsoft/bot/sample/teamssearchauth/ApplicationTest.java delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/icon-color.png delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/icon-outline.png delete mode 100644 samples/52.teams-messaging-extensions-search-auth-config/teamsAppManifest/manifest.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/LICENSE delete mode 100644 samples/53.teams-messaging-extensions-action-preview/README.md delete mode 100644 samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/pom.xml delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/Application.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/TeamsMessagingExtensionsActionPreviewBot.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/AdaptiveCard.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Body.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/java/com/microsoft/bot/sample/teamsactionpreview/models/Choice.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/resources/adaptiveCardEditor.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/resources/application.properties delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/resources/log4j2.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/resources/submitCard.json delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/main/webapp/index.html delete mode 100644 samples/53.teams-messaging-extensions-action-preview/src/test/java/com/microsoft/bot/sample/teamsactionpreview/ApplicationTest.java delete mode 100644 samples/53.teams-messaging-extensions-action-preview/teamsAppManifest/icon-color.png delete mode 100644 samples/53.teams-messaging-extensions-action-preview/teamsAppManifest/icon-outline.png delete mode 100644 samples/53.teams-messaging-extensions-action-preview/teamsAppManifest/manifest.json delete mode 100644 samples/54.teams-task-module/LICENSE delete mode 100644 samples/54.teams-task-module/README.md delete mode 100644 samples/54.teams-task-module/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/54.teams-task-module/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/54.teams-task-module/pom.xml delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/Application.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/TeamsTaskModuleBot.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/AdaptiveCardTaskFetchValue.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/CardTaskFetchValue.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleIds.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleResponseFactory.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/TaskModuleUIConstants.java delete mode 100644 samples/54.teams-task-module/src/main/java/com/microsoft/bot/sample/teamstaskmodule/models/UISettings.java delete mode 100644 samples/54.teams-task-module/src/main/resources/adaptiveTemplate.json delete mode 100644 samples/54.teams-task-module/src/main/resources/adaptivecard.json delete mode 100644 samples/54.teams-task-module/src/main/resources/application.properties delete mode 100644 samples/54.teams-task-module/src/main/resources/log4j2.json delete mode 100644 samples/54.teams-task-module/src/main/resources/static/CustomForm.html delete mode 100644 samples/54.teams-task-module/src/main/resources/static/YouTube.html delete mode 100644 samples/54.teams-task-module/src/main/resources/static/css/Custom.css delete mode 100644 samples/54.teams-task-module/src/main/resources/static/css/Site.css delete mode 100644 samples/54.teams-task-module/src/main/resources/static/css/msteams-16.css delete mode 100644 samples/54.teams-task-module/src/test/java/com/microsoft/bot/sample/teamstaskmodule/ApplicationTest.java delete mode 100644 samples/54.teams-task-module/teamsAppManifest/icon-color.png delete mode 100644 samples/54.teams-task-module/teamsAppManifest/icon-outline.png delete mode 100644 samples/54.teams-task-module/teamsAppManifest/manifest.json delete mode 100644 samples/55.teams-link-unfurling/LICENSE delete mode 100644 samples/55.teams-link-unfurling/README.md delete mode 100644 samples/55.teams-link-unfurling/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/55.teams-link-unfurling/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/55.teams-link-unfurling/pom.xml delete mode 100644 samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/Application.java delete mode 100644 samples/55.teams-link-unfurling/src/main/java/com/microsoft/bot/sample/teamsunfurl/LinkUnfurlingBot.java delete mode 100644 samples/55.teams-link-unfurling/src/main/resources/application.properties delete mode 100644 samples/55.teams-link-unfurling/src/main/resources/log4j2.json delete mode 100644 samples/55.teams-link-unfurling/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/55.teams-link-unfurling/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/55.teams-link-unfurling/src/main/webapp/index.html delete mode 100644 samples/55.teams-link-unfurling/src/test/java/com/microsoft/bot/sample/teamsunfurl/ApplicationTest.java delete mode 100644 samples/55.teams-link-unfurling/teamsAppManifest/icon-color.png delete mode 100644 samples/55.teams-link-unfurling/teamsAppManifest/icon-outline.png delete mode 100644 samples/55.teams-link-unfurling/teamsAppManifest/manifest.json delete mode 100644 samples/56.teams-file-upload/LICENSE delete mode 100644 samples/56.teams-file-upload/README.md delete mode 100644 samples/56.teams-file-upload/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/56.teams-file-upload/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/56.teams-file-upload/files/teams-logo.png delete mode 100644 samples/56.teams-file-upload/pom.xml delete mode 100644 samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/Application.java delete mode 100644 samples/56.teams-file-upload/src/main/java/com/microsoft/bot/sample/teamsfileupload/TeamsFileUploadBot.java delete mode 100644 samples/56.teams-file-upload/src/main/resources/application.properties delete mode 100644 samples/56.teams-file-upload/src/main/resources/log4j2.json delete mode 100644 samples/56.teams-file-upload/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/56.teams-file-upload/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/56.teams-file-upload/src/main/webapp/index.html delete mode 100644 samples/56.teams-file-upload/src/test/java/com/microsoft/bot/sample/teamsfileupload/ApplicationTest.java delete mode 100644 samples/56.teams-file-upload/teamsAppManifest/icon-color.png delete mode 100644 samples/56.teams-file-upload/teamsAppManifest/icon-outline.png delete mode 100644 samples/56.teams-file-upload/teamsAppManifest/manifest.json delete mode 100644 samples/57.teams-conversation-bot/LICENSE delete mode 100644 samples/57.teams-conversation-bot/README.md delete mode 100644 samples/57.teams-conversation-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/57.teams-conversation-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/57.teams-conversation-bot/pom.xml delete mode 100644 samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/Application.java delete mode 100644 samples/57.teams-conversation-bot/src/main/java/com/microsoft/bot/sample/teamsconversation/TeamsConversationBot.java delete mode 100644 samples/57.teams-conversation-bot/src/main/resources/application.properties delete mode 100644 samples/57.teams-conversation-bot/src/main/resources/log4j2.json delete mode 100644 samples/57.teams-conversation-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/57.teams-conversation-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/57.teams-conversation-bot/src/main/webapp/index.html delete mode 100644 samples/57.teams-conversation-bot/src/test/java/com/microsoft/bot/sample/teamsconversation/ApplicationTest.java delete mode 100644 samples/57.teams-conversation-bot/teamsAppManifest/icon-color.png delete mode 100644 samples/57.teams-conversation-bot/teamsAppManifest/icon-outline.png delete mode 100644 samples/57.teams-conversation-bot/teamsAppManifest/manifest.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/LICENSE delete mode 100644 samples/58.teams-start-new-thread-in-channel/README.md delete mode 100644 samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/pom.xml delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/Application.java delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/java/com/microsoft/bot/sample/teamsstartnewthread/TeamsStartNewThreadBot.java delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/resources/application.properties delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/resources/log4j2.json delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/main/webapp/index.html delete mode 100644 samples/58.teams-start-new-thread-in-channel/src/test/java/com/microsoft/bot/sample/teamsstartnewthread/ApplicationTest.java delete mode 100644 samples/58.teams-start-new-thread-in-channel/teamsAppManifest/icon-color.png delete mode 100644 samples/58.teams-start-new-thread-in-channel/teamsAppManifest/icon-outline.png delete mode 100644 samples/58.teams-start-new-thread-in-channel/teamsAppManifest/manifest.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/LICENSE delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/README.md delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/pom.xml delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/Application.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/RootBot.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillAdapterWithErrorHandler.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/SkillsConfiguration.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/authentication/AllowedSkillsClaimsValidator.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/java/com/microsoft/bot/sample/simplerootbot/controller/SkillController.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/application.properties delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/resources/log4j2.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/main/webapp/index.html delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogRootBot/src/test/java/com/microsoft/bot/sample/simplerootbot/ApplicationTest.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/LICENSE delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/README.md delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/pom.xml delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/Application.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/EchoBot.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/java/com/microsoft/bot/sample/echoskillbot/SkillAdapterWithErrorHandler.java delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/application.properties delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/resources/log4j2.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/index.html delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json delete mode 100644 samples/80.skills-simple-bot-to-bot/DialogSkillBot/src/test/java/com/microsoft/bot/sample/echoskillbot/ApplicationTest.java delete mode 100644 samples/80.skills-simple-bot-to-bot/README.md delete mode 100644 samples/81.skills-skilldialog/README.md delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/.vscode/settings.json delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/LICENSE delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/README.md delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/pom.xml delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdapterWithErrorHandler.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/AdaptiveCard.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Application.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Body.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/Bots/RootBot.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/SkillsConfiguration.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/authentication/AllowedSkillsClaimsValidator.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/controller/SkillController.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/dialogs/MainDialog.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/ConsoleLogger.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/Logger.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/java/com/microsoft/bot/sample/dialogrootbot/middleware/LoggerMiddleware.java delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/application.properties delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/log4j2.json delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/resources/welcomeCard.json delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/main/webapp/index.html delete mode 100644 samples/81.skills-skilldialog/dialog-root-bot/src/test/java/com/microsoft/bot/sample/dialogrootbot/ApplicationTest.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/.vscode/settings.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/LICENSE delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/README.md delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/pom.xml delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/Application.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/SkillAdapterWithErrorHandler.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/bots/SkillBot.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/cognitivemodels/flightbooking.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/ActivityRouterDialog.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDetails.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/BookingDialog.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/CancelAndHelpDialog.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DateResolverDialog.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/DialogSkillBotRecognizer.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/java/com/microsoft/bot/sample/dialogskillbot/dialogs/Location.java delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/application.properties delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/resources/log4j2.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/META-INF/MANIFEST.MF delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/WEB-INF/web.xml delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/index.html delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/main/webapp/manifest/echoskillbot-manifest-1.0.json delete mode 100644 samples/81.skills-skilldialog/dialog-skill-bot/src/test/java/com/microsoft/bot/sample/dialogskillbot/ApplicationTest.java delete mode 100644 samples/README.md delete mode 100644 samples/pom.xml delete mode 100644 samples/servlet-echo/LICENSE delete mode 100644 samples/servlet-echo/README.md delete mode 100644 samples/servlet-echo/deploymentTemplates/template-with-new-rg-gov.json delete mode 100644 samples/servlet-echo/deploymentTemplates/template-with-new-rg.json delete mode 100644 samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg-gov.json delete mode 100644 samples/servlet-echo/deploymentTemplates/template-with-preexisting-rg.json delete mode 100644 samples/servlet-echo/pom.xml delete mode 100644 samples/servlet-echo/src/main/java/com/microsoft/bot/sample/servlet/BotController.java delete mode 100644 samples/servlet-echo/src/main/java/com/microsoft/bot/sample/servlet/ControllerBase.java delete mode 100644 samples/servlet-echo/src/main/java/com/microsoft/bot/sample/servlet/EchoBot.java delete mode 100644 samples/servlet-echo/src/main/java/com/microsoft/bot/sample/servlet/ServletWithBotConfiguration.java delete mode 100644 samples/servlet-echo/src/main/resources/application.properties delete mode 100644 samples/servlet-echo/src/main/resources/log4j2.json diff --git a/Generator/generator-botbuilder-java/.gitignore b/Generator/generator-botbuilder-java/.gitignore deleted file mode 100644 index ba2a97b57..000000000 --- a/Generator/generator-botbuilder-java/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -node_modules -coverage diff --git a/Generator/generator-botbuilder-java/LICENSE.md b/Generator/generator-botbuilder-java/LICENSE.md deleted file mode 100644 index 506ab97e5..000000000 --- a/Generator/generator-botbuilder-java/LICENSE.md +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Microsoft Corporation - -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. diff --git a/Generator/generator-botbuilder-java/README.md b/Generator/generator-botbuilder-java/README.md deleted file mode 100644 index f4ad7506b..000000000 --- a/Generator/generator-botbuilder-java/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# generator-botbuilder-java - -Yeoman generator for [Bot Framework v4](https://dev.botframework.com). Will let you quickly set up a conversational AI bot -using core AI capabilities. - -## About - -`generator-botbuilder-java` will help you build new conversational AI bots using the [Bot Framework v4](https://dev.botframework.com). - -## Templates - -The generator supports three different template options. The table below can help guide which template is right for you. - -| Template | Description | -| ---------- | --------- | -| Echo Bot | A good template if you want a little more than "Hello World!", but not much more. This template handles the very basics of sending messages to a bot, and having the bot process the messages by repeating them back to the user. This template produces a bot that simply "echoes" back to the user anything the user says to the bot. | -| Empty Bot | A good template if you are familiar with Bot Framework v4, and simply want a basic skeleton project. Also a good option if you want to take sample code from the documentation and paste it into a minimal bot in order to learn. | -| Core Bot | A good template if you want to create advanced bots, as it uses multi-turn dialogs and [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. This template creates a bot that can extract places and dates to book a flight. | - -### How to Choose a Template - -| Template | When This Template is a Good Choice | -| -------- | -------- | -| Echo Bot | You are new to Bot Framework v4 and want a working bot with minimal features. | -| Empty Bot | You are a seasoned Bot Framework v4 developer. You've built bots before, and want the minimum skeleton of a bot. | -| Core Bot | You are a medium to advanced user of Bot Framework v4 and want to start integrating language understanding as well as multi-turn dialogs in your bots. | - -### Template Overview - -#### Echo Bot Template - -The Echo Bot template is slightly more than the a classic "Hello World!" example, but not by much. This template shows the basic structure of a bot, how a bot receives messages from a user, and how a bot sends messages to a user. The bot will "echo" back to the user, what the user says to the bot. It is a good choice for first time, new to Bot Framework v4 developers. - -#### Empty Bot Template - -The Empty Bot template is the minimal skeleton code for a bot. It provides a stub `onTurn` handler but does not perform any actions. If you are experienced writing bots with Bot Framework v4 and want the minimum scaffolding, the Empty template is for you. - -#### Core Bot Template - -The Core Bot template uses [LUIS](https://www.luis.ai) to implement core AI capabilities, a multi-turn conversation using Dialogs, handles user interruptions, and prompts for and validate requests for information from the user. This template implements a basic three-step waterfall dialog, where the first step asks the user for an input to book a flight, then asks the user if the information is correct, and finally confirms the booking with the user. Choose this template if want to create an advanced bot that can extract information from the user's input. - -## Installation - -1. Install [Yeoman](http://yeoman.io) using [npm](https://www.npmjs.com) (we assume you have pre-installed [node.js](https://nodejs.org/)). - - ```bash - # Make sure both are installed globally - npm install -g yo - ``` - -2. Install generator-botbuilder-java by typing the following in your console: - - ```bash - # Make sure both are installed globally - npm install -g generator-botbuilder-java - ``` - -3. Verify that Yeoman and generator-botbuilder-java have been installed correctly by typing the following into your console: - - ```bash - yo botbuilder-java --help - ``` - -## Usage - -### Creating a New Bot Project - -When the generator is launched, it will prompt for the information required to create a new bot. - -```bash -# Run the generator in interactive mode -yo botbuilder-java -``` - -### Generator Command Line Options - -The generator supports a number of command line options that can be used to change the generator's default options or to pre-seed a prompt. - -| Command line Option | Description | -| ------------------- | ----------- | -| --help, -h | List help text for all supported command-line options | -| --botName, -N | The name given to the bot project | -| --packageName, -P | The Java package name to use for the bot | -| --template, -T | The template used to generate the project. Options are `empty`, or `echo`. See [https://aka.ms/botbuilder-generator](https://aka.ms/botbuilder-generator) for additional information regarding the different template option and their functional differences. | -| --noprompt | The generator will not prompt for confirmation before creating a new bot. Any requirement options not passed on the command line will use a reasonable default value. This option is intended to enable automated bot generation for testing purposes. | - -#### Example Using Command Line Options - -This example shows how to pass command line options to the generator, setting the default language to TypeScript and the default template to Core. - -```bash -# Run the generator defaulting the pacakge name and the template -yo botbuilder-java --P "com.mycompany.bot" --T "echo" -``` - -### Generating a Bot Using --noprompt - -The generator can be run in `--noprompt` mode, which can be used for automated bot creation. When run in `--noprompt` mode, the generator can be configured using command line options as documented above. If a command line option is ommitted a reasonable default will be used. In addition, passing the `--noprompt` option will cause the generator to create a new bot project without prompting for confirmation before generating the bot. - -#### Default Options - -| Command line Option | Default Value | -| ------------------- | ----------- | -| --botname, -N | `echo` | -| --packageName, -p | `echo` | -| --template, -T | `echo` | - -#### Examples Using --noprompt - -This example shows how to run the generator in --noprompt mode, setting all required options on the command line. - -```bash -# Run the generator, setting all command line options -yo botbuilder-java --noprompt -N "MyEchoBot" -P "com.mycompany.bot.echo" -T "echo" -``` - -This example shows how to run the generator in --noprompt mode, using all the default command line options. The generator will create a bot project using all the default values specified in the **Default Options** table above. - -```bash -# Run the generator using all default options -yo botbuilder-java --noprompt -``` - -## Logging Issues and Providing Feedback - -Issues and feedback about the botbuilder generator can be submitted through the project's [GitHub Issues](https://github.com/Microsoft/botbuilder-samples/issues) page. diff --git a/Generator/generator-botbuilder-java/generators/app/index.js b/Generator/generator-botbuilder-java/generators/app/index.js deleted file mode 100644 index 66b306531..000000000 --- a/Generator/generator-botbuilder-java/generators/app/index.js +++ /dev/null @@ -1,276 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -'use strict'; -const pkg = require('../../package.json'); -const Generator = require('yeoman-generator'); -const path = require('path'); -const chalk = require('chalk'); -const mkdirp = require('mkdirp'); -const _ = require('lodash'); - -const BOT_TEMPLATE_NAME_EMPTY = 'Empty Bot'; -const BOT_TEMPLATE_NAME_SIMPLE = 'Echo Bot'; -const BOT_TEMPLATE_NAME_CORE = 'Core Bot'; - -const BOT_TEMPLATE_NOPROMPT_EMPTY = 'empty'; -const BOT_TEMPLATE_NOPROMPT_SIMPLE = 'echo'; -const BOT_TEMPLATE_NOPROMPT_CORE = 'core'; - -const bigBot = - ` ╭─────────────────────────────╮\n` + - ` ` + - chalk.blue.bold(`//`) + - ` ` + - chalk.blue.bold(`\\\\`) + - ` │ Welcome to the │\n` + - ` ` + - chalk.blue.bold(`//`) + - ` () () ` + - chalk.blue.bold(`\\\\`) + - ` │ Microsoft Java Bot Builder │\n` + - ` ` + - chalk.blue.bold(`\\\\`) + - ` ` + - chalk.blue.bold(`//`) + - ` /│ generator! │\n` + - ` ` + - chalk.blue.bold(`\\\\`) + - ` ` + - chalk.blue.bold(`//`) + - ` ╰─────────────────────────────╯\n` + - ` v${pkg.version}`; - -const tinyBot = - ` ` + chalk.blue.bold(`<`) + ` ** ` + chalk.blue.bold(`>`) + ` `; - -module.exports = class extends Generator { - constructor(args, opts) { - super(args, opts); - - // allocate an object that we can use to store our user prompt values from our askFor* functions - this.templateConfig = {}; - - // configure the commandline options - this._configureCommandlineOptions(); - } - - initializing() { - // give the user some data before we start asking them questions - this.log(bigBot); - } - - prompting() { - // if we're told to not prompt, then pick what we need and return - if(this.options.noprompt) { - // this function will throw if it encounters errors/invalid options - return this._verifyNoPromptOptions(); - } - - const userPrompts = this._getPrompts(); - async function executePrompts([prompt, ...rest]) { - if (prompt) { - await prompt(); - return executePrompts(rest); - } - } - - return executePrompts(userPrompts); - } - - writing() { - // if the user confirmed their settings, then lets go ahead - // an install module dependencies - if(this.templateConfig.finalConfirmation === true) { - const botName = this.templateConfig.botName; - const packageName = this.templateConfig.packageName.toLowerCase(); - const packageTree = packageName.replace(/\./g, '/'); - const artifact = _.kebabCase(this.templateConfig.botName).replace(/([^a-z0-9-]+)/gi, ``); - const directoryName = _.camelCase(this.templateConfig.botName); - const template = this.templateConfig.template.toLowerCase(); - - if (path.basename(this.destinationPath()) !== directoryName) { - mkdirp.sync(directoryName); - this.destinationRoot(this.destinationPath(directoryName)); - } - - // Copy the project tree - this.fs.copyTpl( - this.templatePath(path.join(template, 'project', '**')), - this.destinationPath(), - { - botName, - packageName, - artifact - } - ); - - // Copy main source - this.fs.copyTpl( - this.templatePath(path.join(template, 'src/main/java/**')), - this.destinationPath(path.join('src/main/java', packageTree)), - { - packageName - } - ); - - // Copy test source - this.fs.copyTpl( - this.templatePath(path.join(template, 'src/test/java/**')), - this.destinationPath(path.join('src/test/java', packageTree)), - { - packageName - } - ); - } - } - - end() { - if(this.templateConfig.finalConfirmation === true) { - this.log(chalk.green('------------------------ ')); - this.log(chalk.green(' Your new bot is ready! ')); - this.log(chalk.green('------------------------ ')); - this.log(`Your bot should be in a directory named "${_.camelCase(this.templateConfig.botName)}"`); - this.log('Open the ' + chalk.green.bold('README.md') + ' to learn how to run your bot. '); - this.log('Thank you for using the Microsoft Bot Framework. '); - this.log(`\n${tinyBot} The Bot Framework Team`); - } else { - this.log(chalk.red.bold('-------------------------------- ')); - this.log(chalk.red.bold(' New bot creation was canceled. ')); - this.log(chalk.red.bold('-------------------------------- ')); - this.log('Thank you for using the Microsoft Bot Framework. '); - this.log(`\n${tinyBot} The Bot Framework Team`); - } - } - - _configureCommandlineOptions() { - this.option('botName', { - desc: 'The name you want to give to your bot', - type: String, - default: 'echo', - alias: 'N' - }); - - this.option('packageName', { - desc: `What's the fully qualified package name of your bot?`, - type: String, - default: 'com.mycompany.echo', - alias: 'P' - }); - - const templateDesc = `The initial bot capabilities. (${BOT_TEMPLATE_NAME_EMPTY} | ${BOT_TEMPLATE_NAME_SIMPLE} | ${BOT_TEMPLATE_NAME_CORE})`; - this.option('template', { - desc: templateDesc, - type: String, - default: BOT_TEMPLATE_NAME_SIMPLE, - alias: 'T' - }); - - this.argument('noprompt', { - desc: 'Do not prompt for any information or confirmation', - type: Boolean, - required: false, - default: false - }); - } - - _getPrompts() { - return [ - // ask the user to name their bot - async () => { - return this.prompt({ - type: 'input', - name: 'botName', - message: `What's the name of your bot?`, - default: (this.options.botName ? this.options.botName : 'echo') - }).then(answer => { - // store the botname description answer - this.templateConfig.botName = answer.botName; - }); - }, - - // ask for package name - async () => { - return this.prompt({ - type: 'input', - name: 'packageName', - message: `What's the fully qualified package name of your bot?`, - default: (this.options.packageName ? this.options.packageName : 'com.mycompany.echo') - }).then(answer => { - // store the package name description answer - this.templateConfig.packageName = answer.packageName; - }); - }, - - - // ask the user which bot template we should use - async () => { - return this.prompt({ - type: 'list', - name: 'template', - message: 'Which template would you like to start with?', - choices: [ - { - name: BOT_TEMPLATE_NAME_SIMPLE, - value: BOT_TEMPLATE_NOPROMPT_SIMPLE - }, - { - name: BOT_TEMPLATE_NAME_EMPTY, - value: BOT_TEMPLATE_NOPROMPT_EMPTY - }, - { - name: BOT_TEMPLATE_NAME_CORE, - value: BOT_TEMPLATE_NOPROMPT_CORE - } - ], - default: (this.options.template ? _.toLower(this.options.template) : BOT_TEMPLATE_NOPROMPT_SIMPLE) - }).then(answer => { - // store the template prompt answer - this.templateConfig.template = answer.template; - }); - }, - - // ask the user for final confirmation before we generate their bot - async () => { - return this.prompt({ - type: 'confirm', - name: 'finalConfirmation', - message: 'Looking good. Shall I go ahead and create your new bot?', - default: true - }).then(answer => { - // store the finalConfirmation prompt answer - this.templateConfig.finalConfirmation = answer.finalConfirmation; - }); - } - ]; - } - - // if we're run with the --noprompt option, verify that all required options were supplied. - // throw for missing options, or a resolved Promise - _verifyNoPromptOptions() { - this.templateConfig = _.pick(this.options, ['botName', 'packageName', 'template']) - - // validate we have what we need, or we'll need to throw - if(!this.templateConfig.botName) { - throw new Error('Must specify a name for your bot when using --noprompt argument. Use --botName or -N'); - } - if(!this.templateConfig.packageName) { - throw new Error('Must specify a package name for your bot when using --noprompt argument. Use --packageName or -P'); - } - - // make sure we have a supported template - const template = (this.templateConfig.template ? _.toLower(this.templateConfig.template) : undefined); - const tmplEmpty = _.toLower(BOT_TEMPLATE_NOPROMPT_EMPTY); - const tmplSimple = _.toLower(BOT_TEMPLATE_NOPROMPT_SIMPLE); - const tmplCore = _.toLower(BOT_TEMPLATE_NOPROMPT_CORE); - if (!template || (template !== tmplEmpty && template !== tmplSimple && template !== tmplCore)) { - throw new Error('Must specify a template when using --noprompt argument. Use --template or -T'); - } - - // when run using --noprompt and we have all the required templateConfig, then set final confirmation to true - // so we can go forward and create the new bot without prompting the user for confirmation - this.templateConfig.finalConfirmation = true; - - return Promise.resolve(); - } -}; diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md deleted file mode 100644 index 12bc78ed0..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README-LUIS.md +++ /dev/null @@ -1,216 +0,0 @@ -# Setting up LUIS via CLI: - -This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. - -> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ -> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ -> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ - - [Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app - [Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app - -## Table of Contents: - -- [Prerequisites](#Prerequisites) -- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) -- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) - -___ - -## [Prerequisites](#Table-of-Contents): - -#### Install Azure CLI >=2.0.61: - -Visit the following page to find the correct installer for your OS: -- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest - -#### Install LUIS CLI >=2.4.0: - -Open a CLI of your choice and type the following: - -```bash -npm i -g luis-apis@^2.4.0 -``` - -#### LUIS portal account: - -You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. - -After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. - - [LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] - [LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key - -___ - -## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) - -### 1. Import the local LUIS application to luis.ai - -```bash -luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" -``` - -Outputs the following JSON: - -```json -{ - "id": "########-####-####-####-############", - "name": "FlightBooking", - "description": "A LUIS model that uses intent and entities.", - "culture": "en-us", - "usageScenario": "", - "domain": "", - "versionsCount": 1, - "createdDateTime": "2019-03-29T18:32:02Z", - "endpoints": {}, - "endpointHitsCount": 0, - "activeVersion": "0.1", - "ownerEmail": "bot@contoso.com", - "tokenizerVersion": "1.0.0" -} -``` - -For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. - -### 2. Train the LUIS Application - -```bash -luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait -``` - -### 3. Publish the LUIS Application - -```bash -luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" -``` - -> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
-> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
-> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
-> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. - - [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 - -Outputs the following: - -```json - { - "versionId": "0.1", - "isStaging": false, - "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", - "region": "westus", - "assignedEndpointKey": null, - "endpointRegion": "westus", - "failedRegions": "", - "publishedDateTime": "2019-03-29T18:40:32Z", - "directVersionPublish": false -} -``` - -To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. - - [README-LUIS]: ./README-LUIS.md - -___ - -## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) - -### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI - -> _Note:_
-> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ -> ```bash -> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" -> ``` -> _To see a list of valid locations, use `az account list-locations`_ - - -```bash -# Use Azure CLI to create the LUIS Key resource on Azure -az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -The command will output a response similar to the JSON below: - -```json -{ - "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", - "etag": "\"########-####-####-####-############\"", - "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", - "internalId": "################################", - "kind": "luis", - "location": "westus", - "name": "NewLuisResourceName", - "provisioningState": "Succeeded", - "resourceGroup": "ResourceGroupName", - "sku": { - "name": "S0", - "tier": null - }, - "tags": null, - "type": "Microsoft.CognitiveServices/accounts" -} -``` - - - -Take the output from the previous command and create a JSON file in the following format: - -```json -{ - "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", - "resourceGroup": "ResourceGroupName", - "accountName": "NewLuisResourceName" -} -``` - -### 2. Retrieve ARM access token via Azure CLI - -```bash -az account get-access-token --subscription "AzureSubscriptionGuid" -``` - -This will return an object that looks like this: - -```json -{ - "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", - "expiresOn": "2200-12-31 23:59:59.999999", - "subscription": "AzureSubscriptionGuid", - "tenant": "tenant-guid", - "tokenType": "Bearer" -} -``` - -The value needed for the next step is the `"accessToken"`. - -### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application - -```bash -luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" -``` - -If successful, it should yield a response like this: - -```json -{ - "code": "Success", - "message": "Operation Successful" -} -``` - -### 4. See the LUIS Cognitive Services' keys - -```bash -az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -This will return an object that looks like this: - -```json -{ - "key1": "9a69####dc8f####8eb4####399f####", - "key2": "####f99e####4b1a####fb3b####6b9f" -} -``` diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md deleted file mode 100644 index 5428395fc..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# <%= botName %> - -Bot Framework v4 core bot sample. - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: - -- Use [LUIS](https://www.luis.ai) to implement core AI capabilities -- Implement a multi-turn conversation using Dialogs -- Handle user interruptions for such things as `Help` or `Cancel` -- Prompt for and validate requests for information from the user - -## Prerequisites - -This sample **requires** prerequisites in order to run. - -### Overview - -This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. - -### Create a LUIS Application to enable language understanding - -The LUIS model for this example can be found under `cognitiveModels/FlightBooking.json` and the LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). - -Once you created the LUIS model, update `application.properties` with your `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName`. - -``` - LuisAppId="Your LUIS App Id" - LuisAPIKey="Your LUIS Subscription key here" - LuisAPIHostName="Your LUIS App region here (i.e: westus.api.cognitive.microsoft.com)" -``` - -## To try this sample - -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json deleted file mode 100644 index 603dd06b2..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/cognitiveModels/FlightBooking.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "luis_schema_version": "3.2.0", - "versionId": "0.1", - "name": "FlightBooking", - "desc": "Luis Model for <%= botName %>", - "culture": "en-us", - "tokenizerVersion": "1.0.0", - "intents": [ - { - "name": "BookFlight" - }, - { - "name": "Cancel" - }, - { - "name": "GetWeather" - }, - { - "name": "None" - } - ], - "entities": [], - "composites": [ - { - "name": "From", - "children": [ - "Airport" - ], - "roles": [] - }, - { - "name": "To", - "children": [ - "Airport" - ], - "roles": [] - } - ], - "closedLists": [ - { - "name": "Airport", - "subLists": [ - { - "canonicalForm": "Paris", - "list": [ - "paris", - "cdg" - ] - }, - { - "canonicalForm": "London", - "list": [ - "london", - "lhr" - ] - }, - { - "canonicalForm": "Berlin", - "list": [ - "berlin", - "txl" - ] - }, - { - "canonicalForm": "New York", - "list": [ - "new york", - "jfk" - ] - }, - { - "canonicalForm": "Seattle", - "list": [ - "seattle", - "sea" - ] - } - ], - "roles": [] - } - ], - "patternAnyEntities": [], - "regex_entities": [], - "prebuiltEntities": [ - { - "name": "datetimeV2", - "roles": [] - } - ], - "model_features": [], - "regex_features": [], - "patterns": [], - "utterances": [ - { - "text": "book a flight", - "intent": "BookFlight", - "entities": [] - }, - { - "text": "book a flight from new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 26 - } - ] - }, - { - "text": "book a flight from seattle", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 25 - } - ] - }, - { - "text": "book a hotel in new york", - "intent": "None", - "entities": [] - }, - { - "text": "book a restaurant", - "intent": "None", - "entities": [] - }, - { - "text": "book flight from london to paris on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 17, - "endPos": 22 - }, - { - "entity": "To", - "startPos": 27, - "endPos": 31 - } - ] - }, - { - "text": "book flight to berlin on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 15, - "endPos": 20 - } - ] - }, - { - "text": "book me a flight from london to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 22, - "endPos": 27 - }, - { - "entity": "To", - "startPos": 32, - "endPos": 36 - } - ] - }, - { - "text": "bye", - "intent": "Cancel", - "entities": [] - }, - { - "text": "cancel booking", - "intent": "Cancel", - "entities": [] - }, - { - "text": "exit", - "intent": "Cancel", - "entities": [] - }, - { - "text": "find an airport near me", - "intent": "None", - "entities": [] - }, - { - "text": "flight to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "flight to paris from london on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - }, - { - "entity": "From", - "startPos": 21, - "endPos": 26 - } - ] - }, - { - "text": "fly from berlin to paris on may 5th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 9, - "endPos": 14 - }, - { - "entity": "To", - "startPos": 19, - "endPos": 23 - } - ] - }, - { - "text": "go to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 6, - "endPos": 10 - } - ] - }, - { - "text": "going from paris to berlin", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 11, - "endPos": 15 - }, - { - "entity": "To", - "startPos": 20, - "endPos": 25 - } - ] - }, - { - "text": "i'd like to rent a car", - "intent": "None", - "entities": [] - }, - { - "text": "ignore", - "intent": "Cancel", - "entities": [] - }, - { - "text": "travel from new york to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 12, - "endPos": 19 - }, - { - "entity": "To", - "startPos": 24, - "endPos": 28 - } - ] - }, - { - "text": "travel to new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 17 - } - ] - }, - { - "text": "travel to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "what's the forecast for this friday?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like for tomorrow", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like in new york", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "winter is coming", - "intent": "None", - "entities": [] - } - ], - "settings": [] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml deleted file mode 100644 index d299aea02..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/pom.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - 4.0.0 - - <%= packageName %> - <%= artifact %> - 1.0.0 - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Core Bot sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - <%= packageName %>.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - - - com.microsoft.bot - bot-ai-luis-v3 - 4.13.0-SNAPSHOT - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - <%= packageName %>.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties deleted file mode 100644 index 255d7cd56..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -LuisAppId= -LuisAPIKey= -LuisAPIHostName= -server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json deleted file mode 100644 index 9b6389e39..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/cards/welcomeCard.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "Image", - "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", - "size": "stretch" - }, - { - "type": "TextBlock", - "spacing": "medium", - "size": "default", - "weight": "bolder", - "text": "Welcome to Bot Framework!", - "wrap": true, - "maxLines": 0 - }, - { - "type": "TextBlock", - "size": "default", - "isSubtle": true, - "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", - "wrap": true, - "maxLines": 0 - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Get an overview", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - }, - { - "type": "Action.OpenUrl", - "title": "Ask a question", - "url": "https://stackoverflow.com/questions/tagged/botframework" - }, - { - "type": "Action.OpenUrl", - "title": "Learn how to deploy", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html deleted file mode 100644 index 997ee3708..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/project/src/main/webapp/index.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - <%= botName %> - - - - - -
-
-
-
<%= botName %>
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java deleted file mode 100644 index 4f92dae33..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/Application.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * This is the starting point of the Sprint Boot Bot application. - */ -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - /** - * The start method. - * - * @param args The args. - */ - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method with the - * @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - Configuration configuration, - UserState userState, - ConversationState conversationState - ) { - FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration); - MainDialog dialog = new MainDialog(recognizer, new BookingDialog()); - return new DialogAndWelcomeBot<>(conversationState, userState, dialog); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java deleted file mode 100644 index 30b0dc9e9..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDetails.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -/** - * The model class to retrieve the information of the booking. - */ -public class BookingDetails { - - private String destination; - private String origin; - private String travelDate; - - /** - * Gets the destination of the booking. - * - * @return The destination. - */ - public String getDestination() { - return destination; - } - - - /** - * Sets the destination of the booking. - * - * @param withDestination The new destination. - */ - public void setDestination(String withDestination) { - this.destination = withDestination; - } - - /** - * Gets the origin of the booking. - * - * @return The origin. - */ - public String getOrigin() { - return origin; - } - - /** - * Sets the origin of the booking. - * - * @param withOrigin The new origin. - */ - public void setOrigin(String withOrigin) { - this.origin = withOrigin; - } - - /** - * Gets the travel date of the booking. - * - * @return The travel date. - */ - public String getTravelDate() { - return travelDate; - } - - /** - * Sets the travel date of the booking. - * - * @param withTravelDate The new travel date. - */ - public void setTravelDate(String withTravelDate) { - this.travelDate = withTravelDate; - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java deleted file mode 100644 index 7068cc7ac..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/BookingDialog.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the booking dialogs. - */ -public class BookingDialog extends CancelAndHelpDialog { - - private final String destinationStepMsgText = "Where would you like to travel to?"; - private final String originStepMsgText = "Where are you traveling from?"; - - /** - * The constructor of the Booking Dialog class. - */ - public BookingDialog() { - super("BookingDialog"); - - addDialog(new TextPrompt("TextPrompt")); - addDialog(new ConfirmPrompt("ConfirmPrompt")); - addDialog(new DateResolverDialog(null)); - WaterfallStep[] waterfallSteps = { - this::destinationStep, - this::originStep, - this::travelDateStep, - this::confirmStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - - private CompletableFuture destinationStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - if (bookingDetails.getDestination().isEmpty()) { - Activity promptMessage = - MessageFactory.text(destinationStepMsgText, destinationStepMsgText, - InputHints.EXPECTING_INPUT - ); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getDestination()); - } - - - private CompletableFuture originStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setDestination(stepContext.getResult().toString()); - - if (bookingDetails.getOrigin().isEmpty()) { - Activity promptMessage = - MessageFactory - .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getOrigin()); - } - - - private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setOrigin(stepContext.getResult().toString()); - - if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { - return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); - } - - return stepContext.next(bookingDetails.getTravelDate()); - } - - - private CompletableFuture confirmStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setTravelDate(stepContext.getResult().toString()); - - String messageText = - String.format( - "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", - bookingDetails.getDestination(), bookingDetails.getOrigin(), - bookingDetails.getTravelDate() - ); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - - return stepContext.prompt("ConfirmPrompt", promptOptions); - } - - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - if ((Boolean) stepContext.getResult()) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - return stepContext.endDialog(bookingDetails); - } - - return stepContext.endDialog(null); - } - - private static boolean isAmbiguous(String timex) { - TimexProperty timexProperty = new TimexProperty(timex); - return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java deleted file mode 100644 index 3a35b494a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/CancelAndHelpDialog.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.DialogTurnStatus; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; -import com.microsoft.bot.schema.InputHints; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of the dialog interruptions. - */ -public class CancelAndHelpDialog extends ComponentDialog { - - private final String helpMsgText = "Show help here"; - private final String cancelMsgText = "Cancelling..."; - - /** - * The constructor of the CancelAndHelpDialog class. - * - * @param id The dialog's Id. - */ - public CancelAndHelpDialog(String id) { - super(id); - } - - /** - * Called when the dialog is _continued_, where it is the active dialog and the user replies - * with a new activity. - * - * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. - * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is - * successful, the result indicates whether the dialog is still active after the turn has been - * processed by the dialog. The result may also contain a return value. - */ - @Override - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - return interrupt(innerDc).thenCompose(result -> { - if (result != null) { - return CompletableFuture.completedFuture(result); - } - return super.onContinueDialog(innerDc); - }); - } - - private CompletableFuture interrupt(DialogContext innerDc) { - if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { - String text = innerDc.getContext().getActivity().getText().toLowerCase(); - - switch (text) { - case "help": - case "?": - Activity helpMessage = MessageFactory - .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); - return innerDc.getContext().sendActivity(helpMessage) - .thenCompose(sendResult -> - CompletableFuture - .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); - case "cancel": - case "quit": - Activity cancelMessage = MessageFactory - .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); - return innerDc.getContext() - .sendActivity(cancelMessage) - .thenCompose(sendResult -> innerDc.cancelAllDialogs()); - default: - break; - } - } - - return CompletableFuture.completedFuture(null); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java deleted file mode 100644 index 7570fd646..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DateResolverDialog.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.DateTimePrompt; -import com.microsoft.bot.dialogs.prompts.DateTimeResolution; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the date resolver dialogs. - */ -public class DateResolverDialog extends CancelAndHelpDialog { - private final String promptMsgText = "When would you like to travel?"; - private final String repromptMsgText = - "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; - - - /** - * The constructor of the DateResolverDialog class. - * @param id The dialog's id. - */ - public DateResolverDialog(@Nullable String id) { - super(id != null ? id : "DateResolverDialog"); - - - addDialog(new DateTimePrompt("DateTimePrompt", - DateResolverDialog::dateTimePromptValidator, null)); - WaterfallStep[] waterfallSteps = { - this::initialStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture initialStep(WaterfallStepContext stepContext) { - String timex = (String) stepContext.getOptions(); - - Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); - Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); - - if (timex == null) { - // We were not given any date at all so prompt the user. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - promptOptions.setRetryPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - // We have a Date we just need to check it is unambiguous. - TimexProperty timexProperty = new TimexProperty(timex); - if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { - // This is essentially a "reprompt" of the data we were given up front. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - DateTimeResolution dateTimeResolution = new DateTimeResolution(); - dateTimeResolution.setTimex(timex); - List dateTimeResolutions = new ArrayList(); - dateTimeResolutions.add(dateTimeResolution); - return stepContext.next(dateTimeResolutions); - } - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); - return stepContext.endDialog(timex); - } - - private static CompletableFuture dateTimePromptValidator( - PromptValidatorContext> promptContext - ) { - if (promptContext.getRecognized().getSucceeded()) { - // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the - // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. - // e.g. missing a Year. - String timex = ((List) promptContext.getRecognized().getValue()) - .get(0).getTimex().split("T")[0]; - - // If this is a definite Date including year, month and day we are good otherwise reprompt. - // A better solution might be to let the user know what part is actually missing. - Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); - - return CompletableFuture.completedFuture(isDefinite); - } - - return CompletableFuture.completedFuture(false); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java deleted file mode 100644 index 807b6457e..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogAndWelcomeBot.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; -import com.microsoft.applicationinsights.core.dependencies.apachecommons.lang3.StringUtils; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.Serialization; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the welcome dialog. - * - * @param is a Dialog. - */ -public class DialogAndWelcomeBot extends DialogBot { - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogAndWelcomeBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - super(withConversationState, withUserState, withDialog); - } - - /** - * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation - * update activity that indicates one or more users other than the bot are joining the - * conversation, it calls this method. - * - * @param membersAdded A list of all the members added to the conversation, as described by the - * conversation update activity - * @param turnContext The context object for this turn. - * @return A task that represents the work queued to execute. - */ - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - // Greet anyone that was not the target (recipient) of this message. - // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. - Attachment welcomeCard = createAdaptiveCardAttachment(); - Activity response = MessageFactory - .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); - - return turnContext.sendActivity(response).thenApply(sendResult -> { - return Dialog.run(getDialog(), turnContext, - getConversationState().createProperty("DialogState") - ); - }); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - // Load attachment from embedded resource. - private Attachment createAdaptiveCardAttachment() { - try ( - InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") - ) { - String adaptiveCardJson = IOUtils - .toString(inputStream, StandardCharsets.UTF_8.toString()); - - Attachment attachment = new Attachment(); - attachment.setContentType("application/vnd.microsoft.card.adaptive"); - attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); - return attachment; - - } catch (IOException e) { - e.printStackTrace(); - return new Attachment(); - } - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java deleted file mode 100644 index 395d168d3..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/DialogBot.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow - * multiple different bots to be run at different endpoints within the same project. This can be - * achieved by defining distinct Controller types each with dependency on distinct Bot types. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been - * used in a Dialog implementation, and the requirement is that all BotState objects are saved at - * the end of a turn. - * - * @param parameter of a type inheriting from Dialog - */ -public class DialogBot extends ActivityHandler { - - private Dialog dialog; - private BotState conversationState; - private BotState userState; - - /** - * Gets the dialog in use. - * - * @return instance of dialog - */ - protected Dialog getDialog() { - return dialog; - } - - /** - * Gets the conversation state. - * - * @return instance of conversationState - */ - protected BotState getConversationState() { - return conversationState; - } - - /** - * Gets the user state. - * - * @return instance of userState - */ - protected BotState getUserState() { - return userState; - } - - /** - * Sets the dialog in use. - * - * @param withDialog the dialog (of Dialog type) to be set - */ - protected void setDialog(Dialog withDialog) { - dialog = withDialog; - } - - /** - * Sets the conversation state. - * - * @param withConversationState the conversationState (of BotState type) to be set - */ - protected void setConversationState(BotState withConversationState) { - conversationState = withConversationState; - } - - /** - * Sets the user state. - * - * @param withUserState the userState (of BotState type) to be set - */ - protected void setUserState(BotState withUserState) { - userState = withUserState; - } - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - this.conversationState = withConversationState; - this.userState = withUserState; - this.dialog = withDialog; - } - - /** - * Saves the BotState objects at the end of each turn. - * - * @param turnContext - * @return - */ - @Override - public CompletableFuture onTurn(TurnContext turnContext) { - return super.onTurn(turnContext) - .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) - .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); - } - - /** - * This method is executed when the turnContext receives a message activity. - * - * @param turnContext - * @return - */ - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); - - // Run the Dialog with the new message Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java deleted file mode 100644 index 0e5df9b0b..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/FlightBookingRecognizer.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.microsoft.bot.ai.luis.LuisApplication; -import com.microsoft.bot.ai.luis.LuisRecognizer; -import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; -import com.microsoft.bot.builder.Recognizer; -import com.microsoft.bot.builder.RecognizerResult; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.integration.Configuration; -import org.apache.commons.lang3.StringUtils; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of recognizing the booking information. - */ -public class FlightBookingRecognizer implements Recognizer { - - private LuisRecognizer recognizer; - - /** - * The constructor of the FlightBookingRecognizer class. - * - * @param configuration The Configuration object to use. - */ - public FlightBookingRecognizer(Configuration configuration) { - Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); - if (luisIsConfigured) { - LuisApplication luisApplication = new LuisApplication( - configuration.getProperty("LuisAppId"), - configuration.getProperty("LuisAPIKey"), - String.format("https://%s", configuration.getProperty("LuisAPIHostName")) - ); - // Set the recognizer options depending on which endpoint version you want to use. - // More details can be found in - // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); - recognizerOptions.setIncludeInstanceData(true); - - this.recognizer = new LuisRecognizer(recognizerOptions); - } - } - - /** - * Verify if the recognizer is configured. - * - * @return True if it's configured, False if it's not. - */ - public Boolean isConfigured() { - return this.recognizer != null; - } - - /** - * Return an object with preformatted LUIS results for the bot's dialogs to consume. - * - * @param context A {link TurnContext} - * @return A {link RecognizerResult} - */ - public CompletableFuture executeLuisQuery(TurnContext context) { - // Returns true if luis is configured in the application.properties and initialized. - return this.recognizer.recognize(context); - } - - /** - * Gets the From data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the From data. - */ - public ObjectNode getFromEntities(RecognizerResult result) { - String fromValue = "", fromAirportValue = ""; - if (result.getEntities().get("$instance").get("From") != null) { - fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") - .asText(); - } - if (!fromValue.isEmpty() - && result.getEntities().get("From").get(0).get("Airport") != null) { - fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("from", fromValue); - entitiesNode.put("airport", fromAirportValue); - return entitiesNode; - } - - /** - * Gets the To data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the To data. - */ - public ObjectNode getToEntities(RecognizerResult result) { - String toValue = "", toAirportValue = ""; - if (result.getEntities().get("$instance").get("To") != null) { - toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); - } - if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { - toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("to", toValue); - entitiesNode.put("airport", toAirportValue); - return entitiesNode; - } - - /** - * This value will be a TIMEX. And we are only interested in a Date so grab the first result and - * drop the Time part. TIMEX is a format that represents DateTime expressions that include some - * ambiguity. e.g. missing a Year. - * - * @param result A {link RecognizerResult} - * @return The Timex value without the Time model - */ - public String getTravelDate(RecognizerResult result) { - JsonNode datetimeEntity = result.getEntities().get("datetime"); - if (datetimeEntity == null || datetimeEntity.get(0) == null) { - return null; - } - - JsonNode timex = datetimeEntity.get(0).get("timex"); - if (timex == null || timex.get(0) == null) { - return null; - } - - String datetime = timex.get(0).asText().split("T")[0]; - return datetime; - } - - /** - * Runs an utterance through a recognizer and returns a generic recognizer result. - * - * @param turnContext Turn context. - * @return Analysis of utterance. - */ - @Override - public CompletableFuture recognize(TurnContext turnContext) { - return this.recognizer.recognize(turnContext); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java deleted file mode 100644 index 919985175..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/MainDialog.java +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.commons.lang3.StringUtils; - -/** - * The class containing the main dialog for the sample. - */ -public class MainDialog extends ComponentDialog { - - private final FlightBookingRecognizer luisRecognizer; - private final Integer plusDayValue = 7; - - /** - * The constructor of the Main Dialog class. - * - * @param withLuisRecognizer The FlightBookingRecognizer object. - * @param bookingDialog The BookingDialog object with booking dialogs. - */ - public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog bookingDialog) { - super("MainDialog"); - - luisRecognizer = withLuisRecognizer; - - addDialog(new TextPrompt("TextPrompt")); - addDialog(bookingDialog); - WaterfallStep[] waterfallSteps = { - this::introStep, - this::actStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - /** - * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a - * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the - * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture introStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - Activity text = MessageFactory.text("NOTE: LUIS is not configured. " - + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " - + "to the appsettings.json file.", null, InputHints.IGNORING_INPUT); - return stepContext.getContext().sendActivity(text) - .thenCompose(sendResult -> stepContext.next(null)); - } - - // Use the text provided in FinalStepAsync or the default if it is the first time. - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); - String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); - String messageText = stepContext.getOptions() != null - ? stepContext.getOptions().toString() - : String.format("What can I help you with today?\n" - + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - /** - * Second step in the waterfall. This will use LUIS to attempt to extract the origin, - * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect - * any remaining details. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture actStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. - return stepContext.beginDialog("BookingDialog", new BookingDetails()); - } - - // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) - return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { - switch (luisResult.getTopScoringIntent().intent) { - case "BookFlight": - // Extract the values for the composite entities from the LUIS result. - ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); - ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); - - // Show a warning for Origin and Destination if we can't resolve them. - return showWarningForUnsupportedCities( - stepContext.getContext(), fromEntities, toEntities) - .thenCompose(showResult -> { - // Initialize BookingDetails with any entities we may have found in the response. - - BookingDetails bookingDetails = new BookingDetails(); - bookingDetails.setDestination(toEntities.get("airport").asText()); - bookingDetails.setOrigin(fromEntities.get("airport").asText()); - bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); - // Run the BookingDialog giving it whatever details we have from the LUIS call, - // it will fill out the remainder. - return stepContext.beginDialog("BookingDialog", bookingDetails); - } - ); - case "GetWeather": - // We haven't implemented the GetWeatherDialog so we just display a TODO message. - String getWeatherMessageText = "TODO: get weather flow here"; - Activity getWeatherMessage = MessageFactory - .text( - getWeatherMessageText, getWeatherMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(getWeatherMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - - default: - // Catch all for unhandled intents - String didntUnderstandMessageText = String.format( - "Sorry, I didn't get that. Please " - + " try asking in a different way (intent was %s)", - luisResult.getTopScoringIntent().intent - ); - Activity didntUnderstandMessage = MessageFactory - .text( - didntUnderstandMessageText, didntUnderstandMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(didntUnderstandMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - } - }); - } - - /** - * Shows a warning if the requested From or To cities are recognized as entities but they are - * not in the Airport entity list. In some cases LUIS will recognize the From and To composite - * entities as a valid cities but the From and To Airport values will be empty if those entity - * values can't be mapped to a canonical item in the Airport. - * - * @param turnContext A {@link WaterfallStepContext} - * @param fromEntities An ObjectNode with the entities of From object - * @param toEntities An ObjectNode with the entities of To object - * @return A task - */ - private static CompletableFuture showWarningForUnsupportedCities( - TurnContext turnContext, - ObjectNode fromEntities, - ObjectNode toEntities - ) { - List unsupportedCities = new ArrayList(); - - if (StringUtils.isNotBlank(fromEntities.get("from").asText()) - && StringUtils.isBlank(fromEntities.get("airport").asText())) { - unsupportedCities.add(fromEntities.get("from").asText()); - } - - if (StringUtils.isNotBlank(toEntities.get("to").asText()) - && StringUtils.isBlank(toEntities.get("airport").asText())) { - unsupportedCities.add(toEntities.get("to").asText()); - } - - if (!unsupportedCities.isEmpty()) { - String messageText = String.format( - "Sorry but the following airports are not supported: %s", - String.join(", ", unsupportedCities) - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - return turnContext.sendActivity(message) - .thenApply(sendResult -> null); - } - - return CompletableFuture.completedFuture(null); - } - - /** - * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" - * interaction with a simple confirmation. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - CompletableFuture stepResult = CompletableFuture.completedFuture(null); - - // If the child dialog ("BookingDialog") was cancelled, - // the user failed to confirm or if the intent wasn't BookFlight - // the Result here will be null. - if (stepContext.getResult() instanceof BookingDetails) { - // Now we have all the booking details call the booking service. - // If the call to the booking service was successful tell the user. - BookingDetails result = (BookingDetails) stepContext.getResult(); - TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); - String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); - String messageText = String.format("I have you booked to %s from %s on %s", - result.getDestination(), result.getOrigin(), travelDateMsg - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); - } - - // Restart the main dialog with a different message the second time around - String promptMessage = "What else can I do for you?"; - return stepResult - .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java deleted file mode 100644 index 3b99d3596..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/main/java/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. - -/** - * This package contains the classes for the core-bot sample. - */ -package <%= packageName %>; diff --git a/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java b/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java deleted file mode 100644 index 675c5e987..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/core/src/test/java/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md deleted file mode 100644 index 0d514610a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# <%= botName %> - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update -- `MicrosoftAppPassword` with the botsecret value -- `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml deleted file mode 100644 index 4154eca8a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/pom.xml +++ /dev/null @@ -1,253 +0,0 @@ - - - - 4.0.0 - - <%= packageName %> - <%= artifact %> - 1.0.0 - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Echo. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - 1.8 - 1.8 - 1.8 - <%= packageName %>.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.6.0-preview8 - compile - - - - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - - - - build - - true - - - - - src/main/resources - false - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - <%= packageName %>.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json deleted file mode 100644 index ad838e77f..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/resources/log4j2.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": { - "pattern": - "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" - } - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": { "ref": "Console-Appender", "level": "debug" } - } - } - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html deleted file mode 100644 index 88b6eaf65..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/project/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - <%= botName %> - - - - - -
-
-
-
<%= botName %>
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java deleted file mode 100644 index aaaf60a09..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/Application.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new EchoBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java deleted file mode 100644 index 186d292f2..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/main/java/EchoBot.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.ChannelAccount; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would be added. For this - * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link - * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. - *

- */ -public class EchoBot extends ActivityHandler { - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - return turnContext.sendActivity( - MessageFactory.text("Echo: " + turnContext.getActivity().getText()) - ).thenApply(sendResult -> null); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return membersAdded.stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ).map(channel -> turnContext.sendActivity(MessageFactory.text("Hello and welcome!"))) - .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTests.java b/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTests.java deleted file mode 100644 index 37084390c..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/echo/src/test/java/ApplicationTests.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md deleted file mode 100644 index 0d514610a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# <%= botName %> - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\<%= artifact %>-1.0.0.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update -- `MicrosoftAppPassword` with the botsecret value -- `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml deleted file mode 100644 index 4154eca8a..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/pom.xml +++ /dev/null @@ -1,253 +0,0 @@ - - - - 4.0.0 - - <%= packageName %> - <%= artifact %> - 1.0.0 - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Echo. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - 1.8 - 1.8 - 1.8 - <%= packageName %>.Application - https://botbuilder.myget.org/F/botbuilder-v4-java-daily/maven/ - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.6.0-preview8 - compile - - - - - - MyGet - ${repo.url} - - - - - - ossrh - - https://oss.sonatype.org/ - - - - - - - build - - true - - - - - src/main/resources - false - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - <%= packageName %>.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - org.apache.maven.plugins - maven-site-plugin - 3.7.1 - - - org.apache.maven.plugins - maven-project-info-reports-plugin - 3.0.0 - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - org.apache.maven.plugins - maven-jar-plugin - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json deleted file mode 100644 index ad838e77f..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/resources/log4j2.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": { - "pattern": - "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n" - } - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": { "ref": "Console-Appender", "level": "debug" } - } - } - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html b/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html deleted file mode 100644 index 88b6eaf65..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/project/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - <%= botName %> - - - - - -
-
-
-
<%= botName %>
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java deleted file mode 100644 index ab23dfdb6..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/Application.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new EmptyBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java deleted file mode 100644 index cd8b47bd0..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/main/java/EmptyBot.java +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.ChannelAccount; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would be added. For this - * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link - * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. - *

- */ -public class EmptyBot extends ActivityHandler { -} diff --git a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTests.java b/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTests.java deleted file mode 100644 index 37084390c..000000000 --- a/Generator/generator-botbuilder-java/generators/app/templates/empty/src/test/java/ApplicationTests.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package <%= packageName %>; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTests { - - @Test - public void contextLoads() { - } - -} diff --git a/Generator/generator-botbuilder-java/package-lock.json b/Generator/generator-botbuilder-java/package-lock.json deleted file mode 100644 index c8350daf6..000000000 --- a/Generator/generator-botbuilder-java/package-lock.json +++ /dev/null @@ -1,3170 +0,0 @@ -{ - "name": "generator-botbuilder-java", - "version": "4.9.1", - "lockfileVersion": 1, - "requires": true, - "dependencies": { - "@babel/code-frame": { - "version": "7.12.13", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", - "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", - "requires": { - "@babel/highlight": "^7.12.13" - } - }, - "@babel/helper-validator-identifier": { - "version": "7.12.11", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.12.11.tgz", - "integrity": "sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==" - }, - "@babel/highlight": { - "version": "7.13.10", - "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.13.10.tgz", - "integrity": "sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==", - "requires": { - "@babel/helper-validator-identifier": "^7.12.11", - "chalk": "^2.0.0", - "js-tokens": "^4.0.0" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "@mrmlnc/readdir-enhanced": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/@mrmlnc/readdir-enhanced/-/readdir-enhanced-2.2.1.tgz", - "integrity": "sha512-bPHp6Ji8b41szTOcaP63VlnbbO5Ny6dwAATtY6JTjh5N2OLrb5Qk/Th5cRkRQhkWCt+EJsYrNB0MiL+Gpn6e3g==", - "optional": true, - "requires": { - "call-me-maybe": "^1.0.1", - "glob-to-regexp": "^0.3.0" - } - }, - "@nodelib/fs.stat": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz", - "integrity": "sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==", - "optional": true - }, - "@types/glob": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.1.3.tgz", - "integrity": "sha512-SEYeGAIQIQX8NN6LDKprLjbrd5dARM5EXsd8GI/A5l0apYI1fGMWgPHSe4ZKL4eozlAyI+doUE9XbYS4xCkQ1w==", - "optional": true, - "requires": { - "@types/minimatch": "*", - "@types/node": "*" - } - }, - "@types/minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-1z8k4wzFnNjVK/tlxvrWuK5WMt6mydWWP7+zvH5eFep4oj+UkrfiJTRtjCeBXNpwaA/FYqqtb4/QS4ianFpIRA==", - "optional": true - }, - "@types/node": { - "version": "14.14.36", - "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.36.tgz", - "integrity": "sha512-kjivUwDJfIjngzbhooRnOLhGYz6oRFi+L+EpMjxroDYXwDw9lHrJJ43E+dJ6KAd3V3WxWAJ/qZE9XKYHhjPOFQ==", - "optional": true - }, - "@types/normalize-package-data": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz", - "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==" - }, - "JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "optional": true, - "requires": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - } - }, - "ansi-escapes": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", - "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", - "optional": true, - "requires": { - "type-fest": "^0.21.3" - }, - "dependencies": { - "type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "optional": true - } - } - }, - "ansi-regex": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz", - "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==", - "optional": true - }, - "ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "requires": { - "color-convert": "^2.0.1" - } - }, - "arr-diff": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=", - "optional": true - }, - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "optional": true - }, - "arr-union": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=", - "optional": true - }, - "array-differ": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-3.0.0.tgz", - "integrity": "sha512-THtfYS6KtME/yIAhKjZ2ul7XI96lQGHRputJQHO80LAWQnuGP4iCIN8vdMRboGbIEYBwU33q8Tch1os2+X0kMg==", - "optional": true - }, - "array-union": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", - "optional": true, - "requires": { - "array-uniq": "^1.0.1" - } - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "optional": true - }, - "array-unique": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=", - "optional": true - }, - "arrify": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-2.0.1.tgz", - "integrity": "sha512-3duEwti880xqi4eAMN8AyR4a0ByT90zoYdLlevfrvU43vb0YZwZVfxOgxWrLXXXpyugL0hNZc9G6BiB5B3nUug==", - "optional": true - }, - "assign-symbols": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=", - "optional": true - }, - "async": { - "version": "2.6.3", - "resolved": "https://registry.npmjs.org/async/-/async-2.6.3.tgz", - "integrity": "sha512-zflvls11DCy+dQWzTW2dzuilv8Z5X/pjfmZOWba6TNIVDm+2UDaJmXSOXlasHKfNBs8oo3M0aT50fDEWfKZjXg==", - "requires": { - "lodash": "^4.17.14" - } - }, - "atob": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/atob/-/atob-2.1.2.tgz", - "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", - "optional": true - }, - "axios": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.1.tgz", - "integrity": "sha512-dKQiRHxGD9PPRIUNIWvZhPTPpl1rf/OxTYKsqKUDjBwYylTvV7SjSHJb9ratfyzM6wCdLCOYLzs73qpg5c4iGA==", - "optional": true, - "requires": { - "follow-redirects": "^1.10.0" - } - }, - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" - }, - "base": { - "version": "0.11.2", - "resolved": "https://registry.npmjs.org/base/-/base-0.11.2.tgz", - "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", - "optional": true, - "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "binaryextensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binaryextensions/-/binaryextensions-2.3.0.tgz", - "integrity": "sha512-nAihlQsYGyc5Bwq6+EsubvANYGExeJKHDO3RjnvwU042fawQTQfM3Kxn7IHUXQOz4bzfwsGYYHGSvXyW4zOGLg==" - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "braces": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", - "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", - "optional": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "cache-base": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-base/-/cache-base-1.0.1.tgz", - "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", - "optional": true, - "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" - } - }, - "call-me-maybe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz", - "integrity": "sha1-JtII6onje1y95gJQoV8DHBak1ms=", - "optional": true - }, - "capture-stack-trace": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/capture-stack-trace/-/capture-stack-trace-1.0.1.tgz", - "integrity": "sha512-mYQLZnx5Qt1JgB1WEiMCf2647plpGeQ2NMR/5L0HNZzGQo4fuSPnK+wjfPnKZV0aiJDgzmWqqkV/g7JD+DW0qw==" - }, - "chalk": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.0.0.tgz", - "integrity": "sha512-N9oWFcegS0sFr9oh1oz2d7Npos6vNoWW9HvtCg5N1KRFpUhaAhvTv5Y58g880fZaEYSNm3qDz8SU1UrGvp+n7A==", - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", - "optional": true - }, - "class-utils": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/class-utils/-/class-utils-0.3.6.tgz", - "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "cli-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", - "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", - "optional": true, - "requires": { - "restore-cursor": "^3.1.0" - } - }, - "cli-table": { - "version": "0.3.6", - "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.6.tgz", - "integrity": "sha512-ZkNZbnZjKERTY5NwC2SeMeLeifSPq/pubeRoTpdr3WchLlnZg6hEgvHkK5zL7KNFdd9PmHN8lxrENUwI3cE8vQ==", - "requires": { - "colors": "1.0.3" - } - }, - "cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", - "optional": true - }, - "clone": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", - "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "optional": true - }, - "clone-buffer": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-buffer/-/clone-buffer-1.0.0.tgz", - "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=", - "optional": true - }, - "clone-deep": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", - "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4", - "kind-of": "^6.0.2", - "shallow-clone": "^3.0.0" - } - }, - "clone-stats": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-1.0.0.tgz", - "integrity": "sha1-s3gt/4u1R04Yuba/D9/ngvh3doA=", - "optional": true - }, - "cloneable-readable": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/cloneable-readable/-/cloneable-readable-1.1.3.tgz", - "integrity": "sha512-2EF8zTQOxYq70Y4XKtorQupqF0m49MBz2/yf5Bj+MHjvpG3Hy7sImifnqD6UA+TKYxeSV+u6qqQPawN5UvnpKQ==", - "optional": true, - "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "collection-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", - "optional": true, - "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" - } - }, - "color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "requires": { - "color-name": "~1.1.4" - } - }, - "color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" - }, - "colors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", - "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=" - }, - "commondir": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", - "optional": true - }, - "component-emitter": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz", - "integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==", - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" - }, - "copy-descriptor": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=", - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "optional": true - }, - "create-error-class": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", - "requires": { - "capture-stack-trace": "^1.0.0" - } - }, - "cross-spawn": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", - "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", - "requires": { - "nice-try": "^1.0.4", - "path-key": "^2.0.1", - "semver": "^5.5.0", - "shebang-command": "^1.2.0", - "which": "^1.2.9" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "dargs": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/dargs/-/dargs-6.1.0.tgz", - "integrity": "sha512-5dVBvpBLBnPwSsYXqfybFyehMmC/EenKEcf23AhCTgTf48JFBbmJKqoZBsERDnjL0FyiVTYWdFsRfTLHxLyKdQ==" - }, - "dateformat": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-3.0.3.tgz", - "integrity": "sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q==" - }, - "debug": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", - "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", - "requires": { - "ms": "2.1.2" - } - }, - "decode-uri-component": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", - "optional": true - }, - "deep-extend": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", - "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", - "optional": true - }, - "define-property": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", - "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", - "optional": true, - "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" - }, - "dependencies": { - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "diff": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", - "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==" - }, - "dir-glob": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.2.2.tgz", - "integrity": "sha512-f9LBi5QWzIW3I6e//uxZoLBlUt9kcp66qo0sSCxL6YZKc75R1c4MFCoe/LaZiBGmgujvQdxc5Bn3QhfyvK5Hsw==", - "optional": true, - "requires": { - "path-type": "^3.0.0" - } - }, - "download-stats": { - "version": "0.3.4", - "resolved": "https://registry.npmjs.org/download-stats/-/download-stats-0.3.4.tgz", - "integrity": "sha512-ic2BigbyUWx7/CBbsfGjf71zUNZB4edBGC3oRliSzsoNmvyVx3Ycfp1w3vp2Y78Ee0eIIkjIEO5KzW0zThDGaA==", - "optional": true, - "requires": { - "JSONStream": "^1.2.1", - "lazy-cache": "^2.0.1", - "moment": "^2.15.1" - } - }, - "duplexer3": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" - }, - "editions": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/editions/-/editions-2.3.1.tgz", - "integrity": "sha512-ptGvkwTvGdGfC0hfhKg0MT+TRLRKGtUiWGBInxOm5pz7ssADezahjCUaYuZ8Dr+C05FW0AECIIPt4WBxVINEhA==", - "requires": { - "errlop": "^2.0.0", - "semver": "^6.3.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "ejs": { - "version": "2.7.4", - "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.7.4.tgz", - "integrity": "sha512-7vmuyh5+kuUyJKePhQfRQBhXV5Ce+RnaeeQArKu1EAMpL3WbgMt5WG6uQZpEVvYSSsxMXRKOewtDk9RaTKXRlA==", - "optional": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "optional": true - }, - "end-of-stream": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", - "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", - "optional": true, - "requires": { - "once": "^1.4.0" - } - }, - "errlop": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/errlop/-/errlop-2.2.0.tgz", - "integrity": "sha512-e64Qj9+4aZzjzzFpZC7p5kmm/ccCrbLhAJplhsDXQFs87XTsXwOpH4s1Io2s90Tau/8r2j9f4l/thhDevRjzxw==" - }, - "error": { - "version": "7.2.1", - "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", - "integrity": "sha512-fo9HBvWnx3NGUKMvMwB/CBCMMrfEJgbDTVDEkPygA3Bdd3lM1OyCd+rbQ8BwnpF6GdVeOLDNmyL4N5Bg80ZvdA==", - "requires": { - "string-template": "~0.2.1" - } - }, - "error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "requires": { - "is-arrayish": "^0.2.1" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" - }, - "execa": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", - "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", - "optional": true, - "requires": { - "cross-spawn": "^7.0.0", - "get-stream": "^5.0.0", - "human-signals": "^1.1.1", - "is-stream": "^2.0.0", - "merge-stream": "^2.0.0", - "npm-run-path": "^4.0.0", - "onetime": "^5.1.0", - "signal-exit": "^3.0.2", - "strip-final-newline": "^2.0.0" - }, - "dependencies": { - "cross-spawn": { - "version": "7.0.3", - "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", - "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", - "optional": true, - "requires": { - "path-key": "^3.1.0", - "shebang-command": "^2.0.0", - "which": "^2.0.1" - } - }, - "get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "optional": true, - "requires": { - "pump": "^3.0.0" - } - }, - "is-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", - "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", - "optional": true - }, - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true - }, - "shebang-command": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", - "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", - "optional": true, - "requires": { - "shebang-regex": "^3.0.0" - } - }, - "shebang-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", - "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "optional": true - }, - "which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", - "optional": true, - "requires": { - "isexe": "^2.0.0" - } - } - } - }, - "expand-brackets": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", - "optional": true, - "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } - } - }, - "extend-shallow": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", - "optional": true, - "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "optional": true, - "requires": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" - } - }, - "extglob": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-2.0.4.tgz", - "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", - "optional": true, - "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "fast-glob": { - "version": "2.2.7", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-2.2.7.tgz", - "integrity": "sha512-g1KuQwHOZAmOZMuBtHdxDtju+T2RT8jgCC9aANsbpdiDDTSnjgfuVsIBNKbUeJI3oKMRExcfNDtJl4OhbffMsw==", - "optional": true, - "requires": { - "@mrmlnc/readdir-enhanced": "^2.2.1", - "@nodelib/fs.stat": "^1.1.2", - "glob-parent": "^3.1.0", - "is-glob": "^4.0.0", - "merge2": "^1.2.3", - "micromatch": "^3.1.10" - } - }, - "figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "optional": true, - "requires": { - "escape-string-regexp": "^1.0.5" - } - }, - "fill-range": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "find-up": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", - "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", - "requires": { - "locate-path": "^3.0.0" - } - }, - "first-chunk-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-2.0.0.tgz", - "integrity": "sha1-G97NuOCDwGZLkZRVgVd6Q6nzHXA=", - "optional": true, - "requires": { - "readable-stream": "^2.0.2" - }, - "dependencies": { - "readable-stream": { - "version": "2.3.7", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", - "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "optional": true - }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - } - } - }, - "follow-redirects": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.13.3.tgz", - "integrity": "sha512-DUgl6+HDzB0iEptNQEXLx/KhTmDb8tZUHSeLqpnjpknR70H0nC2t9N73BK6fN4hOvJ84pKlIQVQ4k5FFlBedKA==", - "optional": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "optional": true - }, - "fragment-cache": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", - "optional": true, - "requires": { - "map-cache": "^0.2.2" - } - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" - }, - "function-bind": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", - "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" - }, - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=" - }, - "get-value": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=", - "optional": true - }, - "gh-got": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/gh-got/-/gh-got-5.0.0.tgz", - "integrity": "sha1-7pW+NxBv2HSKlvjR20uuqJ4b+oo=", - "requires": { - "got": "^6.2.0", - "is-plain-obj": "^1.1.0" - } - }, - "github-username": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/github-username/-/github-username-3.0.0.tgz", - "integrity": "sha1-CnciGbMTB0NCnyRW0L3T21Xc57E=", - "requires": { - "gh-got": "^5.0.0" - } - }, - "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "glob-parent": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", - "optional": true, - "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" - }, - "dependencies": { - "is-glob": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", - "optional": true, - "requires": { - "is-extglob": "^2.1.0" - } - } - } - }, - "glob-to-regexp": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.3.0.tgz", - "integrity": "sha1-jFoUlNIGbFcMw7/kSWF1rMTVAqs=", - "optional": true - }, - "globby": { - "version": "9.2.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-9.2.0.tgz", - "integrity": "sha512-ollPHROa5mcxDEkwg6bPt3QbEf4pDQSNtd6JPL1YvOvAo/7/0VAm9TccUeoTmarjPw4pfUthSCqcyfNB1I3ZSg==", - "optional": true, - "requires": { - "@types/glob": "^7.1.1", - "array-union": "^1.0.2", - "dir-glob": "^2.2.2", - "fast-glob": "^2.2.6", - "glob": "^7.1.3", - "ignore": "^4.0.3", - "pify": "^4.0.1", - "slash": "^2.0.0" - } - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", - "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" - } - }, - "graceful-fs": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", - "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", - "optional": true - }, - "grouped-queue": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/grouped-queue/-/grouped-queue-1.1.0.tgz", - "integrity": "sha512-rZOFKfCqLhsu5VqjBjEWiwrYqJR07KxIkH4mLZlNlGDfntbb4FbMyGFP14TlvRPrU9S3Hnn/sgxbC5ZeN0no3Q==", - "optional": true, - "requires": { - "lodash": "^4.17.15" - } - }, - "has": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", - "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", - "requires": { - "function-bind": "^1.1.1" - } - }, - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" - }, - "has-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", - "optional": true, - "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" - } - }, - "has-values": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" - }, - "dependencies": { - "kind-of": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" - }, - "human-signals": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", - "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", - "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", - "optional": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" - }, - "inquirer": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-7.3.3.tgz", - "integrity": "sha512-JG3eIAj5V9CwcGvuOmoo6LB9kbAYT8HXffUl6memuszlwDC/qvFAJw49XJ5NROSFNPxp3iQg1GqkFhaY/CR0IA==", - "optional": true, - "requires": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.0", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.19", - "mute-stream": "0.0.8", - "run-async": "^2.4.0", - "rxjs": "^6.6.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6" - }, - "dependencies": { - "chalk": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.0.tgz", - "integrity": "sha512-qwx12AxXe2Q5xQ43Ac//I6v5aXTipYrSESdOgzrN+9XjgEpyjpKuvSGaN4qE93f7TQTlerQQ8S+EQ0EyDoVL1A==", - "optional": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - } - }, - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "optional": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "interpret": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", - "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "optional": true - }, - "is-core-module": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", - "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", - "requires": { - "has": "^1.0.3" - } - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "optional": true - } - } - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "optional": true - }, - "is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", - "optional": true - }, - "is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "optional": true - }, - "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", - "optional": true, - "requires": { - "is-extglob": "^2.1.1" - } - }, - "is-number": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-plain-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", - "integrity": "sha1-caUMhCnfync8kqOQpKA7OfzVHT4=" - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "is-redirect": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" - }, - "is-retry-allowed": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz", - "integrity": "sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==" - }, - "is-scoped": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-scoped/-/is-scoped-1.0.0.tgz", - "integrity": "sha1-RJypgpnnEwOCViieyytUDcQ3yzA=", - "optional": true, - "requires": { - "scoped-regex": "^1.0.0" - } - }, - "is-stream": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "optional": true - }, - "is-windows": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", - "integrity": "sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==", - "optional": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "optional": true - }, - "isbinaryfile": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/isbinaryfile/-/isbinaryfile-4.0.6.tgz", - "integrity": "sha512-ORrEy+SNVqUhrCaal4hA4fBzhggQQ+BaLntyPOdoEiwlKZW9BZiJXjg3RMiruE4tPEI3pyVPpySHQF/dKWperg==", - "optional": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" - }, - "isobject": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "optional": true - }, - "istextorbinary": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/istextorbinary/-/istextorbinary-2.6.0.tgz", - "integrity": "sha512-+XRlFseT8B3L9KyjxxLjfXSLMuErKDsd8DBNrsaxoViABMEZlOSCstwmw0qpoFX3+U6yWU1yhLudAe6/lETGGA==", - "requires": { - "binaryextensions": "^2.1.2", - "editions": "^2.2.0", - "textextensions": "^2.5.0" - } - }, - "js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" - }, - "json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==" - }, - "jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha1-P02uSpH6wxX3EGL4UhzCOfE2YoA=", - "optional": true - }, - "kind-of": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", - "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", - "optional": true - }, - "lazy-cache": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-2.0.2.tgz", - "integrity": "sha1-uRkKT5EzVGlIQIWfio9whNiCImQ=", - "optional": true, - "requires": { - "set-getter": "^0.1.0" - } - }, - "lines-and-columns": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz", - "integrity": "sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=" - }, - "locate-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", - "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", - "requires": { - "p-locate": "^3.0.0", - "path-exists": "^3.0.0" - } - }, - "lodash": { - "version": "4.17.21", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "log-symbols": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", - "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", - "optional": true, - "requires": { - "chalk": "^2.0.1" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "lowercase-keys": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", - "integrity": "sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==" - }, - "lru-cache": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", - "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", - "requires": { - "yallist": "^4.0.0" - } - }, - "make-dir": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", - "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", - "requires": { - "semver": "^6.0.0" - }, - "dependencies": { - "semver": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", - "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==" - } - } - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "optional": true - }, - "map-visit": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", - "optional": true, - "requires": { - "object-visit": "^1.0.0" - } - }, - "mem-fs": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/mem-fs/-/mem-fs-1.2.0.tgz", - "integrity": "sha512-b8g0jWKdl8pM0LqAPdK9i8ERL7nYrzmJfRhxMiWH2uYdfYnb7uXnmwVb0ZGe7xyEl4lj+nLIU3yf4zPUT+XsVQ==", - "optional": true, - "requires": { - "through2": "^3.0.0", - "vinyl": "^2.0.1", - "vinyl-file": "^3.0.0" - } - }, - "mem-fs-editor": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/mem-fs-editor/-/mem-fs-editor-6.0.0.tgz", - "integrity": "sha512-e0WfJAMm8Gv1mP5fEq/Blzy6Lt1VbLg7gNnZmZak7nhrBTibs+c6nQ4SKs/ZyJYHS1mFgDJeopsLAv7Ow0FMFg==", - "optional": true, - "requires": { - "commondir": "^1.0.1", - "deep-extend": "^0.6.0", - "ejs": "^2.6.1", - "glob": "^7.1.4", - "globby": "^9.2.0", - "isbinaryfile": "^4.0.0", - "mkdirp": "^0.5.0", - "multimatch": "^4.0.0", - "rimraf": "^2.6.3", - "through2": "^3.0.1", - "vinyl": "^2.2.0" - }, - "dependencies": { - "mkdirp": { - "version": "0.5.5", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", - "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - } - } - }, - "merge-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", - "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", - "optional": true - }, - "merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "optional": true - }, - "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" - } - }, - "mimic-fn": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", - "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", - "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" - }, - "mixin-deep": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", - "integrity": "sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==", - "optional": true, - "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" - }, - "dependencies": { - "is-extendable": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "optional": true, - "requires": { - "is-plain-object": "^2.0.4" - } - } - } - }, - "mkdirp": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", - "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" - }, - "moment": { - "version": "2.29.1", - "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.1.tgz", - "integrity": "sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ==", - "optional": true - }, - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - }, - "multimatch": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/multimatch/-/multimatch-4.0.0.tgz", - "integrity": "sha512-lDmx79y1z6i7RNx0ZGCPq1bzJ6ZoDDKbvh7jxr9SJcWLkShMzXrHbYVpTdnhNM5MXpDUxCQ4DgqVttVXlBgiBQ==", - "optional": true, - "requires": { - "@types/minimatch": "^3.0.3", - "array-differ": "^3.0.0", - "array-union": "^2.1.0", - "arrify": "^2.0.1", - "minimatch": "^3.0.4" - }, - "dependencies": { - "array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "optional": true - } - } - }, - "mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", - "optional": true - }, - "nanomatch": { - "version": "1.2.13", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", - "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", - "optional": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" - } - }, - "nice-try": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", - "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" - }, - "node-fetch": { - "version": "2.6.1", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz", - "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==", - "optional": true - }, - "normalize-package-data": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", - "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", - "requires": { - "hosted-git-info": "^2.1.4", - "resolve": "^1.10.0", - "semver": "2 || 3 || 4 || 5", - "validate-npm-package-license": "^3.0.1" - }, - "dependencies": { - "semver": { - "version": "5.7.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", - "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" - } - } - }, - "npm-api": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/npm-api/-/npm-api-1.0.1.tgz", - "integrity": "sha512-4sITrrzEbPcr0aNV28QyOmgn6C9yKiF8k92jn4buYAK8wmA5xo1qL3II5/gT1r7wxbXBflSduZ2K3FbtOrtGkA==", - "optional": true, - "requires": { - "JSONStream": "^1.3.5", - "clone-deep": "^4.0.1", - "download-stats": "^0.3.4", - "moment": "^2.24.0", - "node-fetch": "^2.6.0", - "paged-request": "^2.0.1" - } - }, - "npm-run-path": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", - "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", - "optional": true, - "requires": { - "path-key": "^3.0.0" - }, - "dependencies": { - "path-key": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", - "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "optional": true - } - } - }, - "object-copy": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", - "optional": true, - "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "object-visit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", - "optional": true, - "requires": { - "isobject": "^3.0.0" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "optional": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", - "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", - "optional": true, - "requires": { - "mimic-fn": "^2.1.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "optional": true - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "paged-request": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/paged-request/-/paged-request-2.0.2.tgz", - "integrity": "sha512-NWrGqneZImDdcMU/7vMcAOo1bIi5h/pmpJqe7/jdsy85BA/s5MSaU/KlpxwW/IVPmIwBcq2uKPrBWWhEWhtxag==", - "optional": true, - "requires": { - "axios": "^0.21.1" - } - }, - "parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "requires": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - } - }, - "pascalcase": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=", - "optional": true - }, - "path-dirname": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=", - "optional": true - }, - "path-exists": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" - }, - "path-key": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" - }, - "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" - }, - "path-type": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz", - "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==", - "optional": true, - "requires": { - "pify": "^3.0.0" - }, - "dependencies": { - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true - } - } - }, - "pify": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", - "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==" - }, - "posix-character-classes": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=", - "optional": true - }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" - }, - "pretty-bytes": { - "version": "5.6.0", - "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", - "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==" - }, - "process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "optional": true - }, - "pump": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", - "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", - "optional": true, - "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "read-chunk": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/read-chunk/-/read-chunk-3.2.0.tgz", - "integrity": "sha512-CEjy9LCzhmD7nUpJ1oVOE6s/hBkejlcJEgLQHVnQznOSilOPb+kpKktlLfFDK3/WP43+F80xkUTM2VOkYoSYvQ==", - "requires": { - "pify": "^4.0.1", - "with-open-file": "^0.1.6" - } - }, - "read-pkg": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz", - "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==", - "requires": { - "@types/normalize-package-data": "^2.4.0", - "normalize-package-data": "^2.5.0", - "parse-json": "^5.0.0", - "type-fest": "^0.6.0" - } - }, - "read-pkg-up": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-5.0.0.tgz", - "integrity": "sha512-XBQjqOBtTzyol2CpsQOw8LHV0XbDZVG7xMMjmXAJomlVY03WOBRmYgDJETlvcg0H63AJvPRwT7GFi5rvOzUOKg==", - "requires": { - "find-up": "^3.0.0", - "read-pkg": "^5.0.0" - } - }, - "readable-stream": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz", - "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==", - "requires": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - } - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "requires": { - "resolve": "^1.1.6" - } - }, - "regex-not": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", - "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", - "optional": true, - "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" - } - }, - "remove-trailing-separator": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", - "optional": true - }, - "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", - "optional": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "optional": true - }, - "replace-ext": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", - "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==", - "optional": true - }, - "resolve": { - "version": "1.20.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", - "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", - "requires": { - "is-core-module": "^2.2.0", - "path-parse": "^1.0.6" - } - }, - "resolve-url": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=", - "optional": true - }, - "restore-cursor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", - "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", - "optional": true, - "requires": { - "onetime": "^5.1.0", - "signal-exit": "^3.0.2" - } - }, - "ret": { - "version": "0.1.15", - "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", - "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==", - "optional": true - }, - "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", - "requires": { - "glob": "^7.1.3" - } - }, - "run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==" - }, - "rxjs": { - "version": "6.6.6", - "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.6.6.tgz", - "integrity": "sha512-/oTwee4N4iWzAMAL9xdGKjkEHmIwupR3oXbQjCKywF1BeFohswF3vZdogbmEF6pZkOsXTzWkrZszrWpQTByYVg==", - "optional": true, - "requires": { - "tslib": "^1.9.0" - } - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" - }, - "safe-regex": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", - "optional": true, - "requires": { - "ret": "~0.1.10" - } - }, - "safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "optional": true - }, - "scoped-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/scoped-regex/-/scoped-regex-1.0.0.tgz", - "integrity": "sha1-o0a7Gs1CB65wvXwMfKnlZra63bg=", - "optional": true - }, - "semver": { - "version": "7.3.5", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", - "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", - "requires": { - "lru-cache": "^6.0.0" - } - }, - "set-getter": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/set-getter/-/set-getter-0.1.0.tgz", - "integrity": "sha1-12nBgsnVpR9AkUXy+6guXoboA3Y=", - "optional": true, - "requires": { - "to-object-path": "^0.3.0" - } - }, - "set-value": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/set-value/-/set-value-2.0.1.tgz", - "integrity": "sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==", - "optional": true, - "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" - }, - "dependencies": { - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - } - } - }, - "shallow-clone": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", - "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", - "optional": true, - "requires": { - "kind-of": "^6.0.2" - } - }, - "shebang-command": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", - "requires": { - "shebang-regex": "^1.0.0" - } - }, - "shebang-regex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" - }, - "shelljs": { - "version": "0.8.4", - "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.4.tgz", - "integrity": "sha512-7gk3UZ9kOfPLIAbslLzyWeGiEqx9e3rxwZM0KE6EL8GlGwjym9Mrlx5/p33bWTu9YG6vcS4MBxYZDHYr5lr8BQ==", - "requires": { - "glob": "^7.0.0", - "interpret": "^1.0.0", - "rechoir": "^0.6.2" - } - }, - "signal-exit": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", - "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", - "optional": true - }, - "slash": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-2.0.0.tgz", - "integrity": "sha512-ZYKh3Wh2z1PpEXWr0MpSBZ0V6mZHAQfYevttO11c51CaWjGTaadiKZ+wVt1PbMlDV5qhMFslpZCemhwOK7C89A==", - "optional": true - }, - "snapdragon": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/snapdragon/-/snapdragon-0.8.2.tgz", - "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", - "optional": true, - "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" - }, - "dependencies": { - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "optional": true, - "requires": { - "ms": "2.0.0" - } - }, - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - }, - "extend-shallow": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", - "optional": true, - "requires": { - "is-extendable": "^0.1.0" - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "optional": true - } - } - }, - "snapdragon-node": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/snapdragon-node/-/snapdragon-node-2.1.1.tgz", - "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", - "optional": true, - "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" - }, - "dependencies": { - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "optional": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, - "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", - "optional": true, - "requires": { - "kind-of": "^6.0.0" - } - }, - "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", - "optional": true, - "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" - } - } - } - }, - "snapdragon-util": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/snapdragon-util/-/snapdragon-util-3.0.1.tgz", - "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", - "optional": true, - "requires": { - "kind-of": "^3.2.0" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "source-map": { - "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", - "optional": true - }, - "source-map-resolve": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz", - "integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==", - "optional": true, - "requires": { - "atob": "^2.1.2", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" - } - }, - "source-map-url": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", - "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==", - "optional": true - }, - "spdx-correct": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", - "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", - "requires": { - "spdx-expression-parse": "^3.0.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-exceptions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", - "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" - }, - "spdx-expression-parse": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", - "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", - "requires": { - "spdx-exceptions": "^2.1.0", - "spdx-license-ids": "^3.0.0" - } - }, - "spdx-license-ids": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.7.tgz", - "integrity": "sha512-U+MTEOO0AiDzxwFvoa4JVnMV6mZlJKk2sBLt90s7G0Gd0Mlknc7kxEn3nuDPNZRta7O2uy8oLcZLVT+4sqNZHQ==" - }, - "split-string": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/split-string/-/split-string-3.1.0.tgz", - "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", - "optional": true, - "requires": { - "extend-shallow": "^3.0.0" - } - }, - "static-extend": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/static-extend/-/static-extend-0.1.2.tgz", - "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", - "optional": true, - "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" - }, - "dependencies": { - "define-property": { - "version": "0.2.5", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", - "optional": true, - "requires": { - "is-descriptor": "^0.1.0" - } - } - } - }, - "string-template": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/string-template/-/string-template-0.2.1.tgz", - "integrity": "sha1-QpMuWYo1LQH8IuwzZ9nYTuxsmt0=" - }, - "string-width": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.2.tgz", - "integrity": "sha512-XBJbT3N4JhVumXE0eoLU9DCjcaF92KLNqTmFCnG1pf8duUxFGwtP6AD6nkjw9a3IdiRtL3E2w3JDiE/xi3vOeA==", - "optional": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "strip-ansi": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz", - "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==", - "optional": true, - "requires": { - "ansi-regex": "^5.0.0" - } - } - } - }, - "string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "requires": { - "safe-buffer": "~5.2.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "optional": true, - "requires": { - "ansi-regex": "^3.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "optional": true - } - } - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "optional": true, - "requires": { - "is-utf8": "^0.2.0" - } - }, - "strip-bom-buf": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-buf/-/strip-bom-buf-1.0.0.tgz", - "integrity": "sha1-HLRar1dTD0yvhsf3UXnSyaUd1XI=", - "optional": true, - "requires": { - "is-utf8": "^0.2.1" - } - }, - "strip-bom-stream": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom-stream/-/strip-bom-stream-2.0.0.tgz", - "integrity": "sha1-+H217yYT9paKpUWr/h7HKLaoKco=", - "optional": true, - "requires": { - "first-chunk-stream": "^2.0.0", - "strip-bom": "^2.0.0" - } - }, - "strip-final-newline": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", - "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "optional": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "requires": { - "has-flag": "^4.0.0" - } - }, - "text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=" - }, - "textextensions": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/textextensions/-/textextensions-2.6.0.tgz", - "integrity": "sha512-49WtAWS+tcsy93dRt6P0P3AMD2m5PvXRhuEA0kaXos5ZLlujtYmpmFsB+QvWUSxE1ZsstmYXfQ7L40+EcQgpAQ==" - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "optional": true - }, - "through2": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/through2/-/through2-3.0.2.tgz", - "integrity": "sha512-enaDQ4MUyP2W6ZyT6EsMzqBPZaM/avg8iuo+l2d3QCs0J+6RaqkHV/2/lOwDTueBHeJ/2LG9lrLW3d5rWPucuQ==", - "requires": { - "inherits": "^2.0.4", - "readable-stream": "2 || 3" - } - }, - "timed-out": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", - "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" - }, - "tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "optional": true, - "requires": { - "os-tmpdir": "~1.0.2" - } - }, - "to-object-path": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/to-object-path/-/to-object-path-0.3.0.tgz", - "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", - "optional": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "optional": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "to-regex": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/to-regex/-/to-regex-3.0.2.tgz", - "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", - "optional": true, - "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" - } - }, - "to-regex-range": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz", - "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", - "optional": true, - "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" - } - }, - "tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "optional": true - }, - "type-fest": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz", - "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==" - }, - "union-value": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/union-value/-/union-value-1.0.1.tgz", - "integrity": "sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg==", - "optional": true, - "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^2.0.1" - } - }, - "unset-value": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unset-value/-/unset-value-1.0.0.tgz", - "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", - "optional": true, - "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" - }, - "dependencies": { - "has-value": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", - "optional": true, - "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" - }, - "dependencies": { - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "optional": true, - "requires": { - "isarray": "1.0.0" - } - } - } - }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=", - "optional": true - } - } - }, - "untildify": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-3.0.3.tgz", - "integrity": "sha512-iSk/J8efr8uPT/Z4eSUywnqyrQU7DSdMfdqK4iWEaUVVmcP5JcnpRqmVMwcwcnmI1ATFNgC5V90u09tBynNFKA==", - "optional": true - }, - "unzip-response": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/unzip-response/-/unzip-response-2.0.1.tgz", - "integrity": "sha1-0vD3N9FrBhXnKmk17QQhRXLVb5c=" - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=", - "optional": true - }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", - "requires": { - "prepend-http": "^1.0.1" - } - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", - "optional": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" - }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", - "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" - } - }, - "vinyl": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", - "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", - "optional": true, - "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" - } - }, - "vinyl-file": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/vinyl-file/-/vinyl-file-3.0.0.tgz", - "integrity": "sha1-sQTZ5ECf+jJfqt1SBkLQo7SIs2U=", - "optional": true, - "requires": { - "graceful-fs": "^4.1.2", - "pify": "^2.3.0", - "strip-bom-buf": "^1.0.0", - "strip-bom-stream": "^2.0.0", - "vinyl": "^2.0.1" - }, - "dependencies": { - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "optional": true - } - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", - "requires": { - "isexe": "^2.0.0" - } - }, - "with-open-file": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/with-open-file/-/with-open-file-0.1.7.tgz", - "integrity": "sha512-ecJS2/oHtESJ1t3ZfMI3B7KIDKyfN0O16miWxdn30zdh66Yd3LsRFebXZXq6GU4xfxLf6nVxp9kIqElb5fqczA==", - "requires": { - "p-finally": "^1.0.0", - "p-try": "^2.1.0", - "pify": "^4.0.1" - } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "yallist": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", - "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" - }, - "yeoman-environment": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/yeoman-environment/-/yeoman-environment-2.10.3.tgz", - "integrity": "sha512-pLIhhU9z/G+kjOXmJ2bPFm3nejfbH+f1fjYRSOteEXDBrv1EoJE/e+kuHixSXfCYfTkxjYsvRaDX+1QykLCnpQ==", - "optional": true, - "requires": { - "chalk": "^2.4.1", - "debug": "^3.1.0", - "diff": "^3.5.0", - "escape-string-regexp": "^1.0.2", - "execa": "^4.0.0", - "globby": "^8.0.1", - "grouped-queue": "^1.1.0", - "inquirer": "^7.1.0", - "is-scoped": "^1.0.0", - "lodash": "^4.17.10", - "log-symbols": "^2.2.0", - "mem-fs": "^1.1.0", - "mem-fs-editor": "^6.0.0", - "npm-api": "^1.0.0", - "semver": "^7.1.3", - "strip-ansi": "^4.0.0", - "text-table": "^0.2.0", - "untildify": "^3.0.3", - "yeoman-generator": "^4.8.2" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "optional": true, - "requires": { - "color-convert": "^1.9.0" - } - }, - "arrify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", - "optional": true - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "optional": true, - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "optional": true, - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", - "optional": true - }, - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "optional": true - }, - "dir-glob": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-2.0.0.tgz", - "integrity": "sha512-37qirFDz8cA5fimp9feo43fSuRo2gHwaIn6dXL8Ber1dGwUosDrGZeCCXq57WnIqE4aQ+u3eQZzsk1yOzhdwag==", - "optional": true, - "requires": { - "arrify": "^1.0.1", - "path-type": "^3.0.0" - } - }, - "globby": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/globby/-/globby-8.0.2.tgz", - "integrity": "sha512-yTzMmKygLp8RUpG1Ymu2VXPSJQZjNAZPD4ywgYEaG7e4tBJeUQBO8OpXrf1RCNcEs5alsoJYPAMiIHP0cmeC7w==", - "optional": true, - "requires": { - "array-union": "^1.0.1", - "dir-glob": "2.0.0", - "fast-glob": "^2.0.2", - "glob": "^7.1.2", - "ignore": "^3.3.5", - "pify": "^3.0.0", - "slash": "^1.0.0" - } - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "optional": true - }, - "ignore": { - "version": "3.3.10", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", - "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", - "optional": true - }, - "pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", - "optional": true - }, - "slash": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", - "optional": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "optional": true, - "requires": { - "has-flag": "^3.0.0" - } - } - } - }, - "yeoman-generator": { - "version": "4.10.1", - "resolved": "https://registry.npmjs.org/yeoman-generator/-/yeoman-generator-4.10.1.tgz", - "integrity": "sha512-QgbtHSaqBAkyJJM0heQUhT63ubCt34NBFMEBydOBUdAuy8RBvGSzeqVBSZOjdh1tSLrwWXlU3Ck6y14awinF6Q==", - "requires": { - "async": "^2.6.2", - "chalk": "^2.4.2", - "cli-table": "^0.3.1", - "cross-spawn": "^6.0.5", - "dargs": "^6.1.0", - "dateformat": "^3.0.3", - "debug": "^4.1.1", - "diff": "^4.0.1", - "error": "^7.0.2", - "find-up": "^3.0.0", - "github-username": "^3.0.0", - "grouped-queue": "^1.1.0", - "istextorbinary": "^2.5.1", - "lodash": "^4.17.11", - "make-dir": "^3.0.0", - "mem-fs-editor": "^6.0.0", - "minimist": "^1.2.5", - "pretty-bytes": "^5.2.0", - "read-chunk": "^3.2.0", - "read-pkg-up": "^5.0.0", - "rimraf": "^2.6.3", - "run-async": "^2.0.0", - "semver": "^7.2.1", - "shelljs": "^0.8.3", - "text-table": "^0.2.0", - "through2": "^3.0.1", - "yeoman-environment": "^2.9.5" - }, - "dependencies": { - "ansi-styles": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", - "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", - "requires": { - "color-convert": "^1.9.0" - } - }, - "chalk": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", - "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", - "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" - } - }, - "color-convert": { - "version": "1.9.3", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", - "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", - "requires": { - "color-name": "1.1.3" - } - }, - "color-name": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "requires": { - "has-flag": "^3.0.0" - } - } - } - } - } -} diff --git a/Generator/generator-botbuilder-java/package.json b/Generator/generator-botbuilder-java/package.json deleted file mode 100644 index cab1b039c..000000000 --- a/Generator/generator-botbuilder-java/package.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "name": "generator-botbuilder-java", - "version": "4.9.1", - "description": "A yeoman generator for creating Java bots built with Bot Framework v4", - "homepage": "https://github.com/Microsoft/BotBuilder-Samples/tree/master/generators/generator-botbuilder", - "author": { - "name": "Microsoft", - "email": "botframework@microsoft.com", - "url": "http://dev.botframework.com" - }, - "license": "SEE LICENSE IN LICENSE.md", - "repository": "https://github.com/Microsoft/BotBuilder-Samples.git", - "files": [ - "components", - "generators" - ], - "keywords": [ - "botbuilder", - "bots", - "bot framework", - "yeoman-generator", - "Microsoft AI", - "Microsoft Teams", - "Conversational AI" - ], - "dependencies": { - "chalk": "~4.0.0", - "lodash": "~4.17.15", - "mkdirp": "^1.0.4", - "yeoman-generator": "~4.10.1" - } -} diff --git a/samples/02.echo-bot/LICENSE b/samples/02.echo-bot/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/02.echo-bot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/02.echo-bot/README.md b/samples/02.echo-bot/README.md deleted file mode 100644 index b34532527..000000000 --- a/samples/02.echo-bot/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# EchoBot - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a simple bot that accepts input from the user and echoes it back. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-echo-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json b/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/02.echo-bot/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/02.echo-bot/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/02.echo-bot/pom.xml b/samples/02.echo-bot/pom.xml deleted file mode 100644 index 3547ec69a..000000000 --- a/samples/02.echo-bot/pom.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-echo - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Echo sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.echo.Application - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.echo.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java deleted file mode 100644 index cc7014c76..000000000 --- a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/Application.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.echo; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new EchoBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java b/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java deleted file mode 100644 index e34b01416..000000000 --- a/samples/02.echo-bot/src/main/java/com/microsoft/bot/sample/echo/EchoBot.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.echo; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.ChannelAccount; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would be added. For this - * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link - * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. - *

- */ -public class EchoBot extends ActivityHandler { - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - return turnContext.sendActivity( - MessageFactory.text("Echo: " + turnContext.getActivity().getText()) - ).thenApply(sendResult -> null); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return membersAdded.stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ).map(channel -> turnContext.sendActivity(MessageFactory.text("Hello and welcome!"))) - .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); - } -} diff --git a/samples/02.echo-bot/src/main/resources/application.properties b/samples/02.echo-bot/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/02.echo-bot/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/02.echo-bot/src/main/resources/log4j2.json b/samples/02.echo-bot/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/02.echo-bot/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/02.echo-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/02.echo-bot/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/02.echo-bot/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/02.echo-bot/src/main/webapp/WEB-INF/web.xml b/samples/02.echo-bot/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/02.echo-bot/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/02.echo-bot/src/main/webapp/index.html b/samples/02.echo-bot/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/02.echo-bot/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java b/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java deleted file mode 100644 index 7cab25783..000000000 --- a/samples/02.echo-bot/src/test/java/com/microsoft/bot/sample/echo/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.echo; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/03.welcome-user/LICENSE b/samples/03.welcome-user/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/03.welcome-user/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/03.welcome-user/README.md b/samples/03.welcome-user/README.md deleted file mode 100644 index fe55c6e58..000000000 --- a/samples/03.welcome-user/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Welcome users bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to welcome users when they join the conversation. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-welcomeuser-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "welcomBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="welcomBotPlan" newWebAppName="welcomBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="welcomBot" newAppServicePlanName="welcomBotPlan" appServicePlanLocation="westus" --name "welcomBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json b/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/03.welcome-user/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json b/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/03.welcome-user/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/03.welcome-user/pom.xml b/samples/03.welcome-user/pom.xml deleted file mode 100644 index 95b42ef00..000000000 --- a/samples/03.welcome-user/pom.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-welcomeuser - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Java State Management sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.welcomeuser.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.welcomeuser.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java deleted file mode 100644 index 78f74fe9d..000000000 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/Application.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.welcomeuser; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot(UserState userState) { - return new WelcomeUserBot(userState); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java deleted file mode 100644 index 87ee851ad..000000000 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserBot.java +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.welcomeuser; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.StatePropertyAccessor; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.CardImage; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.HeroCard; -import com.microsoft.bot.schema.ResourceResponse; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; - -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would - * be added. This class tracks the conversation state through a POJO saved in - * {@link UserState} and demonstrates welcome messages and state. - *

- * - * @see WelcomeUserState - */ -public class WelcomeUserBot extends ActivityHandler { - // Messages sent to the user. - private static final String WELCOMEMESSAGE = - "This is a simple Welcome Bot sample. This bot will introduce you " - + "to welcoming and greeting users. You can say 'intro' to see the " - + "introduction card. If you are running this bot in the Bot Framework " - + "Emulator, press the 'Start Over' button to simulate user joining " - + "a bot or a channel"; - - private static final String INFOMESSAGE = - "You are seeing this message because the bot received at least one " - + "'ConversationUpdate' event, indicating you (and possibly others) " - + "joined the conversation. If you are using the emulator, pressing " - + "the 'Start Over' button to trigger this event again. The specifics " - + "of the 'ConversationUpdate' event depends on the channel. You can " - + "read more information at: " + "https://aka.ms/about-botframework-welcome-user"; - - private String LOCALEMESSAGE = - "You can use the activity's GetLocale() method to welcome the user " - + "using the locale received from the channel. " - + "If you are using the Emulator, you can set this value in Settings."; - - private static final String PATTERNMESSAGE = - "It is a good pattern to use this event to send general greeting" - + "to user, explaining what your bot can do. In this example, the bot " - + "handles 'hello', 'hi', 'help' and 'intro'. Try it now, type 'hi'"; - - private static final String FIRST_WELCOME_ONE = - "You are seeing this message because this was your first message ever to this bot."; - - private static final String FIRST_WELCOME_TWO = - "It is a good practice to welcome the user and provide personal greeting. For example: Welcome %s"; - - private UserState userState; - - @Autowired - public WelcomeUserBot(UserState withUserState) { - userState = withUserState; - } - - /** - * Normal onTurn processing, with saving of state after each turn. - * - * @param turnContext The context object for this turn. Provides information - * about the incoming activity, and other data needed to - * process the activity. - * @return A future task. - */ - @Override - public CompletableFuture onTurn(TurnContext turnContext) { - return super.onTurn(turnContext) - .thenCompose(saveResult -> userState.saveChanges(turnContext)); - } - - /** - * Greet when users are added to the conversation. - * - *

Note that all channels do not send the conversation update activity. - * If you find that this bot works in the emulator, but does not in - * another channel the reason is most likely that the channel does not - * send this activity.

- * - * @param membersAdded A list of all the members added to the conversation, as - * described by the conversation update activity. - * @param turnContext The context object for this turn. - * @return A future task. - */ - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return membersAdded.stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ) - .map( - channel -> turnContext - .sendActivities( - MessageFactory.text( - "Hi there - " + channel.getName() + ". " + WELCOMEMESSAGE - ), - MessageFactory.text( - LOCALEMESSAGE - + " Current locale is " + turnContext.getActivity().getLocale()), - MessageFactory.text(INFOMESSAGE), - MessageFactory.text(PATTERNMESSAGE) - ) - ) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponses -> null); - } - - /** - * This will prompt for a user name, after which it will send info about the - * conversation. After sending information, the cycle restarts. - * - * @param turnContext The context object for this turn. - * @return A future task. - */ - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - // Get state data from UserState. - StatePropertyAccessor stateAccessor = - userState.createProperty("WelcomeUserState"); - CompletableFuture stateFuture = - stateAccessor.get(turnContext, WelcomeUserState::new); - - return stateFuture.thenApply(thisUserState -> { - if (!thisUserState.getDidBotWelcomeUser()) { - thisUserState.setDidBotWelcomeUser(true); - - String userName = turnContext.getActivity().getFrom().getName(); - return turnContext - .sendActivities( - MessageFactory.text(FIRST_WELCOME_ONE), - MessageFactory.text(String.format(FIRST_WELCOME_TWO, userName)) - ); - } else { - String text = turnContext.getActivity().getText().toLowerCase(); - switch (text) { - case "hello": - case "hi": - return turnContext.sendActivities(MessageFactory.text("You said " + text)); - - case "intro": - case "help": - return sendIntroCard(turnContext); - - default: - return turnContext.sendActivity(WELCOMEMESSAGE); - } - } - }) - // make the return value happy. - .thenApply(resourceResponse -> null); - } - - private CompletableFuture sendIntroCard(TurnContext turnContext) { - HeroCard card = new HeroCard(); - card.setTitle("Welcome to Bot Framework!"); - card.setText( - "Welcome to Welcome Users bot sample! This Introduction card " - + "is a great way to introduce your Bot to the user and suggest " - + "some things to get them started. We use this opportunity to " - + "recommend a few next steps for learning more creating and deploying bots." - ); - - CardImage image = new CardImage(); - image.setUrl("https://aka.ms/bf-welcome-card-image"); - - card.setImages(Collections.singletonList(image)); - - CardAction overviewAction = new CardAction(); - overviewAction.setType(ActionTypes.OPEN_URL); - overviewAction.setTitle("Get an overview"); - overviewAction.setText("Get an overview"); - overviewAction.setDisplayText("Get an overview"); - overviewAction.setValue( - "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - ); - - CardAction questionAction = new CardAction(); - questionAction.setType(ActionTypes.OPEN_URL); - questionAction.setTitle("Ask a question"); - questionAction.setText("Ask a question"); - questionAction.setDisplayText("Ask a question"); - questionAction.setValue("https://stackoverflow.com/questions/tagged/botframework"); - - CardAction deployAction = new CardAction(); - deployAction.setType(ActionTypes.OPEN_URL); - deployAction.setTitle("Learn how to deploy"); - deployAction.setText("Learn how to deploy"); - deployAction.setDisplayText("Learn how to deploy"); - deployAction.setValue( - "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - ); - card.setButtons(Arrays.asList(overviewAction, questionAction, deployAction)); - - Activity response = MessageFactory.attachment(card.toAttachment()); - return turnContext.sendActivity(response); - } -} diff --git a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserState.java b/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserState.java deleted file mode 100644 index c0e79cf37..000000000 --- a/samples/03.welcome-user/src/main/java/com/microsoft/bot/sample/welcomeuser/WelcomeUserState.java +++ /dev/null @@ -1,28 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.welcomeuser; - -/** - * This is the welcome state for this sample. - * - *

- * NOTE: Standard Java getters/setters must be used for properties. - * Alternatively, the Jackson JSON annotations could be used instead. If any - * methods start with "get" but aren't a property, the Jackson JSON 'JsonIgnore' - * annotation must be used. - *

- * - * @see WelcomeUserBot - */ -public class WelcomeUserState { - private boolean didBotWelcomeUser; - - public boolean getDidBotWelcomeUser() { - return didBotWelcomeUser; - } - - public void setDidBotWelcomeUser(boolean withDidWelcomUser) { - didBotWelcomeUser = withDidWelcomUser; - } -} diff --git a/samples/03.welcome-user/src/main/resources/application.properties b/samples/03.welcome-user/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/03.welcome-user/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/03.welcome-user/src/main/resources/log4j2.json b/samples/03.welcome-user/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/03.welcome-user/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/03.welcome-user/src/main/webapp/META-INF/MANIFEST.MF b/samples/03.welcome-user/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/03.welcome-user/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/03.welcome-user/src/main/webapp/WEB-INF/web.xml b/samples/03.welcome-user/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/03.welcome-user/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/03.welcome-user/src/main/webapp/index.html b/samples/03.welcome-user/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/03.welcome-user/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java b/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java deleted file mode 100644 index 693bec16e..000000000 --- a/samples/03.welcome-user/src/test/java/com/microsoft/bot/sample/welcomeuser/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.welcomeuser; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/05.multi-turn-prompt/LICENSE b/samples/05.multi-turn-prompt/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/05.multi-turn-prompt/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/05.multi-turn-prompt/README.md b/samples/05.multi-turn-prompt/README.md deleted file mode 100644 index c45ee940a..000000000 --- a/samples/05.multi-turn-prompt/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Multi-turn prompt - -Bot Framework v4 multi-turn prompt bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use the prompts classes included in `botbuilder-dialogs`. This bot will ask for the user's name and age, then store the responses. It demonstrates a multi-turn dialog flow using a text prompt, a number prompt, and state accessors to store and retrieve values. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-multiturnprompt-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json b/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/05.multi-turn-prompt/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/05.multi-turn-prompt/pom.xml b/samples/05.multi-turn-prompt/pom.xml deleted file mode 100644 index 17a72ca91..000000000 --- a/samples/05.multi-turn-prompt/pom.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-multiturnprompt - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Multi-turn Prompt sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.multiturnprompt.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.multiturnprompt.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java deleted file mode 100644 index 32ac57561..000000000 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/Application.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multiturnprompt; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - ConversationState conversationState, - UserState userState, - Dialog dialog - ) { - return new DialogBot(conversationState, userState, dialog); - } - - /** - * Returns the starting Dialog for this application. - * - *

- * The @Component annotation could be used on the Dialog class instead of this method - * with the @Bean annotation. - *

- * - * @return The Dialog implementation for this application. - */ - @Bean - public Dialog getRootDialog(UserState userState) { - return new UserProfileDialog(userState); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java deleted file mode 100644 index ddcafee17..000000000 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/DialogBot.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multiturnprompt; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import java.util.concurrent.CompletableFuture; - -/** - * This IBot implementation can run any type of Dialog. The use of type parameterization is to - * allows multiple different bots to be run at different endpoints within the same project. This - * can be achieved by defining distinct Controller types each with dependency on distinct IBot - * types, this way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have - * been used in a Dialog implementation, and the requirement is that all BotState objects are - * saved at the end of a turn. - */ -public class DialogBot extends ActivityHandler { - protected Dialog dialog; - protected BotState conversationState; - protected BotState userState; - - public DialogBot( - ConversationState withConversationState, - UserState withUserState, - Dialog withDialog - ) { - dialog = withDialog; - conversationState = withConversationState; - userState = withUserState; - } - - @Override - public CompletableFuture onTurn( - TurnContext turnContext - ) { - return super.onTurn(turnContext) - .thenCompose(result -> conversationState.saveChanges(turnContext)) - .thenCompose(result -> userState.saveChanges(turnContext)); - } - - @Override - protected CompletableFuture onMessageActivity( - TurnContext turnContext - ) { - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java deleted file mode 100644 index a04f2f72b..000000000 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfile.java +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multiturnprompt; - -import com.microsoft.bot.schema.Attachment; - -public class UserProfile { - public String transport; - public String name; - public Integer age; - public Attachment picture; -} diff --git a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java b/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java deleted file mode 100644 index ade706bd8..000000000 --- a/samples/05.multi-turn-prompt/src/main/java/com/microsoft/bot/sample/multiturnprompt/UserProfileDialog.java +++ /dev/null @@ -1,222 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multiturnprompt; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.StatePropertyAccessor; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.connector.Channels; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.choices.ChoiceFactory; -import com.microsoft.bot.dialogs.choices.FoundChoice; -import com.microsoft.bot.dialogs.prompts.AttachmentPrompt; -import com.microsoft.bot.dialogs.prompts.ChoicePrompt; -import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; -import com.microsoft.bot.dialogs.prompts.NumberPrompt; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Attachment; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import org.apache.commons.lang3.StringUtils; - -public class UserProfileDialog extends ComponentDialog { - private StatePropertyAccessor userProfileAccessor; - - public UserProfileDialog(UserState withUserState) { - super("UserProfileDialog"); - - userProfileAccessor = withUserState.createProperty("UserProfile"); - - WaterfallStep[] waterfallSteps = { - UserProfileDialog::transportStep, - UserProfileDialog::nameStep, - UserProfileDialog::nameConfirmStep, - UserProfileDialog::ageStep, - UserProfileDialog::pictureStep, - UserProfileDialog::confirmStep, - this::summaryStep - }; - - // Add named dialogs to the DialogSet. These names are saved in the dialog state. - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - addDialog(new TextPrompt("TextPrompt")); - addDialog(new NumberPrompt("NumberPrompt", UserProfileDialog::agePromptValidator, Integer.class)); - addDialog(new ChoicePrompt("ChoicePrompt")); - addDialog(new ConfirmPrompt("ConfirmPrompt")); - addDialog(new AttachmentPrompt("AttachmentPrompt", UserProfileDialog::picturePromptValidator)); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private static CompletableFuture transportStep(WaterfallStepContext stepContext) { - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. - // Running a prompt here means the next WaterfallStep will be run when the user's response is received. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Please enter your mode of transport.")); - promptOptions.setChoices(ChoiceFactory.toChoices("Car", "Bus", "Bicycle")); - - return stepContext.prompt("ChoicePrompt", promptOptions); - } - - private static CompletableFuture nameStep(WaterfallStepContext stepContext) { - stepContext.getValues().put("transport", ((FoundChoice) stepContext.getResult()).getValue()); - - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Please enter your name.")); - return stepContext.prompt("TextPrompt", promptOptions); - } - - private static CompletableFuture nameConfirmStep(WaterfallStepContext stepContext) { - stepContext.getValues().put("name", stepContext.getResult()); - - // We can send messages to the user at any point in the WaterfallStep. - return stepContext.getContext().sendActivity(MessageFactory.text(String.format("Thanks %s", stepContext.getResult()))) - .thenCompose(resourceResponse -> { - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Would you like to give your age?")); - return stepContext.prompt("ConfirmPrompt", promptOptions); - }); - } - - private static CompletableFuture ageStep(WaterfallStepContext stepContext) { - if ((Boolean)stepContext.getResult()) { - // User said "yes" so we will be prompting for the age. - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Please enter your age.")); - promptOptions.setRetryPrompt(MessageFactory.text("The value entered must be greater than 0 and less than 150.")); - - return stepContext.prompt("NumberPrompt", promptOptions); - } - - // User said "no" so we will skip the next step. Give -1 as the age. - return stepContext.next(-1); - } - - private static CompletableFuture pictureStep(WaterfallStepContext stepContext) { - stepContext.getValues().put("age", (Integer) stepContext.getResult()); - - String msg = (Integer)stepContext.getValues().get("age") == -1 - ? "No age given." - : String.format("I have your age as %d.", (Integer)stepContext.getValues().get("age")); - - // We can send messages to the user at any point in the WaterfallStep. - return stepContext.getContext().sendActivity(MessageFactory.text(msg)) - .thenCompose(resourceResponse -> { - if (StringUtils.equals(stepContext.getContext().getActivity().getChannelId(), Channels.MSTEAMS)) { - // This attachment prompt example is not designed to work for Teams attachments, so skip it in this case - return stepContext.getContext().sendActivity(MessageFactory.text("Skipping attachment prompt in Teams channel...")) - .thenCompose(resourceResponse1 -> stepContext.next(null)); - } - - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Please attach a profile picture (or type any message to skip).")); - promptOptions.setRetryPrompt(MessageFactory.text("The attachment must be a jpeg/png image file.")); - - return stepContext.prompt("AttachmentPrompt", promptOptions); - }); - } - - private static CompletableFuture confirmStep(WaterfallStepContext stepContext) { - List attachments = (List)stepContext.getResult(); - stepContext.getValues().put("picture", attachments == null ? null : attachments.get(0)); - - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is a Prompt Dialog. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("Is this ok?")); - return stepContext.prompt("ConfirmPrompt", promptOptions); - } - - private CompletableFuture summaryStep(WaterfallStepContext stepContext) { - if ((Boolean)stepContext.getResult()) { - // Get the current profile object from user state. - return userProfileAccessor.get(stepContext.getContext(), () -> new UserProfile()) - .thenCompose(userProfile -> { - userProfile.transport = (String) stepContext.getValues().get("transport"); - userProfile.name = (String) stepContext.getValues().get("name"); - userProfile.age = (Integer) stepContext.getValues().get("age"); - userProfile.picture = (Attachment) stepContext.getValues().get("picture"); - - String msg = String.format( - "I have your mode of transport as %s and your name as %s", - userProfile.transport, userProfile.name - ); - - if (userProfile.age != -1) { - msg += String.format(" and your age as %s", userProfile.age); - } - - msg += "."; - - return stepContext.getContext().sendActivity(MessageFactory.text(msg)) - .thenApply(resourceResponse -> userProfile); - }) - .thenCompose(userProfile -> { - if (userProfile.picture != null) { - return stepContext.getContext().sendActivity( - MessageFactory.attachment(userProfile.picture, - "This is your profile picture." - )); - } - - return stepContext.getContext().sendActivity( - MessageFactory.text("A profile picture wasn't attached.") - ); - }) - .thenCompose(resourceResponse -> stepContext.endDialog()); - } - - // WaterfallStep always finishes with the end of the Waterfall or with another dialog; here it is the end. - return stepContext.getContext().sendActivity(MessageFactory.text("Thanks. Your profile will not be kept.")) - .thenCompose(resourceResponse -> stepContext.endDialog()); - } - - private static CompletableFuture picturePromptValidator( - PromptValidatorContext> promptContext - ) { - if (promptContext.getRecognized().getSucceeded()) { - List attachments = promptContext.getRecognized().getValue(); - List validImages = new ArrayList<>(); - - for (Attachment attachment : attachments) { - if (StringUtils.equals( - attachment.getContentType(), "image/jpeg") || StringUtils.equals(attachment.getContentType(), "image/png") - ) { - validImages.add(attachment); - } - } - - promptContext.getRecognized().setValue(validImages); - - // If none of the attachments are valid images, the retry prompt should be sent. - return CompletableFuture.completedFuture(!validImages.isEmpty()); - } - else { - // We can return true from a validator function even if Recognized.Succeeded is false. - return promptContext.getContext().sendActivity("No attachments received. Proceeding without a profile picture...") - .thenApply(resourceResponse -> true); - } - } - - private static CompletableFuture agePromptValidator( - PromptValidatorContext promptContext - ) { - // This condition is our validation rule. You can also change the value at this point. - return CompletableFuture.completedFuture( - promptContext.getRecognized().getSucceeded() - && promptContext.getRecognized().getValue() > 0 - && promptContext.getRecognized().getValue() < 150); - } -} diff --git a/samples/05.multi-turn-prompt/src/main/resources/application.properties b/samples/05.multi-turn-prompt/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/05.multi-turn-prompt/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/05.multi-turn-prompt/src/main/resources/log4j2.json b/samples/05.multi-turn-prompt/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/05.multi-turn-prompt/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF b/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/05.multi-turn-prompt/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml b/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/05.multi-turn-prompt/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/05.multi-turn-prompt/src/main/webapp/index.html b/samples/05.multi-turn-prompt/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/05.multi-turn-prompt/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java b/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java deleted file mode 100644 index ced035bd3..000000000 --- a/samples/05.multi-turn-prompt/src/test/java/com/microsoft/bot/sample/multiturnprompt/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multiturnprompt; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/06.using-cards/LICENSE b/samples/06.using-cards/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/06.using-cards/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/06.using-cards/README.md b/samples/06.using-cards/README.md deleted file mode 100644 index dae398417..000000000 --- a/samples/06.using-cards/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Using Cards - -Bot Framework v4 using cards bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses rich cards to enhance your bot design. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-usingcards-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Interacting with the bot - -Most channels support rich content. In this sample we explore the different types of rich cards your bot may use. A key to good bot design is to send interactive media, such as Rich Cards. There are several different types of Rich Cards, which are as follows: - -- Animation Card -- Audio Card -- Hero Card -- Receipt Card -- Sign In Card -- Thumbnail Card -- Video Card - -When [designing the user experience](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-design-user-experience?view=azure-bot-service-4.0#cards) developers should consider adding visual elements such as Rich Cards. - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "usingCardsBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="usingCardsBotPlan" newWebAppName="usingCardsBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="usingCardsBot" newAppServicePlanName="usingCardsBotPlan" appServicePlanLocation="westus" --name "usingCardsBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Rich Cards](https://docs.microsoft.com/azure/bot-service/bot-builder-howto-add-media-attachments?view=azure-bot-service-4.0&tabs=csharp#send-a-hero-card) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/06.using-cards/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json b/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/06.using-cards/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/06.using-cards/pom.xml b/samples/06.using-cards/pom.xml deleted file mode 100644 index 23a004d35..000000000 --- a/samples/06.using-cards/pom.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-usingcards - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Using Cards sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.usingcards.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.usingcards.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java deleted file mode 100644 index 9db23c06f..000000000 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Application.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - ConversationState conversationState, - UserState userState, - Dialog rootDialog - ) { - return new RichCardsBot(conversationState, userState, rootDialog); - } - - /** - * Returns the starting Dialog for this application. - * - *

- * The @Component annotation could be used on the Dialog class instead of this method - * with the @Bean annotation. - *

- * - * @return The Dialog implementation for this application. - */ - @Bean - public Dialog getRootDialog() { - return new MainDialog(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java deleted file mode 100644 index 1cf86be65..000000000 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/Cards.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.AnimationCard; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.AudioCard; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.CardImage; -import com.microsoft.bot.schema.Fact; -import com.microsoft.bot.schema.HeroCard; -import com.microsoft.bot.schema.MediaUrl; -import com.microsoft.bot.schema.OAuthCard; -import com.microsoft.bot.schema.ReceiptCard; -import com.microsoft.bot.schema.ReceiptItem; -import com.microsoft.bot.schema.SigninCard; -import com.microsoft.bot.schema.ThumbnailCard; -import com.microsoft.bot.schema.ThumbnailUrl; -import com.microsoft.bot.schema.VideoCard; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.concurrent.CompletionException; -import org.apache.commons.io.IOUtils; - -public class Cards { - public static Attachment createAdaptiveCardAttachment() { - Attachment adaptiveCardAttachment = new Attachment(); - - try ( - InputStream inputStream = adaptiveCardAttachment.getClass().getClassLoader() - .getResourceAsStream("adaptiveCard.json") - ) { - String result = IOUtils.toString(inputStream, StandardCharsets.UTF_8); - - adaptiveCardAttachment.setContentType("application/vnd.microsoft.card.adaptive"); - adaptiveCardAttachment.setContent(new ObjectMapper().readValue(result, ObjectNode.class)); - - return adaptiveCardAttachment; - } catch (Throwable t) { - throw new CompletionException(t); - } - } - - public static HeroCard getHeroCard() { - HeroCard heroCard = new HeroCard(); - heroCard.setTitle("BotFramework Hero Card"); - heroCard.setSubtitle("Microsoft Bot Framework"); - heroCard.setText("Build and connect intelligent bots to interact with your users naturally wherever they are," + - " from text/sms to Skype, Slack, Office 365 mail and other popular services."); - heroCard.setImages(new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg")); - heroCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Get Started", "https://docs.microsoft.com/bot-framework")); - - return heroCard; - } - - public static ThumbnailCard getThumbnailCard() { - ThumbnailCard thumbnailCard = new ThumbnailCard(); - thumbnailCard.setTitle("BotFramework Thumbnail Card"); - thumbnailCard.setSubtitle("Microsoft Bot Framework"); - thumbnailCard.setText("Build and connect intelligent bots to interact with your users naturally wherever they are," + - " from text/sms to Skype, Slack, Office 365 mail and other popular services."); - thumbnailCard.setImages(new CardImage("https://sec.ch9.ms/ch9/7ff5/e07cfef0-aa3b-40bb-9baa-7c9ef8ff7ff5/buildreactionbotframework_960.jpg")); - thumbnailCard.setButtons(new CardAction( - ActionTypes.OPEN_URL, "Get Started", "https://docs.microsoft.com/bot-framework")); - - return thumbnailCard; - } - - public static ReceiptCard getReceiptCard() { - ReceiptCard receiptCard = new ReceiptCard(); - receiptCard.setTitle("John Doe"); - ReceiptItem receiptDataTransfer = new ReceiptItem(); - receiptDataTransfer.setTitle("Data Transfer"); - receiptDataTransfer.setPrice("$ 38.45"); - receiptDataTransfer.setQuantity("368"); - receiptDataTransfer.setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/traffic-manager.png")); - ReceiptItem receiptAppService = new ReceiptItem(); - receiptAppService.setTitle("App Service"); - receiptAppService.setPrice("$ 45.00"); - receiptAppService.setQuantity("720"); - receiptAppService.setImage(new CardImage("https://github.com/amido/azure-vector-icons/raw/master/renders/cloud-service.png")); - receiptCard.setFacts(new Fact("Order Number", "1234"), new Fact("Payment Method", "VISA 5555-****")); - receiptCard.setItems(receiptDataTransfer, receiptAppService); - receiptCard.setTax("$ 7.50"); - receiptCard.setTotal("$ 90.95"); - CardAction cardAction = new CardAction(ActionTypes.OPEN_URL, "More information"); - cardAction.setImage("https://account.windowsazure.com/content/6.10.1.38-.8225.160809-1618/aux-pre/images/offer-icon-freetrial.png"); - cardAction.setValue("https://azure.microsoft.com/en-us/pricing/"); - receiptCard.setButtons(cardAction); - - return receiptCard; - } - - public static SigninCard getSigninCard() { - SigninCard signinCard = new SigninCard(); - signinCard.setText("BotFramework Sign-in Card"); - signinCard.setButtons(new CardAction(ActionTypes.SIGNIN, "Sign-in", "https://login.microsoftonline.com/")); - return signinCard; - } - - public static AnimationCard getAnimationCard() { - AnimationCard animationCard = new AnimationCard(); - animationCard.setTitle("Microsoft Bot Framework"); - animationCard.setSubtitle("Animation Card"); - animationCard.setImage(new ThumbnailUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png")); - animationCard.setMedia(new MediaUrl("http://i.giphy.com/Ki55RUbOV5njy.gif")); - return animationCard; - } - - public static VideoCard getVideoCard() { - VideoCard videoCard = new VideoCard(); - videoCard.setTitle("Big Buck Bunny"); - videoCard.setSubtitle("by the Blender Institute"); - videoCard.setText("Big Buck Bunny (code-named Peach) is a short computer-animated comedy film by the Blender Institute," + - " part of the Blender Foundation. Like the foundation's previous film Elephants Dream," + - " the film was made using Blender, a free software application for animation made by the same foundation." + - " It was released as an open-source film under Creative Commons License Attribution 3.0."); - videoCard.setImage(new ThumbnailUrl("https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Big_buck_bunny_poster_big.jpg/220px-Big_buck_bunny_poster_big.jpg")); - videoCard.setMedia(new MediaUrl("http://download.blender.org/peach/bigbuckbunny_movies/BigBuckBunny_320x180.mp4")); - videoCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Learn More", "https://peach.blender.org/")); - - return videoCard; - } - - public static AudioCard getAudioCard() { - AudioCard audioCard = new AudioCard(); - audioCard.setTitle("I am your father"); - audioCard.setSubtitle("Star Wars: Episode V - The Empire Strikes Back"); - audioCard.setText("The Empire Strikes Back (also known as Star Wars: Episode V – The Empire Strikes Back)" + - " is a 1980 American epic space opera film directed by Irvin Kershner. Leigh Brackett and" + - " Lawrence Kasdan wrote the screenplay, with George Lucas writing the film's story and serving" + - " as executive producer. The second installment in the original Star Wars trilogy, it was produced" + - " by Gary Kurtz for Lucasfilm Ltd. and stars Mark Hamill, Harrison Ford, Carrie Fisher, Billy Dee Williams," + - " Anthony Daniels, David Prowse, Kenny Baker, Peter Mayhew and Frank Oz."); - audioCard.setImage(new ThumbnailUrl("https://upload.wikimedia.org/wikipedia/en/3/3c/SW_-_Empire_Strikes_Back.jpg")); - audioCard.setMedia(new MediaUrl("http://www.wavlist.com/movies/004/father.wav")); - audioCard.setButtons(new CardAction(ActionTypes.OPEN_URL, "Read More", "https://en.wikipedia.org/wiki/The_Empire_Strikes_Back")); - - return audioCard; - } - - public static OAuthCard getOAuthCard() { - OAuthCard oauthCard = new OAuthCard(); - oauthCard.setText("BotFramework OAuth Card"); - oauthCard.setConnectionName("OAuth connection"); // Replace with the name of your Azure AD connection. - oauthCard.setButtons(new CardAction(ActionTypes.SIGNIN, "Sign In", "https://example.org/signin")); - return oauthCard; - } -} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java deleted file mode 100644 index 68aacb03d..000000000 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/DialogBot.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to - * allows multiple different bots to be run at different endpoints within the same project. This - * can be achieved by defining distinct Controller types each with dependency on distinct IBot - * types, this way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have - * been used in a Dialog implementation, and the requirement is that all BotState objects are - * saved at the end of a turn. - */ -public class DialogBot extends ActivityHandler { - protected Dialog dialog; - protected BotState conversationState; - protected BotState userState; - - public DialogBot( - ConversationState withConversationState, - UserState withUserState, - T withDialog - ) { - dialog = withDialog; - conversationState = withConversationState; - userState = withUserState; - } - - @Override - public CompletableFuture onTurn( - TurnContext turnContext - ) { - return super.onTurn(turnContext) - .thenCompose(result -> conversationState.saveChanges(turnContext)) - .thenCompose(result -> userState.saveChanges(turnContext)); - } - - @Override - protected CompletableFuture onMessageActivity( - TurnContext turnContext - ) { - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java deleted file mode 100644 index c6510175f..000000000 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/MainDialog.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.choices.Choice; -import com.microsoft.bot.dialogs.choices.FoundChoice; -import com.microsoft.bot.dialogs.prompts.ChoicePrompt; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.AttachmentLayoutTypes; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -public class MainDialog extends ComponentDialog { - public MainDialog() { - super("MainDialog"); - - WaterfallStep[] waterfallSteps = { - this::choiceCardStep, - this::showCardStep - }; - - // Define the main dialog and its related components. - addDialog(new ChoicePrompt("ChoicePrompt")); - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - // 1. Prompts the user if the user is not in the middle of a dialog. - // 2. Re-prompts the user when an invalid input is received. - private CompletableFuture choiceCardStep(WaterfallStepContext stepContext) { - // Create the PromptOptions which contain the prompt and re-prompt messages. - // PromptOptions also contains the list of choices available to the user. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(MessageFactory.text("What card would you like to see? You can click or type the card name")); - promptOptions.setRetryPrompt(MessageFactory.text("That was not a valid choice, please select a card or number from 1 to 9.")); - promptOptions.setChoices(getChoices()); - - return stepContext.prompt("ChoicePrompt", promptOptions); - } - - // Send a Rich Card response to the user based on their choice. - // This method is only called when a valid prompt response is parsed from the user's response to the ChoicePrompt. - private CompletableFuture showCardStep(WaterfallStepContext stepContext) { - List attachments = new ArrayList<>(); - Activity reply = MessageFactory.attachment(attachments); - - switch (((FoundChoice) stepContext.getResult()).getValue()) { - case "Adaptive Card": - // Display an Adaptive Card - reply.getAttachments().add(Cards.createAdaptiveCardAttachment()); - break; - case "Animation Card": - // Display an AnimationCard. - reply.getAttachments().add(Cards.getAnimationCard().toAttachment()); - break; - case "Audio Card": - // Display an AudioCard - reply.getAttachments().add(Cards.getAudioCard().toAttachment()); - break; - case "Hero Card": - // Display a HeroCard. - reply.getAttachments().add(Cards.getHeroCard().toAttachment()); - break; - case "OAuth Card": - // Display an OAuthCard - reply.getAttachments().add(Cards.getOAuthCard().toAttachment()); - break; - case "Receipt Card": - // Display a ReceiptCard. - reply.getAttachments().add(Cards.getReceiptCard().toAttachment()); - break; - case "Signin Card": - // Display a SignInCard. - reply.getAttachments().add(Cards.getSigninCard().toAttachment()); - break; - case "Thumbnail Card": - // Display a ThumbnailCard. - reply.getAttachments().add(Cards.getThumbnailCard().toAttachment()); - break; - case "Video Card": - // Display a VideoCard - reply.getAttachments().add(Cards.getVideoCard().toAttachment()); - break; - default: - // Display a carousel of all the rich card types. - reply.setAttachmentLayout(AttachmentLayoutTypes.CAROUSEL); - reply.getAttachments().add(Cards.createAdaptiveCardAttachment()); - reply.getAttachments().add(Cards.getAnimationCard().toAttachment()); - reply.getAttachments().add(Cards.getAudioCard().toAttachment()); - reply.getAttachments().add(Cards.getHeroCard().toAttachment()); - reply.getAttachments().add(Cards.getOAuthCard().toAttachment()); - reply.getAttachments().add(Cards.getReceiptCard().toAttachment()); - reply.getAttachments().add(Cards.getSigninCard().toAttachment()); - reply.getAttachments().add(Cards.getThumbnailCard().toAttachment()); - reply.getAttachments().add(Cards.getVideoCard().toAttachment()); - break; - } - - // Send the card(s) to the user as an attachment to the activity - return stepContext.getContext().sendActivity(reply) - .thenCompose(resourceResponse -> stepContext.getContext().sendActivity( - // Give the user instructions about what to do next - MessageFactory.text("Type anything to see another card.") - )) - .thenCompose(resourceResponse -> stepContext.endDialog()); - } - - private List getChoices() { - return Arrays.asList( - new Choice("Adaptive Card", "adaptive"), - new Choice("Animation Card", "animation"), - new Choice("Audio Card", "audio"), - new Choice("Hero Card", "hero"), - new Choice("OAuth Card", "oauth"), - new Choice("Receipt Card", "receipt"), - new Choice("Signin Card", "signin"), - new Choice("Thumbnail Card", "thumbnail", "thumb"), - new Choice("Video Card", "video"), - new Choice("All cards", "all") - ); - } -} diff --git a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java b/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java deleted file mode 100644 index 70235e3d1..000000000 --- a/samples/06.using-cards/src/main/java/com/microsoft/bot/sample/usingcards/RichCardsBot.java +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ChannelAccount; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import org.apache.commons.lang3.StringUtils; - -// RichCardsBot prompts a user to select a Rich Card and then returns the card -// that matches the user's selection. -public class RichCardsBot extends DialogBot { - - public RichCardsBot( - ConversationState withConversationState, - UserState withUserState, - Dialog withDialog - ) { - super(withConversationState, withUserState, withDialog); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - Activity reply = MessageFactory.text("Welcome to CardBot." - + " This bot will show you different types of Rich Cards." - + " Please type anything to get started."); - - return turnContext.sendActivity(reply); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } -} diff --git a/samples/06.using-cards/src/main/resources/adaptiveCard.json b/samples/06.using-cards/src/main/resources/adaptiveCard.json deleted file mode 100644 index 1888ceefc..000000000 --- a/samples/06.using-cards/src/main/resources/adaptiveCard.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.0", - "type": "AdaptiveCard", - "speak": "Your flight is confirmed for you and 3 other passengers from San Francisco to Amsterdam on Friday, October 10 8:30 AM", - "body": [ - { - "type": "TextBlock", - "text": "Passengers", - "weight": "bolder", - "isSubtle": false - }, - { - "type": "TextBlock", - "text": "Sarah Hum", - "separator": true - }, - { - "type": "TextBlock", - "text": "Jeremy Goldberg", - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "Evan Litvak", - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "2 Stops", - "weight": "bolder", - "spacing": "medium" - }, - { - "type": "TextBlock", - "text": "Fri, October 10 8:30 AM", - "weight": "bolder", - "spacing": "none" - }, - { - "type": "ColumnSet", - "separator": true, - "columns": [ - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "text": "San Francisco", - "isSubtle": true - }, - { - "type": "TextBlock", - "size": "extraLarge", - "color": "accent", - "text": "SFO", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "TextBlock", - "text": " " - }, - { - "type": "Image", - "url": "http://adaptivecards.io/content/airplane.png", - "size": "small", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "Amsterdam", - "isSubtle": true - }, - { - "type": "TextBlock", - "horizontalAlignment": "right", - "size": "extraLarge", - "color": "accent", - "text": "AMS", - "spacing": "none" - } - ] - } - ] - }, - { - "type": "TextBlock", - "text": "Non-Stop", - "weight": "bolder", - "spacing": "medium" - }, - { - "type": "TextBlock", - "text": "Fri, October 18 9:50 PM", - "weight": "bolder", - "spacing": "none" - }, - { - "type": "ColumnSet", - "separator": true, - "columns": [ - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "text": "Amsterdam", - "isSubtle": true - }, - { - "type": "TextBlock", - "size": "extraLarge", - "color": "accent", - "text": "AMS", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "TextBlock", - "text": " " - }, - { - "type": "Image", - "url": "http://adaptivecards.io/content/airplane.png", - "size": "small", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "San Francisco", - "isSubtle": true - }, - { - "type": "TextBlock", - "horizontalAlignment": "right", - "size": "extraLarge", - "color": "accent", - "text": "SFO", - "spacing": "none" - } - ] - } - ] - }, - { - "type": "ColumnSet", - "spacing": "medium", - "columns": [ - { - "type": "Column", - "width": "1", - "items": [ - { - "type": "TextBlock", - "text": "Total", - "size": "medium", - "isSubtle": true - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "$4,032.54", - "size": "medium", - "weight": "bolder" - } - ] - } - ] - } - ] -} diff --git a/samples/06.using-cards/src/main/resources/application.properties b/samples/06.using-cards/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/06.using-cards/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/06.using-cards/src/main/resources/log4j2.json b/samples/06.using-cards/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/06.using-cards/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF b/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/06.using-cards/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml b/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/06.using-cards/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/06.using-cards/src/main/webapp/index.html b/samples/06.using-cards/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/06.using-cards/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java b/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java deleted file mode 100644 index 0eb8407c0..000000000 --- a/samples/06.using-cards/src/test/java/com/microsoft/bot/sample/usingcards/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingcards; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/07.using-adaptive-cards/LICENSE b/samples/07.using-adaptive-cards/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/07.using-adaptive-cards/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/07.using-adaptive-cards/README.md b/samples/07.using-adaptive-cards/README.md deleted file mode 100644 index 3212a04ad..000000000 --- a/samples/07.using-adaptive-cards/README.md +++ /dev/null @@ -1,59 +0,0 @@ -# Using Adaptive Cards - -Bot Framework v4 using adaptive cards bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to send an Adaptive Card from the bot to the user. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\adaptive-cards-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -## Interacting with the bot - -Card authors describe their content as a simple JSON object. That content can then be rendered natively inside a host application, automatically adapting to the look and feel of the host. For example, Contoso Bot can author an Adaptive Card through the Bot Framework, and when delivered to Cortana, it will look and feel like a Cortana card. When that same payload is sent to Microsoft Teams, it will look and feel like Microsoft Teams. As more host apps start to support Adaptive Cards, that same payload will automatically light up inside these applications, yet still feel entirely native to the app. Users win because everything feels familiar. Host apps win because they control the user experience. Card authors win because their content gets broader reach without any additional work. - -The Bot Framework provides support for Adaptive Cards. See the following to learn more about Adaptive Cards. - -- [Adaptive card](http://adaptivecards.io) -- [Send an Adaptive card](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-rich-cards?view=azure-bot-service-3.0&viewFallbackFrom=azure-bot-service-4.0#send-an-adaptive-card) - -### Adding media to messages - -A message exchange between user and bot can contain media attachments, such as cards, images, video, audio, and files. - -## Deploy the bot to Azure - -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Adaptive Cards](https://adaptivecards.io/) -- [Send an Adaptive card](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-rich-cards?view=azure-bot-service-3.0&viewFallbackFrom=azure-bot-service-4.0#send-an-adaptive-card) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json b/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/07.using-adaptive-cards/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/07.using-adaptive-cards/pom.xml b/samples/07.using-adaptive-cards/pom.xml deleted file mode 100644 index 1de1703ee..000000000 --- a/samples/07.using-adaptive-cards/pom.xml +++ /dev/null @@ -1,249 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - adaptive-cards - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Adaptive Cards bot sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.usingadaptivecards.Application - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-builder - 4.13.0-SNAPSHOT - compile - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.usingadaptivecards.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java deleted file mode 100644 index 93bb1388b..000000000 --- a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/Application.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingadaptivecards; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.usingadaptivecards.bots.AdaptiveCardsBot; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new AdaptiveCardsBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java b/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java deleted file mode 100644 index 546f23a7b..000000000 --- a/samples/07.using-adaptive-cards/src/main/java/com/microsoft/bot/sample/usingadaptivecards/bots/AdaptiveCardsBot.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.usingadaptivecards.bots; - -import java.io.InputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.Random; -import java.util.concurrent.CompletableFuture; - - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.Serialization; - -import org.apache.commons.io.IOUtils; - -// This bot will respond to the user's input with an Adaptive Card. -// Adaptive Cards are a way for developers to exchange card content -// in a common and consistent way. A simple open card format enables -// an ecosystem of shared tooling, seamless integration between apps, -// and native cross-platform performance on any device. -// For each user interaction, an instance of this class instanceof created and the OnTurnAsync method instanceof called. -// This instanceof a Transient lifetime service. Transient lifetime services are created -// each time they're requested. For each Activity received, a new instance of this -// class instanceof created. Objects that are expensive to construct, or have a lifetime -// beyond the single turn, should be carefully managed. - -public class AdaptiveCardsBot extends ActivityHandler { - - private static final String welcomeText = "This bot will introduce you to AdaptiveCards. " - + "Type anything to see an AdaptiveCard."; - - // This array contains the file names of our adaptive cards - private final String[] cards = { - "FlightItineraryCard.json", - "ImageGalleryCard.json", - "LargeWeatherCard.json", - "RestaurantCard.json", - "SolitaireCard.json" - }; - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return sendWelcomeMessage(turnContext); - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - Random r = new Random(); - Attachment cardAttachment = createAdaptiveCardAttachment(cards[r.nextInt(cards.length)]); - - return turnContext.sendActivity(MessageFactory.attachment(cardAttachment)).thenCompose(result ->{ - return turnContext.sendActivity(MessageFactory.text("Please enter any text to see another card.")) - .thenApply(sendResult -> null); - }); - } - - private static CompletableFuture sendWelcomeMessage(TurnContext turnContext) { - for (ChannelAccount member : turnContext.getActivity().getMembersAdded()) { - if (!member.getId().equals(turnContext.getActivity().getRecipient().getId())) { - turnContext.sendActivity( - String.format("Welcome to Adaptive Cards Bot %s. %s", member.getName(), welcomeText) - ).join(); - } - } - return CompletableFuture.completedFuture(null); - } - - private static Attachment createAdaptiveCardAttachment(String filePath) { - try ( - InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream(filePath) - ) { - String adaptiveCardJson = IOUtils - .toString(inputStream, StandardCharsets.UTF_8.toString()); - - Attachment attachment = new Attachment(); - attachment.setContentType("application/vnd.microsoft.card.adaptive"); - attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); - - return attachment; - - } catch (IOException e) { - e.printStackTrace(); - return new Attachment(); - } - } -} - diff --git a/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json b/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json deleted file mode 100644 index 1c97e8a72..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/FlightItineraryCard.json +++ /dev/null @@ -1,204 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "version": "1.0", - "type": "AdaptiveCard", - "speak": "Your flight is confirmed for you and 3 other passengers from San Francisco to Amsterdam on Friday, October 10 8:30 AM", - "body": [ - { - "type": "TextBlock", - "text": "Passengers", - "weight": "bolder", - "isSubtle": false - }, - { - "type": "TextBlock", - "text": "Sarah Hum", - "separator": true - }, - { - "type": "TextBlock", - "text": "Jeremy Goldberg", - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "Evan Litvak", - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "2 Stops", - "weight": "bolder", - "spacing": "medium" - }, - { - "type": "TextBlock", - "text": "Fri, October 10 8:30 AM", - "weight": "bolder", - "spacing": "none" - }, - { - "type": "ColumnSet", - "separator": true, - "columns": [ - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "text": "San Francisco", - "isSubtle": true - }, - { - "type": "TextBlock", - "size": "extraLarge", - "color": "accent", - "text": "SFO", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "TextBlock", - "text": " " - }, - { - "type": "Image", - "url": "https://adaptivecards.io/content/airplane.png", - "size": "small", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "Amsterdam", - "isSubtle": true - }, - { - "type": "TextBlock", - "horizontalAlignment": "right", - "size": "extraLarge", - "color": "accent", - "text": "AMS", - "spacing": "none" - } - ] - } - ] - }, - { - "type": "TextBlock", - "text": "Non-Stop", - "weight": "bolder", - "spacing": "medium" - }, - { - "type": "TextBlock", - "text": "Fri, October 18 9:50 PM", - "weight": "bolder", - "spacing": "none" - }, - { - "type": "ColumnSet", - "separator": true, - "columns": [ - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "text": "Amsterdam", - "isSubtle": true - }, - { - "type": "TextBlock", - "size": "extraLarge", - "color": "accent", - "text": "AMS", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": "auto", - "items": [ - { - "type": "TextBlock", - "text": " " - }, - { - "type": "Image", - "url": "https://adaptivecards.io/content/airplane.png", - "size": "small", - "spacing": "none" - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "San Francisco", - "isSubtle": true - }, - { - "type": "TextBlock", - "horizontalAlignment": "right", - "size": "extraLarge", - "color": "accent", - "text": "SFO", - "spacing": "none" - } - ] - } - ] - }, - { - "type": "ColumnSet", - "spacing": "medium", - "columns": [ - { - "type": "Column", - "width": "1", - "items": [ - { - "type": "TextBlock", - "text": "Total", - "size": "medium", - "isSubtle": true - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "right", - "text": "$4,032.54", - "size": "medium", - "weight": "bolder" - } - ] - } - ] - } - ] -} \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json b/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json deleted file mode 100644 index b2558d5f2..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/ImageGalleryCard.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "TextBlock", - "text": "Here are some cool photos", - "size": "large" - }, - { - "type": "TextBlock", - "text": "Sorry some of them are repeats", - "size": "medium", - "weight": "lighter" - }, - { - "type": "ImageSet", - "imageSize": "medium", - "images": [ - { - "type": "Image", - "url": "https://picsum.photos/200/200?image=100" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=200" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=301" - }, - { - "type": "Image", - "url": "https://picsum.photos/200/200?image=400" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=500" - }, - { - "type": "Image", - "url": "https://picsum.photos/200/200?image=600" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=700" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=800" - }, - { - "type": "Image", - "url": "https://picsum.photos/300/200?image=900" - } - ] - } - ] - } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json b/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json deleted file mode 100644 index 938fc5db0..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/LargeWeatherCard.json +++ /dev/null @@ -1,205 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "speak": "Weather forecast for Monday is high of 62 and low of 42 degrees with a 20% chance of rain. Winds will be 5 mph from the northeast.", - "backgroundImage": "https://adaptivecards.io/content/Mostly%20Cloudy-Background-Dark.jpg", - "body": [ - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "35", - "items": [ - { - "type": "Image", - "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png", - "size": "stretch" - } - ] - }, - { - "type": "Column", - "width": "65", - "items": [ - { - "type": "TextBlock", - "text": "Monday April 1", - "weight": "bolder", - "size": "large", - "color": "light" - }, - { - "type": "TextBlock", - "text": "63 / 42", - "size": "medium", - "spacing": "none" - }, - { - "type": "TextBlock", - "isSubtle": true, - "text": "20% chance of rain", - "spacing": "none" - }, - { - "type": "TextBlock", - "isSubtle": true, - "text": "Winds 5 mph NE", - "spacing": "none" - } - ] - } - ] - }, - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": "20", - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "Fri" - }, - { - "type": "Image", - "size": "auto", - "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "62" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "isSubtle": true, - "wrap": false, - "text": "52", - "spacing": "none" - } - ], - "selectAction": { - "type": "Action.OpenUrl", - "title": "View Friday", - "url": "https://www.microsoft.com" - } - }, - { - "type": "Column", - "width": "20", - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "Sat" - }, - { - "type": "Image", - "size": "auto", - "url": "https://adaptivecards.io/content/Drizzle-Square.png" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "60" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "isSubtle": true, - "wrap": false, - "text": "48", - "spacing": "none" - } - ], - "selectAction": { - "type": "Action.OpenUrl", - "title": "View Saturday", - "url": "https://www.microsoft.com" - } - }, - { - "type": "Column", - "width": "20", - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "Sun" - }, - { - "type": "Image", - "size": "auto", - "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "59" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "isSubtle": true, - "wrap": false, - "text": "49", - "spacing": "none" - } - ], - "selectAction": { - "type": "Action.OpenUrl", - "title": "View Sunday", - "url": "https://www.microsoft.com" - } - }, - { - "type": "Column", - "width": "20", - "items": [ - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "Mon" - }, - { - "type": "Image", - "size": "auto", - "url": "https://adaptivecards.io/content/Mostly%20Cloudy-Square.png" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "wrap": false, - "text": "64" - }, - { - "type": "TextBlock", - "horizontalAlignment": "center", - "isSubtle": true, - "wrap": false, - "text": "51", - "spacing": "none" - } - ], - "selectAction": { - "type": "Action.OpenUrl", - "title": "View Monday", - "url": "https://www.microsoft.com" - } - } - ] - } - ] - } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json b/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json deleted file mode 100644 index 20acdc3e6..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/RestaurantCard.json +++ /dev/null @@ -1,60 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "speak": "Tom's Pie is a pizza restaurant which is rated 9.3 by customers.", - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": 2, - "items": [ - { - "type": "TextBlock", - "text": "PIZZA" - }, - { - "type": "TextBlock", - "text": "Tom's Pie", - "weight": "bolder", - "size": "extraLarge", - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "4.2 ★★★☆ (93) · $$", - "isSubtle": true, - "spacing": "none" - }, - { - "type": "TextBlock", - "text": "**Matt H. said** \"I'm compelled to give this place 5 stars due to the number of times I've chosen to eat here this past year!\"", - "size": "small", - "wrap": true - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "Image", - "url": "https://picsum.photos/300?image=882", - "size": "auto" - } - ] - } - ] - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "More Info", - "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ" - } - ] - } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json b/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json deleted file mode 100644 index 5d68664b3..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/SolitaireCard.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "backgroundImage": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/card_background.png", - "body": [ - { - "type": "ColumnSet", - "columns": [ - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "Image", - "url": "https://download-ssl.msgamestudios.com/content/mgs/ce/production/SolitaireWin10/dev/adapative_card_assets/v1/tile_spider.png", - "size": "stretch" - } - ] - }, - { - "type": "Column", - "width": 1, - "items": [ - { - "type": "TextBlock", - "text": "Click here to play another game of Spider in Microsoft Solitaire Collection!", - "color": "light", - "weight": "bolder", - "wrap": true, - "size": "default", - "horizontalAlignment": "center" - } - ] - } - ] - } - ] - } \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/resources/application.properties b/samples/07.using-adaptive-cards/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/07.using-adaptive-cards/src/main/resources/log4j2.json b/samples/07.using-adaptive-cards/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/07.using-adaptive-cards/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF b/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/07.using-adaptive-cards/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml b/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/07.using-adaptive-cards/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/07.using-adaptive-cards/src/main/webapp/index.html b/samples/07.using-adaptive-cards/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/07.using-adaptive-cards/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java b/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java deleted file mode 100644 index c2cc1d1e6..000000000 --- a/samples/07.using-adaptive-cards/src/test/java/com/microsoft/bot/sample/usingadaptivecards/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.usingadaptivecards; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/08.suggested-actions/LICENSE b/samples/08.suggested-actions/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/08.suggested-actions/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/08.suggested-actions/README.md b/samples/08.suggested-actions/README.md deleted file mode 100644 index 4c19c3efd..000000000 --- a/samples/08.suggested-actions/README.md +++ /dev/null @@ -1,85 +0,0 @@ -# Suggested actions - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use suggested actions. Suggested actions enable your bot to present buttons that the user can tap to provide input. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-suggestedactions-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "suggestedActionsBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="suggestedActionsBotPlan" newWebAppName="suggestedActionsBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="suggestedActionsBot" newAppServicePlanName="suggestedActionsBotPlan" appServicePlanLocation="westus" --name "suggestedActionsBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/08.suggested-actions/bin/LICENSE b/samples/08.suggested-actions/bin/LICENSE deleted file mode 100644 index 09d2ba6d8..000000000 --- a/samples/08.suggested-actions/bin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Dave Taniguchi - -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. diff --git a/samples/08.suggested-actions/bin/README.md b/samples/08.suggested-actions/bin/README.md deleted file mode 100644 index 131a86b65..000000000 --- a/samples/08.suggested-actions/bin/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Spring Boot EchoBot - -This demonstrates how to create a Bot using the Bot Framework 4 SDK Preview for Java in Azure. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\springechobot-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or Powershell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` - -#### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` - -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 7. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #7 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/08.suggested-actions/bin/deploymentTemplates/new-rg-parameters.json b/samples/08.suggested-actions/bin/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/08.suggested-actions/bin/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/08.suggested-actions/bin/deploymentTemplates/preexisting-rg-parameters.json b/samples/08.suggested-actions/bin/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/08.suggested-actions/bin/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/08.suggested-actions/bin/deploymentTemplates/template-with-new-rg.json b/samples/08.suggested-actions/bin/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index dcd6260a5..000000000 --- a/samples/08.suggested-actions/bin/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "F0", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "resourcesLocation": "[deployment().location]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new App Service Plan", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using the new App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('appServicePlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} \ No newline at end of file diff --git a/samples/08.suggested-actions/bin/deploymentTemplates/template-with-preexisting-rg.json b/samples/08.suggested-actions/bin/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index b790d2bdc..000000000 --- a/samples/08.suggested-actions/bin/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using an App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", - "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} \ No newline at end of file diff --git a/samples/08.suggested-actions/bin/pom.xml b/samples/08.suggested-actions/bin/pom.xml deleted file mode 100644 index 6900cba5e..000000000 --- a/samples/08.suggested-actions/bin/pom.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.connector.sample - spring-echobot - sample - jar - - ${project.groupId}:${project.artifactId} - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - UTF-8 - UTF-8 - 1.8 - com.microsoft.bot.sample.echo.Application - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - junit - junit - 4.12 - test - - - com.microsoft.bot.schema - botbuilder-schema - 4.0.0-SNAPSHOT - - - com.microsoft.bot.connector - bot-connector - 4.0.0-SNAPSHOT - - - com.fasterxml.jackson.module - jackson-module-parameter-names - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.2 - - - - - - MyGet - ${repo.url} - - - - - - MyGet - ${repo.url} - - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.echo.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - - - diff --git a/samples/08.suggested-actions/bin/src/main/resources/application.properties b/samples/08.suggested-actions/bin/src/main/resources/application.properties deleted file mode 100644 index a695b3bf0..000000000 --- a/samples/08.suggested-actions/bin/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= diff --git a/samples/08.suggested-actions/bin/src/main/webapp/META-INF/MANIFEST.MF b/samples/08.suggested-actions/bin/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/08.suggested-actions/bin/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/08.suggested-actions/bin/src/main/webapp/WEB-INF/web.xml b/samples/08.suggested-actions/bin/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/08.suggested-actions/bin/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/08.suggested-actions/bin/src/main/webapp/index.html b/samples/08.suggested-actions/bin/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/08.suggested-actions/bin/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json b/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/08.suggested-actions/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json b/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/08.suggested-actions/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/08.suggested-actions/pom.xml b/samples/08.suggested-actions/pom.xml deleted file mode 100644 index 943a8a74d..000000000 --- a/samples/08.suggested-actions/pom.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-suggestedactions - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Suggested Actions sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.suggestedactions.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.suggestedactions.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java deleted file mode 100644 index 08514f8b3..000000000 --- a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/Application.java +++ /dev/null @@ -1,64 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.suggestedactions; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new SuggestedActionsBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java b/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java deleted file mode 100644 index 5c3b29c4a..000000000 --- a/samples/08.suggested-actions/src/main/java/com/microsoft/bot/sample/suggestedactions/SuggestedActionsBot.java +++ /dev/null @@ -1,124 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.suggestedactions; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.SuggestedActions; -import org.apache.commons.lang3.StringUtils; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would - * be added. For this sample, the {@link #onMessageActivity(TurnContext)} - * displays a list of SuggestedActions to the user. The - * {@link #onMembersAdded(List, TurnContext)} will send a greeting to new - * conversation participants. - *

- */ -public class SuggestedActionsBot extends ActivityHandler { - public static final String WELCOMETEXT = - "This bot will introduce you to suggestedActions." + " Please answer the question:"; - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return sendWelcomeMessage(turnContext); - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - // Extract the text from the message activity the user sent. - String text = turnContext.getActivity().getText().toLowerCase(); - - // Take the input from the user and create the appropriate response. - String responseText = processInput(text); - - // Respond to the user. - return turnContext - .sendActivities(MessageFactory.text(responseText), createSuggestedActions()) - .thenApply(responses -> null); - } - - private CompletableFuture sendWelcomeMessage(TurnContext turnContext) { - return turnContext.getActivity() - .getMembersAdded() - .stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ) - .map( - channel -> turnContext.sendActivities( - MessageFactory.text( - "Welcome to SuggestedActionsBot " + channel.getName() + ". " + WELCOMETEXT - ), createSuggestedActions() - ) - ) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponses -> null); - } - - private String processInput(String text) { - String colorText = "is the best color, I agree."; - switch (text) { - case "red": - return "Red " + colorText; - - case "yellow": - return "Yellow " + colorText; - - case "blue": - return "Blue " + colorText; - - default: - return "Please select a color from the suggested action choices"; - } - } - - private Activity createSuggestedActions() { - Activity reply = MessageFactory.text("What is your favorite color?"); - - CardAction redAction = new CardAction(); - redAction.setTitle("Red"); - redAction.setType(ActionTypes.IM_BACK); - redAction.setValue("Red"); - redAction.setImage("https://via.placeholder.com/20/FF0000?text=R"); - redAction.setImageAltText("R"); - - CardAction yellowAction = new CardAction(); - yellowAction.setTitle("Yellow"); - yellowAction.setType(ActionTypes.IM_BACK); - yellowAction.setValue("Yellow"); - yellowAction.setImage("https://via.placeholder.com/20/FFFF00?text=Y"); - yellowAction.setImageAltText("Y"); - - CardAction blueAction = new CardAction(); - blueAction.setTitle("Blue"); - blueAction.setType(ActionTypes.IM_BACK); - blueAction.setValue("Blue"); - blueAction.setImage("https://via.placeholder.com/20/0000FF?text=B"); - blueAction.setImageAltText("B"); - - SuggestedActions actions = new SuggestedActions(); - actions.setActions(Arrays.asList(redAction, yellowAction, blueAction)); - reply.setSuggestedActions(actions); - - return reply; - } -} diff --git a/samples/08.suggested-actions/src/main/resources/application.properties b/samples/08.suggested-actions/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/08.suggested-actions/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/08.suggested-actions/src/main/resources/log4j2.json b/samples/08.suggested-actions/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/08.suggested-actions/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/08.suggested-actions/src/main/webapp/META-INF/MANIFEST.MF b/samples/08.suggested-actions/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/08.suggested-actions/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/08.suggested-actions/src/main/webapp/WEB-INF/web.xml b/samples/08.suggested-actions/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/08.suggested-actions/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/08.suggested-actions/src/main/webapp/index.html b/samples/08.suggested-actions/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/08.suggested-actions/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java b/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java deleted file mode 100644 index 387aac524..000000000 --- a/samples/08.suggested-actions/src/test/java/com/microsoft/bot/sample/suggestedactions/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.suggestedactions; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/11.qnamaker/LICENSE b/samples/11.qnamaker/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/11.qnamaker/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/11.qnamaker/README.md b/samples/11.qnamaker/README.md deleted file mode 100644 index 7a82c0e33..000000000 --- a/samples/11.qnamaker/README.md +++ /dev/null @@ -1,64 +0,0 @@ -# QnA Maker - -Bot Framework v4 QnA Maker bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that uses the [QnA Maker Cognitive AI](https://www.qnamaker.ai) service. - -The [QnA Maker Service](https://www.qnamaker.ai) enables you to build, train and publish a simple question and answer bot based on FAQ URLs, structured documents or editorial content in minutes. In this sample, we demonstrate how to use the QnA Maker service to answer questions based on a FAQ text file used as input. - -## Prerequisites - -This samples **requires** prerequisites in order to run. - -### Overview - -- This bot uses [QnA Maker Service](https://www.qnamaker.ai), an AI based cognitive service, to implement simple Question and Answer conversational patterns. -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -### Create a QnAMaker Application to enable QnA Knowledge Bases - -QnA knowledge base setup and application configuration steps can be found [here](https://aka.ms/qna-instructions). - -## To try this sample - -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-qna-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -## Interacting with the bot - -QnA Maker enables you to power a question and answer service from your semi-structured content. - -One of the basic requirements in writing your own bot is to seed it with questions and answers. In many cases, the questions and answers already exist in content like FAQ URLs/documents, product manuals, etc. With QnA Maker, users can query your application in a natural, conversational manner. QnA Maker uses machine learning to extract relevant question-answer pairs from your content. It also uses powerful matching and ranking algorithms to provide the best possible match between the user query and the questions. - -## Deploy the bot to Azure - -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [QnA Maker Documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/qnamaker/overview/overview) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [QnA Maker CLI](https://github.com/microsoft/botframework-cli/tree/main/packages/qnamaker) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/samples/11.qnamaker/deploymentTemplates/template-with-new-rg.json b/samples/11.qnamaker/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/11.qnamaker/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/11.qnamaker/deploymentTemplates/template-with-preexisting-rg.json b/samples/11.qnamaker/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/11.qnamaker/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/11.qnamaker/pom.xml b/samples/11.qnamaker/pom.xml deleted file mode 100644 index 5b58dce77..000000000 --- a/samples/11.qnamaker/pom.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-qna - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java QnAMaker Bot sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.qnamaker.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - com.microsoft.bot - bot-ai-qna - 4.13.0-SNAPSHOT - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.qnamaker.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/Application.java b/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/Application.java deleted file mode 100644 index 053636cd5..000000000 --- a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/Application.java +++ /dev/null @@ -1,66 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.qnamaker; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot(Configuration configuration) { - return new QnABot(configuration); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } - -} - diff --git a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java b/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java deleted file mode 100644 index 754d0a20f..000000000 --- a/samples/11.qnamaker/src/main/java/com/microsoft/bot/sample/qnamaker/QnABot.java +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.qnamaker; - -import com.microsoft.bot.ai.qna.QnAMaker; -import com.microsoft.bot.ai.qna.QnAMakerEndpoint; -import com.microsoft.bot.ai.qna.QnAMakerOptions; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.integration.Configuration; -import org.slf4j.LoggerFactory; -import java.util.concurrent.CompletableFuture; - -public class QnABot extends ActivityHandler { - private Configuration configuration; - - public QnABot(Configuration withConfiguration) { - configuration = withConfiguration; - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - QnAMakerEndpoint qnAMakerEndpoint = new QnAMakerEndpoint(); - qnAMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); - qnAMakerEndpoint.setEndpointKey(configuration.getProperty("QnAEndpointKey")); - qnAMakerEndpoint.setHost(configuration.getProperty("QnAEndpointHostName")); - - QnAMaker qnaMaker = new QnAMaker(qnAMakerEndpoint, null); - - LoggerFactory.getLogger(QnABot.class).info("Calling QnA Maker"); - - QnAMakerOptions options = new QnAMakerOptions(); - options.setTop(1); - - // The actual call to the QnA Maker service. - return qnaMaker.getAnswers(turnContext, options) - .thenCompose(response -> { - if (response != null && response.length > 0) { - return turnContext.sendActivity(MessageFactory.text(response[0].getAnswer())) - .thenApply(sendResult -> null); - } - else { - return turnContext.sendActivity(MessageFactory.text("No QnA Maker answers were found.")) - .thenApply(sendResult -> null); - } - }); - } -} diff --git a/samples/11.qnamaker/src/main/resources/application.properties b/samples/11.qnamaker/src/main/resources/application.properties deleted file mode 100644 index 90f36f7a3..000000000 --- a/samples/11.qnamaker/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -QnAKnowledgebaseId= -QnAEndpointKey= -QnAEndpointHostName= -server.port=3978 diff --git a/samples/11.qnamaker/src/main/resources/log4j2.json b/samples/11.qnamaker/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/11.qnamaker/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/11.qnamaker/src/main/resources/smartLightFAQ.tsv b/samples/11.qnamaker/src/main/resources/smartLightFAQ.tsv deleted file mode 100644 index 754118909..000000000 --- a/samples/11.qnamaker/src/main/resources/smartLightFAQ.tsv +++ /dev/null @@ -1,15 +0,0 @@ -Question Answer Source Keywords -Question Answer 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Source -My Contoso smart light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -Light won't turn on. Check the connection to the wall outlet to make sure it's plugged in properly. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -My smart light app stopped responding. Restart the app. If the problem persists, contact support. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -How do I contact support? Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -I need help. Email us at service@contoso.com 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -I upgraded the app and it doesn't work anymore. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -Light doesn't work after upgrade. When you upgrade, you need to disable Bluetooth, then re-enable it. After re-enable, re-pair your light with the app. 96207418-4609-48df-9cbc-dd35a71e83f7-KB.tsv Editorial -Question Answer 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Source -Who should I contact for customer service? Please direct all customer service questions to (202) 555-0164 \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial -Why does the light not work? The simplest way to troubleshoot your smart light is to turn it off and on. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial -How long does the light's battery last for? The battery will last approximately 10 - 12 weeks with regular use. \n 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial -What type of light bulb do I need? A 26-Watt compact fluorescent light bulb that features both energy savings and long-life performance. 890b1efc-27ad-4dca-8dcb-b20d29d50e14-KB.tsv Editorial -Hi Hello Editorial \ No newline at end of file diff --git a/samples/11.qnamaker/src/main/webapp/META-INF/MANIFEST.MF b/samples/11.qnamaker/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/11.qnamaker/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/11.qnamaker/src/main/webapp/WEB-INF/web.xml b/samples/11.qnamaker/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/11.qnamaker/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/11.qnamaker/src/main/webapp/index.html b/samples/11.qnamaker/src/main/webapp/index.html deleted file mode 100644 index 9193cd547..000000000 --- a/samples/11.qnamaker/src/main/webapp/index.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - QnA Maker Sample - - - - - -
-
-
-
QnA Maker Sample
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - diff --git a/samples/11.qnamaker/src/test/java/com/microsoft/bot/sample/qnamaker/ApplicationTest.java b/samples/11.qnamaker/src/test/java/com/microsoft/bot/sample/qnamaker/ApplicationTest.java deleted file mode 100644 index 070e5145a..000000000 --- a/samples/11.qnamaker/src/test/java/com/microsoft/bot/sample/qnamaker/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.qnamaker; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/13.core-bot/LICENSE b/samples/13.core-bot/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/13.core-bot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/13.core-bot/README-LUIS.md b/samples/13.core-bot/README-LUIS.md deleted file mode 100644 index 12bc78ed0..000000000 --- a/samples/13.core-bot/README-LUIS.md +++ /dev/null @@ -1,216 +0,0 @@ -# Setting up LUIS via CLI: - -This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. - -> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ -> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ -> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ - - [Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app - [Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app - -## Table of Contents: - -- [Prerequisites](#Prerequisites) -- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) -- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) - -___ - -## [Prerequisites](#Table-of-Contents): - -#### Install Azure CLI >=2.0.61: - -Visit the following page to find the correct installer for your OS: -- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest - -#### Install LUIS CLI >=2.4.0: - -Open a CLI of your choice and type the following: - -```bash -npm i -g luis-apis@^2.4.0 -``` - -#### LUIS portal account: - -You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. - -After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. - - [LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] - [LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key - -___ - -## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) - -### 1. Import the local LUIS application to luis.ai - -```bash -luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" -``` - -Outputs the following JSON: - -```json -{ - "id": "########-####-####-####-############", - "name": "FlightBooking", - "description": "A LUIS model that uses intent and entities.", - "culture": "en-us", - "usageScenario": "", - "domain": "", - "versionsCount": 1, - "createdDateTime": "2019-03-29T18:32:02Z", - "endpoints": {}, - "endpointHitsCount": 0, - "activeVersion": "0.1", - "ownerEmail": "bot@contoso.com", - "tokenizerVersion": "1.0.0" -} -``` - -For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. - -### 2. Train the LUIS Application - -```bash -luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait -``` - -### 3. Publish the LUIS Application - -```bash -luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" -``` - -> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
-> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
-> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
-> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. - - [Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 - -Outputs the following: - -```json - { - "versionId": "0.1", - "isStaging": false, - "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", - "region": "westus", - "assignedEndpointKey": null, - "endpointRegion": "westus", - "failedRegions": "", - "publishedDateTime": "2019-03-29T18:40:32Z", - "directVersionPublish": false -} -``` - -To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. - - [README-LUIS]: ./README-LUIS.md - -___ - -## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) - -### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI - -> _Note:_
-> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ -> ```bash -> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" -> ``` -> _To see a list of valid locations, use `az account list-locations`_ - - -```bash -# Use Azure CLI to create the LUIS Key resource on Azure -az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -The command will output a response similar to the JSON below: - -```json -{ - "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", - "etag": "\"########-####-####-####-############\"", - "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", - "internalId": "################################", - "kind": "luis", - "location": "westus", - "name": "NewLuisResourceName", - "provisioningState": "Succeeded", - "resourceGroup": "ResourceGroupName", - "sku": { - "name": "S0", - "tier": null - }, - "tags": null, - "type": "Microsoft.CognitiveServices/accounts" -} -``` - - - -Take the output from the previous command and create a JSON file in the following format: - -```json -{ - "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", - "resourceGroup": "ResourceGroupName", - "accountName": "NewLuisResourceName" -} -``` - -### 2. Retrieve ARM access token via Azure CLI - -```bash -az account get-access-token --subscription "AzureSubscriptionGuid" -``` - -This will return an object that looks like this: - -```json -{ - "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", - "expiresOn": "2200-12-31 23:59:59.999999", - "subscription": "AzureSubscriptionGuid", - "tenant": "tenant-guid", - "tokenType": "Bearer" -} -``` - -The value needed for the next step is the `"accessToken"`. - -### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application - -```bash -luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" -``` - -If successful, it should yield a response like this: - -```json -{ - "code": "Success", - "message": "Operation Successful" -} -``` - -### 4. See the LUIS Cognitive Services' keys - -```bash -az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -This will return an object that looks like this: - -```json -{ - "key1": "9a69####dc8f####8eb4####399f####", - "key2": "####f99e####4b1a####fb3b####6b9f" -} -``` diff --git a/samples/13.core-bot/README.md b/samples/13.core-bot/README.md deleted file mode 100644 index 5eb579ae9..000000000 --- a/samples/13.core-bot/README.md +++ /dev/null @@ -1,67 +0,0 @@ -# CoreBot - -Bot Framework v4 core bot sample. - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: - -- Use [LUIS](https://www.luis.ai) to implement core AI capabilities -- Implement a multi-turn conversation using Dialogs -- Handle user interruptions for such things as `Help` or `Cancel` -- Prompt for and validate requests for information from the user - -## Prerequisites - -This sample **requires** prerequisites in order to run. - -### Overview - -This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding. - -### Create a LUIS Application to enable language understanding - -The LUIS model for this example can be found under `cognitiveModels/FlightBooking.json` and the LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). - -Once you created the LUIS model, update `application.properties` with your `LuisAppId`, `LuisAPIKey` and `LuisAPIHostName`. - -``` - LuisAppId="Your LUIS App Id" - LuisAPIKey="Your LUIS Subscription key here" - LuisAPIHostName="Your LUIS App region here (i.e: westus.api.cognitive.microsoft.com)" -``` - -## To try this sample - -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-core-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/samples/13.core-bot/cognitiveModels/FlightBooking.json b/samples/13.core-bot/cognitiveModels/FlightBooking.json deleted file mode 100644 index 89aad31ae..000000000 --- a/samples/13.core-bot/cognitiveModels/FlightBooking.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "luis_schema_version": "3.2.0", - "versionId": "0.1", - "name": "FlightBooking", - "desc": "Luis Model for CoreBot", - "culture": "en-us", - "tokenizerVersion": "1.0.0", - "intents": [ - { - "name": "BookFlight" - }, - { - "name": "Cancel" - }, - { - "name": "GetWeather" - }, - { - "name": "None" - } - ], - "entities": [], - "composites": [ - { - "name": "From", - "children": [ - "Airport" - ], - "roles": [] - }, - { - "name": "To", - "children": [ - "Airport" - ], - "roles": [] - } - ], - "closedLists": [ - { - "name": "Airport", - "subLists": [ - { - "canonicalForm": "Paris", - "list": [ - "paris", - "cdg" - ] - }, - { - "canonicalForm": "London", - "list": [ - "london", - "lhr" - ] - }, - { - "canonicalForm": "Berlin", - "list": [ - "berlin", - "txl" - ] - }, - { - "canonicalForm": "New York", - "list": [ - "new york", - "jfk" - ] - }, - { - "canonicalForm": "Seattle", - "list": [ - "seattle", - "sea" - ] - } - ], - "roles": [] - } - ], - "patternAnyEntities": [], - "regex_entities": [], - "prebuiltEntities": [ - { - "name": "datetimeV2", - "roles": [] - } - ], - "model_features": [], - "regex_features": [], - "patterns": [], - "utterances": [ - { - "text": "book a flight", - "intent": "BookFlight", - "entities": [] - }, - { - "text": "book a flight from new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 26 - } - ] - }, - { - "text": "book a flight from seattle", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 25 - } - ] - }, - { - "text": "book a hotel in new york", - "intent": "None", - "entities": [] - }, - { - "text": "book a restaurant", - "intent": "None", - "entities": [] - }, - { - "text": "book flight from london to paris on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 17, - "endPos": 22 - }, - { - "entity": "To", - "startPos": 27, - "endPos": 31 - } - ] - }, - { - "text": "book flight to berlin on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 15, - "endPos": 20 - } - ] - }, - { - "text": "book me a flight from london to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 22, - "endPos": 27 - }, - { - "entity": "To", - "startPos": 32, - "endPos": 36 - } - ] - }, - { - "text": "bye", - "intent": "Cancel", - "entities": [] - }, - { - "text": "cancel booking", - "intent": "Cancel", - "entities": [] - }, - { - "text": "exit", - "intent": "Cancel", - "entities": [] - }, - { - "text": "find an airport near me", - "intent": "None", - "entities": [] - }, - { - "text": "flight to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "flight to paris from london on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - }, - { - "entity": "From", - "startPos": 21, - "endPos": 26 - } - ] - }, - { - "text": "fly from berlin to paris on may 5th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 9, - "endPos": 14 - }, - { - "entity": "To", - "startPos": 19, - "endPos": 23 - } - ] - }, - { - "text": "go to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 6, - "endPos": 10 - } - ] - }, - { - "text": "going from paris to berlin", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 11, - "endPos": 15 - }, - { - "entity": "To", - "startPos": 20, - "endPos": 25 - } - ] - }, - { - "text": "i'd like to rent a car", - "intent": "None", - "entities": [] - }, - { - "text": "ignore", - "intent": "Cancel", - "entities": [] - }, - { - "text": "travel from new york to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 12, - "endPos": 19 - }, - { - "entity": "To", - "startPos": 24, - "endPos": 28 - } - ] - }, - { - "text": "travel to new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 17 - } - ] - }, - { - "text": "travel to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "what's the forecast for this friday?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like for tomorrow", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like in new york", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "winter is coming", - "intent": "None", - "entities": [] - } - ], - "settings": [] -} diff --git a/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json b/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/13.core-bot/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/13.core-bot/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/13.core-bot/pom.xml b/samples/13.core-bot/pom.xml deleted file mode 100644 index 9ea745588..000000000 --- a/samples/13.core-bot/pom.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-core - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Core Bot sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.core.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - - - com.microsoft.bot - bot-ai-luis-v3 - 4.13.0-SNAPSHOT - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.core.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java deleted file mode 100644 index 8ae95d1f7..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/Application.java +++ /dev/null @@ -1,79 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * This is the starting point of the Sprint Boot Bot application. - */ -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - /** - * The start method. - * - * @param args The args. - */ - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method with the - * @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - Configuration configuration, - UserState userState, - ConversationState conversationState - ) { - FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration); - MainDialog dialog = new MainDialog(recognizer, new BookingDialog()); - return new DialogAndWelcomeBot<>(conversationState, userState, dialog); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} - diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java deleted file mode 100644 index 330740a57..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDetails.java +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -/** - * The model class to retrieve the information of the booking. - */ -public class BookingDetails { - - private String destination; - private String origin; - private String travelDate; - - /** - * Gets the destination of the booking. - * - * @return The destination. - */ - public String getDestination() { - return destination; - } - - - /** - * Sets the destination of the booking. - * - * @param withDestination The new destination. - */ - public void setDestination(String withDestination) { - this.destination = withDestination; - } - - /** - * Gets the origin of the booking. - * - * @return The origin. - */ - public String getOrigin() { - return origin; - } - - /** - * Sets the origin of the booking. - * - * @param withOrigin The new origin. - */ - public void setOrigin(String withOrigin) { - this.origin = withOrigin; - } - - /** - * Gets the travel date of the booking. - * - * @return The travel date. - */ - public String getTravelDate() { - return travelDate; - } - - /** - * Sets the travel date of the booking. - * - * @param withTravelDate The new travel date. - */ - public void setTravelDate(String withTravelDate) { - this.travelDate = withTravelDate; - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java deleted file mode 100644 index 4a5e45721..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/BookingDialog.java +++ /dev/null @@ -1,135 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the booking dialogs. - */ -public class BookingDialog extends CancelAndHelpDialog { - - private final String destinationStepMsgText = "Where would you like to travel to?"; - private final String originStepMsgText = "Where are you traveling from?"; - - /** - * The constructor of the Booking Dialog class. - */ - public BookingDialog() { - super("BookingDialog"); - - addDialog(new TextPrompt("TextPrompt")); - addDialog(new ConfirmPrompt("ConfirmPrompt")); - addDialog(new DateResolverDialog(null)); - WaterfallStep[] waterfallSteps = { - this::destinationStep, - this::originStep, - this::travelDateStep, - this::confirmStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - - private CompletableFuture destinationStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - if (bookingDetails.getDestination().isEmpty()) { - Activity promptMessage = - MessageFactory.text(destinationStepMsgText, destinationStepMsgText, - InputHints.EXPECTING_INPUT - ); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getDestination()); - } - - - private CompletableFuture originStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setDestination(stepContext.getResult().toString()); - - if (bookingDetails.getOrigin().isEmpty()) { - Activity promptMessage = - MessageFactory - .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getOrigin()); - } - - - private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setOrigin(stepContext.getResult().toString()); - - if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { - return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); - } - - return stepContext.next(bookingDetails.getTravelDate()); - } - - - private CompletableFuture confirmStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setTravelDate(stepContext.getResult().toString()); - - String messageText = - String.format( - "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", - bookingDetails.getDestination(), bookingDetails.getOrigin(), - bookingDetails.getTravelDate() - ); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - - return stepContext.prompt("ConfirmPrompt", promptOptions); - } - - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - if ((Boolean) stepContext.getResult()) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - return stepContext.endDialog(bookingDetails); - } - - return stepContext.endDialog(null); - } - - private static boolean isAmbiguous(String timex) { - TimexProperty timexProperty = new TimexProperty(timex); - return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java deleted file mode 100644 index 570fb3eaf..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/CancelAndHelpDialog.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.DialogTurnStatus; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; -import com.microsoft.bot.schema.InputHints; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of the dialog interruptions. - */ -public class CancelAndHelpDialog extends ComponentDialog { - - private final String helpMsgText = "Show help here"; - private final String cancelMsgText = "Cancelling..."; - - /** - * The constructor of the CancelAndHelpDialog class. - * - * @param id The dialog's Id. - */ - public CancelAndHelpDialog(String id) { - super(id); - } - - /** - * Called when the dialog is _continued_, where it is the active dialog and the user replies - * with a new activity. - * - * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. - * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is - * successful, the result indicates whether the dialog is still active after the turn has been - * processed by the dialog. The result may also contain a return value. - */ - @Override - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - return interrupt(innerDc).thenCompose(result -> { - if (result != null) { - return CompletableFuture.completedFuture(result); - } - return super.onContinueDialog(innerDc); - }); - } - - private CompletableFuture interrupt(DialogContext innerDc) { - if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { - String text = innerDc.getContext().getActivity().getText().toLowerCase(); - - switch (text) { - case "help": - case "?": - Activity helpMessage = MessageFactory - .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); - return innerDc.getContext().sendActivity(helpMessage) - .thenCompose(sendResult -> - CompletableFuture - .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); - case "cancel": - case "quit": - Activity cancelMessage = MessageFactory - .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); - return innerDc.getContext() - .sendActivity(cancelMessage) - .thenCompose(sendResult -> innerDc.cancelAllDialogs()); - default: - break; - } - } - - return CompletableFuture.completedFuture(null); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java deleted file mode 100644 index a6b8dde11..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DateResolverDialog.java +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.DateTimePrompt; -import com.microsoft.bot.dialogs.prompts.DateTimeResolution; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the date resolver dialogs. - */ -public class DateResolverDialog extends CancelAndHelpDialog { - private final String promptMsgText = "When would you like to travel?"; - private final String repromptMsgText = - "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; - - - /** - * The constructor of the DateResolverDialog class. - * @param id The dialog's id. - */ - public DateResolverDialog(@Nullable String id) { - super(id != null ? id : "DateResolverDialog"); - - - addDialog(new DateTimePrompt("DateTimePrompt", - DateResolverDialog::dateTimePromptValidator, null)); - WaterfallStep[] waterfallSteps = { - this::initialStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture initialStep(WaterfallStepContext stepContext) { - String timex = (String) stepContext.getOptions(); - - Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); - Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); - - if (timex == null) { - // We were not given any date at all so prompt the user. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - promptOptions.setRetryPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - // We have a Date we just need to check it is unambiguous. - TimexProperty timexProperty = new TimexProperty(timex); - if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { - // This is essentially a "reprompt" of the data we were given up front. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - DateTimeResolution dateTimeResolution = new DateTimeResolution(); - dateTimeResolution.setTimex(timex); - List dateTimeResolutions = new ArrayList(); - dateTimeResolutions.add(dateTimeResolution); - return stepContext.next(dateTimeResolutions); - } - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); - return stepContext.endDialog(timex); - } - - private static CompletableFuture dateTimePromptValidator( - PromptValidatorContext> promptContext - ) { - if (promptContext.getRecognized().getSucceeded()) { - // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the - // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. - // e.g. missing a Year. - String timex = ((List) promptContext.getRecognized().getValue()) - .get(0).getTimex().split("T")[0]; - - // If this is a definite Date including year, month and day we are good otherwise reprompt. - // A better solution might be to let the user know what part is actually missing. - Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); - - return CompletableFuture.completedFuture(isDefinite); - } - - return CompletableFuture.completedFuture(false); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java deleted file mode 100644 index fa6c8e476..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogAndWelcomeBot.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; -import com.microsoft.applicationinsights.core.dependencies.apachecommons.lang3.StringUtils; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.Serialization; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the welcome dialog. - * - * @param is a Dialog. - */ -public class DialogAndWelcomeBot extends DialogBot { - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogAndWelcomeBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - super(withConversationState, withUserState, withDialog); - } - - /** - * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation - * update activity that indicates one or more users other than the bot are joining the - * conversation, it calls this method. - * - * @param membersAdded A list of all the members added to the conversation, as described by the - * conversation update activity - * @param turnContext The context object for this turn. - * @return A task that represents the work queued to execute. - */ - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - // Greet anyone that was not the target (recipient) of this message. - // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. - Attachment welcomeCard = createAdaptiveCardAttachment(); - Activity response = MessageFactory - .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); - - return turnContext.sendActivity(response).thenApply(sendResult -> { - return Dialog.run(getDialog(), turnContext, - getConversationState().createProperty("DialogState") - ); - }); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - // Load attachment from embedded resource. - private Attachment createAdaptiveCardAttachment() { - try ( - InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") - ) { - String adaptiveCardJson = IOUtils - .toString(inputStream, StandardCharsets.UTF_8.toString()); - - Attachment attachment = new Attachment(); - attachment.setContentType("application/vnd.microsoft.card.adaptive"); - attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); - return attachment; - - } catch (IOException e) { - e.printStackTrace(); - return new Attachment(); - } - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java deleted file mode 100644 index 18df7b20f..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/DialogBot.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow - * multiple different bots to be run at different endpoints within the same project. This can be - * achieved by defining distinct Controller types each with dependency on distinct Bot types. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been - * used in a Dialog implementation, and the requirement is that all BotState objects are saved at - * the end of a turn. - * - * @param parameter of a type inheriting from Dialog - */ -public class DialogBot extends ActivityHandler { - - private Dialog dialog; - private BotState conversationState; - private BotState userState; - - /** - * Gets the dialog in use. - * - * @return instance of dialog - */ - protected Dialog getDialog() { - return dialog; - } - - /** - * Gets the conversation state. - * - * @return instance of conversationState - */ - protected BotState getConversationState() { - return conversationState; - } - - /** - * Gets the user state. - * - * @return instance of userState - */ - protected BotState getUserState() { - return userState; - } - - /** - * Sets the dialog in use. - * - * @param withDialog the dialog (of Dialog type) to be set - */ - protected void setDialog(Dialog withDialog) { - dialog = withDialog; - } - - /** - * Sets the conversation state. - * - * @param withConversationState the conversationState (of BotState type) to be set - */ - protected void setConversationState(BotState withConversationState) { - conversationState = withConversationState; - } - - /** - * Sets the user state. - * - * @param withUserState the userState (of BotState type) to be set - */ - protected void setUserState(BotState withUserState) { - userState = withUserState; - } - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - this.conversationState = withConversationState; - this.userState = withUserState; - this.dialog = withDialog; - } - - /** - * Saves the BotState objects at the end of each turn. - * - * @param turnContext - * @return - */ - @Override - public CompletableFuture onTurn(TurnContext turnContext) { - return super.onTurn(turnContext) - .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) - .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); - } - - /** - * This method is executed when the turnContext receives a message activity. - * - * @param turnContext - * @return - */ - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); - - // Run the Dialog with the new message Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java deleted file mode 100644 index 52cc1fd15..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/FlightBookingRecognizer.java +++ /dev/null @@ -1,153 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.microsoft.bot.ai.luis.LuisApplication; -import com.microsoft.bot.ai.luis.LuisRecognizer; -import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; -import com.microsoft.bot.builder.Recognizer; -import com.microsoft.bot.builder.RecognizerResult; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.integration.Configuration; -import org.apache.commons.lang3.StringUtils; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of recognizing the booking information. - */ -public class FlightBookingRecognizer implements Recognizer { - - private LuisRecognizer recognizer; - - /** - * The constructor of the FlightBookingRecognizer class. - * - * @param configuration The Configuration object to use. - */ - public FlightBookingRecognizer(Configuration configuration) { - Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); - if (luisIsConfigured) { - LuisApplication luisApplication = new LuisApplication( - configuration.getProperty("LuisAppId"), - configuration.getProperty("LuisAPIKey"), - String.format("https://%s", configuration.getProperty("LuisAPIHostName")) - ); - // Set the recognizer options depending on which endpoint version you want to use. - // More details can be found in - // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); - recognizerOptions.setIncludeInstanceData(true); - - this.recognizer = new LuisRecognizer(recognizerOptions); - } - } - - /** - * Verify if the recognizer is configured. - * - * @return True if it's configured, False if it's not. - */ - public Boolean isConfigured() { - return this.recognizer != null; - } - - /** - * Return an object with preformatted LUIS results for the bot's dialogs to consume. - * - * @param context A {link TurnContext} - * @return A {link RecognizerResult} - */ - public CompletableFuture executeLuisQuery(TurnContext context) { - // Returns true if luis is configured in the application.properties and initialized. - return this.recognizer.recognize(context); - } - - /** - * Gets the From data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the From data. - */ - public ObjectNode getFromEntities(RecognizerResult result) { - String fromValue = "", fromAirportValue = ""; - if (result.getEntities().get("$instance").get("From") != null) { - fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") - .asText(); - } - if (!fromValue.isEmpty() - && result.getEntities().get("From").get(0).get("Airport") != null) { - fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("from", fromValue); - entitiesNode.put("airport", fromAirportValue); - return entitiesNode; - } - - /** - * Gets the To data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the To data. - */ - public ObjectNode getToEntities(RecognizerResult result) { - String toValue = "", toAirportValue = ""; - if (result.getEntities().get("$instance").get("To") != null) { - toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); - } - if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { - toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("to", toValue); - entitiesNode.put("airport", toAirportValue); - return entitiesNode; - } - - /** - * This value will be a TIMEX. And we are only interested in a Date so grab the first result and - * drop the Time part. TIMEX is a format that represents DateTime expressions that include some - * ambiguity. e.g. missing a Year. - * - * @param result A {link RecognizerResult} - * @return The Timex value without the Time model - */ - public String getTravelDate(RecognizerResult result) { - JsonNode datetimeEntity = result.getEntities().get("datetime"); - if (datetimeEntity == null || datetimeEntity.get(0) == null) { - return null; - } - - JsonNode timex = datetimeEntity.get(0).get("timex"); - if (timex == null || timex.get(0) == null) { - return null; - } - - String datetime = timex.get(0).asText().split("T")[0]; - return datetime; - } - - /** - * Runs an utterance through a recognizer and returns a generic recognizer result. - * - * @param turnContext Turn context. - * @return Analysis of utterance. - */ - @Override - public CompletableFuture recognize(TurnContext turnContext) { - return this.recognizer.recognize(turnContext); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java deleted file mode 100644 index 6f913a559..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/MainDialog.java +++ /dev/null @@ -1,232 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.commons.lang3.StringUtils; - -/** - * The class containing the main dialog for the sample. - */ -public class MainDialog extends ComponentDialog { - - private final FlightBookingRecognizer luisRecognizer; - private final Integer plusDayValue = 7; - - /** - * The constructor of the Main Dialog class. - * - * @param withLuisRecognizer The FlightBookingRecognizer object. - * @param bookingDialog The BookingDialog object with booking dialogs. - */ - public MainDialog(FlightBookingRecognizer withLuisRecognizer, BookingDialog bookingDialog) { - super("MainDialog"); - - luisRecognizer = withLuisRecognizer; - - addDialog(new TextPrompt("TextPrompt")); - addDialog(bookingDialog); - WaterfallStep[] waterfallSteps = { - this::introStep, - this::actStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - /** - * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a - * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the - * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture introStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - Activity text = MessageFactory.text("NOTE: LUIS is not configured. " - + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " - + "to the appsettings.json file.", null, InputHints.IGNORING_INPUT); - return stepContext.getContext().sendActivity(text) - .thenCompose(sendResult -> stepContext.next(null)); - } - - // Use the text provided in FinalStepAsync or the default if it is the first time. - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); - String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); - String messageText = stepContext.getOptions() != null - ? stepContext.getOptions().toString() - : String.format("What can I help you with today?\n" - + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - /** - * Second step in the waterfall. This will use LUIS to attempt to extract the origin, - * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect - * any remaining details. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture actStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. - return stepContext.beginDialog("BookingDialog", new BookingDetails()); - } - - // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) - return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { - switch (luisResult.getTopScoringIntent().intent) { - case "BookFlight": - // Extract the values for the composite entities from the LUIS result. - ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); - ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); - - // Show a warning for Origin and Destination if we can't resolve them. - return showWarningForUnsupportedCities( - stepContext.getContext(), fromEntities, toEntities) - .thenCompose(showResult -> { - // Initialize BookingDetails with any entities we may have found in the response. - - BookingDetails bookingDetails = new BookingDetails(); - bookingDetails.setDestination(toEntities.get("airport").asText()); - bookingDetails.setOrigin(fromEntities.get("airport").asText()); - bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); - // Run the BookingDialog giving it whatever details we have from the LUIS call, - // it will fill out the remainder. - return stepContext.beginDialog("BookingDialog", bookingDetails); - } - ); - case "GetWeather": - // We haven't implemented the GetWeatherDialog so we just display a TODO message. - String getWeatherMessageText = "TODO: get weather flow here"; - Activity getWeatherMessage = MessageFactory - .text( - getWeatherMessageText, getWeatherMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(getWeatherMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - - default: - // Catch all for unhandled intents - String didntUnderstandMessageText = String.format( - "Sorry, I didn't get that. Please " - + " try asking in a different way (intent was %s)", - luisResult.getTopScoringIntent().intent - ); - Activity didntUnderstandMessage = MessageFactory - .text( - didntUnderstandMessageText, didntUnderstandMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(didntUnderstandMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - } - }); - } - - /** - * Shows a warning if the requested From or To cities are recognized as entities but they are - * not in the Airport entity list. In some cases LUIS will recognize the From and To composite - * entities as a valid cities but the From and To Airport values will be empty if those entity - * values can't be mapped to a canonical item in the Airport. - * - * @param turnContext A {@link WaterfallStepContext} - * @param fromEntities An ObjectNode with the entities of From object - * @param toEntities An ObjectNode with the entities of To object - * @return A task - */ - private static CompletableFuture showWarningForUnsupportedCities( - TurnContext turnContext, - ObjectNode fromEntities, - ObjectNode toEntities - ) { - List unsupportedCities = new ArrayList(); - - if (StringUtils.isNotBlank(fromEntities.get("from").asText()) - && StringUtils.isBlank(fromEntities.get("airport").asText())) { - unsupportedCities.add(fromEntities.get("from").asText()); - } - - if (StringUtils.isNotBlank(toEntities.get("to").asText()) - && StringUtils.isBlank(toEntities.get("airport").asText())) { - unsupportedCities.add(toEntities.get("to").asText()); - } - - if (!unsupportedCities.isEmpty()) { - String messageText = String.format( - "Sorry but the following airports are not supported: %s", - String.join(", ", unsupportedCities) - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - return turnContext.sendActivity(message) - .thenApply(sendResult -> null); - } - - return CompletableFuture.completedFuture(null); - } - - /** - * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" - * interaction with a simple confirmation. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - CompletableFuture stepResult = CompletableFuture.completedFuture(null); - - // If the child dialog ("BookingDialog") was cancelled, - // the user failed to confirm or if the intent wasn't BookFlight - // the Result here will be null. - if (stepContext.getResult() instanceof BookingDetails) { - // Now we have all the booking details call the booking service. - // If the call to the booking service was successful tell the user. - BookingDetails result = (BookingDetails) stepContext.getResult(); - TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); - String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); - String messageText = String.format("I have you booked to %s from %s on %s", - result.getDestination(), result.getOrigin(), travelDateMsg - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); - } - - // Restart the main dialog with a different message the second time around - String promptMessage = "What else can I do for you?"; - return stepResult - .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); - } -} diff --git a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java b/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java deleted file mode 100644 index c08ec99b7..000000000 --- a/samples/13.core-bot/src/main/java/com/microsoft/bot/sample/core/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. - -/** - * This package contains the classes for the core-bot sample. - */ -package com.microsoft.bot.sample.core; diff --git a/samples/13.core-bot/src/main/resources/application.properties b/samples/13.core-bot/src/main/resources/application.properties deleted file mode 100644 index 255d7cd56..000000000 --- a/samples/13.core-bot/src/main/resources/application.properties +++ /dev/null @@ -1,6 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -LuisAppId= -LuisAPIKey= -LuisAPIHostName= -server.port=3978 diff --git a/samples/13.core-bot/src/main/resources/cards/welcomeCard.json b/samples/13.core-bot/src/main/resources/cards/welcomeCard.json deleted file mode 100644 index 9b6389e39..000000000 --- a/samples/13.core-bot/src/main/resources/cards/welcomeCard.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "Image", - "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", - "size": "stretch" - }, - { - "type": "TextBlock", - "spacing": "medium", - "size": "default", - "weight": "bolder", - "text": "Welcome to Bot Framework!", - "wrap": true, - "maxLines": 0 - }, - { - "type": "TextBlock", - "size": "default", - "isSubtle": true, - "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", - "wrap": true, - "maxLines": 0 - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Get an overview", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - }, - { - "type": "Action.OpenUrl", - "title": "Ask a question", - "url": "https://stackoverflow.com/questions/tagged/botframework" - }, - { - "type": "Action.OpenUrl", - "title": "Learn how to deploy", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - } - ] -} diff --git a/samples/13.core-bot/src/main/resources/log4j2.json b/samples/13.core-bot/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/13.core-bot/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/13.core-bot/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml b/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/13.core-bot/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/13.core-bot/src/main/webapp/index.html b/samples/13.core-bot/src/main/webapp/index.html deleted file mode 100644 index 750c0f776..000000000 --- a/samples/13.core-bot/src/main/webapp/index.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - Core Bot Sample - - - - - -
-
-
-
Core Bot Sample
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - diff --git a/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java b/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java deleted file mode 100644 index 6c266138a..000000000 --- a/samples/13.core-bot/src/test/java/com/microsoft/bot/sample/core/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.core; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/14.nlp-with-dispatch/.vscode/settings.json b/samples/14.nlp-with-dispatch/.vscode/settings.json deleted file mode 100644 index e0f15db2e..000000000 --- a/samples/14.nlp-with-dispatch/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "java.configuration.updateBuildConfiguration": "automatic" -} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/LICENSE b/samples/14.nlp-with-dispatch/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/14.nlp-with-dispatch/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/14.nlp-with-dispatch/README.md b/samples/14.nlp-with-dispatch/README.md deleted file mode 100644 index ef05e09ea..000000000 --- a/samples/14.nlp-with-dispatch/README.md +++ /dev/null @@ -1,103 +0,0 @@ -# NLP with Dispatch - -Bot Framework v4 NLP with Dispatch bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to create a bot that relies on multiple [LUIS.ai](https://www.luis.ai) and [QnAMaker.ai](https://www.qnamaker.ai) models for natural language processing (NLP). - -Use the Dispatch model in cases when: - -- Your bot consists of multiple language modules (LUIS + QnA) and you need assistance in routing user's utterances to these modules in order to integrate the different modules into your bot. -- Evaluate quality of intents classification of a single LUIS model. -- Create a text classification model from text files. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -### Use Dispatch with Multiple LUIS and QnA Models - -To learn how to configure Dispatch with multiple LUIS models and QnA Maker services, refer to the steps found [here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-tutorial-dispatch?view=azure-bot-service-4.0). - - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\nlp-with-dispatch-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Using LUIS for Language Understanding](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=js) -- [LUIS documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/LUIS/) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) - diff --git a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json b/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/14.nlp-with-dispatch/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/14.nlp-with-dispatch/pom.xml b/samples/14.nlp-with-dispatch/pom.xml deleted file mode 100644 index ef763c809..000000000 --- a/samples/14.nlp-with-dispatch/pom.xml +++ /dev/null @@ -1,248 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - nlp-with-dispatch - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java NLP with Dispatch sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.nlpwithdispatch.Application - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-ai-luis-v3 - 4.13.0-SNAPSHOT - - - com.microsoft.bot - bot-ai-qna - 4.13.0-SNAPSHOT - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.nlpwithdispatch.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java deleted file mode 100644 index 5efd67782..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Application.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.nlpwithdispatch; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.nlpwithdispatch.bots.DispatchBot; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot(BotServices botServices) { - return new DispatchBot(botServices); - } - - @Bean - public BotServices getBotServices(Configuration configuration) { - return new BotServicesImpl(configuration); - } - - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java deleted file mode 100644 index 21d585047..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServices.java +++ /dev/null @@ -1,12 +0,0 @@ -package com.microsoft.bot.sample.nlpwithdispatch; - -import com.microsoft.bot.ai.luis.LuisRecognizer; -import com.microsoft.bot.ai.qna.QnAMaker; - -public interface BotServices { - - LuisRecognizer getDispatch(); - - QnAMaker getSampleQnA(); - -} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java deleted file mode 100644 index f94fdb927..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/BotServicesImpl.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.nlpwithdispatch; - -import com.microsoft.bot.ai.luis.LuisApplication; -import com.microsoft.bot.ai.luis.LuisRecognizer; -import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; -import com.microsoft.bot.ai.qna.QnAMaker; -import com.microsoft.bot.ai.qna.QnAMakerEndpoint; -import com.microsoft.bot.integration.Configuration; - -public class BotServicesImpl implements BotServices { - - private LuisRecognizer dispatch; - - private QnAMaker sampleQnA; - - public BotServicesImpl(Configuration configuration) { - // Read the setting for cognitive services (LUS, QnA) from the application.properties file. - // If includeApiResults instanceof set to true, the full response from the LUS api (LuisResult) - // will be made available in the properties collection of the RecognizerResult - - LuisApplication luisApplication = new LuisApplication( - configuration.getProperty("LuisAppId"), - configuration.getProperty("LuisAPIKey"), - String.format("https://%s.api.cognitive.microsoft.com", - configuration.getProperty("LuisAPIHostName"))); - - // Set the recognizer options depending on which endpoint version you want to use. - // More details can be found in https://docs.getmicrosoft().com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); - recognizerOptions.setIncludeAPIResults(true); - recognizerOptions.setIncludeAllIntents(true); - recognizerOptions.setIncludeInstanceData(true); - - dispatch = new LuisRecognizer(recognizerOptions); - - QnAMakerEndpoint qnaMakerEndpoint = new QnAMakerEndpoint(); - qnaMakerEndpoint.setKnowledgeBaseId(configuration.getProperty("QnAKnowledgebaseId")); - qnaMakerEndpoint.setEndpointKey(configuration.getProperty("QnAEndpointKey")); - qnaMakerEndpoint.setHost(configuration.getProperty("QnAEndpointHostName")); - - sampleQnA = new QnAMaker(qnaMakerEndpoint, null); - } - - /** - * @return the Dispatch value as a LuisRecognizer. - */ - public LuisRecognizer getDispatch() { - return this.dispatch; - } - - /** - * @return the SampleQnA value as a QnAMaker. - */ - public QnAMaker getSampleQnA() { - return this.sampleQnA; - } -} - diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java deleted file mode 100644 index 8f4f36235..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/Intent.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.nlpwithdispatch; - -import java.util.List; - -public class Intent { - private String name; - private Double score; - private List childIntents; - private String topIntent; - - public String getName() { - return this.name; - } - - public void setName(String name) { - this.name = name; - } - - public Double getScore() { - return this.score; - } - - public void setScore(Double score) { - this.score = score; - } - - public List getChildIntents() { - return this.childIntents; - } - - public void setChildIntents(List childIntents) { - this.childIntents = childIntents; - } - - public String getTopIntent() { - return this.topIntent; - } - - public void setTopIntent(String topIntent) { - this.topIntent = topIntent; - } - -} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java deleted file mode 100644 index f331d57d3..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/PredictionResult.java +++ /dev/null @@ -1,27 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.nlpwithdispatch; - -import java.util.List; - -public class PredictionResult { - private String topIntent; - private List intents; - - public String getTopIntent() { - return this.topIntent; - } - - public void setTopIntent(String topIntent) { - this.topIntent = topIntent; - } - - public List getIntents() { - return this.intents; - } - - public void setIntents(List intents) { - this.intents = intents; - } -} diff --git a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java b/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java deleted file mode 100644 index 6efd6de3c..000000000 --- a/samples/14.nlp-with-dispatch/src/main/java/com/microsoft/bot/sample/nlpwithdispatch/bots/DispatchBot.java +++ /dev/null @@ -1,229 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.nlpwithdispatch.bots; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.fasterxml.jackson.databind.JsonNode; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.RecognizerResult; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.RecognizerResult.NamedIntentScore; -import com.microsoft.bot.sample.nlpwithdispatch.BotServices; -import com.microsoft.bot.sample.nlpwithdispatch.Intent; -import com.microsoft.bot.sample.nlpwithdispatch.PredictionResult; -import com.microsoft.bot.schema.ChannelAccount; - -import org.apache.commons.lang3.StringUtils; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class DispatchBot extends ActivityHandler { - - private final Logger _logger; - private final BotServices _botServices; - - public DispatchBot(BotServices botServices) { - _logger = LoggerFactory.getLogger(DispatchBot.class); - _botServices = botServices; - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - // First, we use the dispatch model to determine which cognitive service (LUS or - // QnA) to use. - RecognizerResult recognizerResult = _botServices.getDispatch().recognize(turnContext).join(); - - // Top intent tell us which cognitive service to use. - NamedIntentScore topIntent = recognizerResult.getTopScoringIntent(); - - // Next, we call the dispatcher with the top intent. - return dispatchToTopIntent(turnContext, topIntent.intent, recognizerResult); - } - - @Override - protected CompletableFuture onMembersAdded(List membersAdded, TurnContext turnContext) { - final String WelcomeText = "Type a greeting, or a question about the weather to get started."; - - return membersAdded.stream() - .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(member -> { - String msg = String.format("Welcome to Dispatch bot %s. %s", member.getName(), WelcomeText); - return turnContext.sendActivity(MessageFactory.text(msg)); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponses -> null); - } - - private CompletableFuture dispatchToTopIntent( - TurnContext turnContext, - String intent, - RecognizerResult recognizerResult - ) { - switch (intent) { - case "l_HomeAutomation": - return processHomeAutomation(turnContext, recognizerResult); - - case "l_Weather": - return processWeather(turnContext, recognizerResult); - - case "q_sample-qna": - return processSampleQnA(turnContext); - - default: - _logger.info(String.format("Dispatch unrecognized intent: %s.", intent)); - return turnContext - .sendActivity(MessageFactory.text(String.format("Dispatch unrecognized intent: %s.", intent))) - .thenApply(result -> null); - } - } - - private CompletableFuture processHomeAutomation(TurnContext turnContext, RecognizerResult luisResult) { - _logger.info("ProcessHomeAutomationAsync"); - - // Retrieve LUIS result for Process Automation. - PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); - - Intent topIntent = predictionResult.getIntents().get(0); - return turnContext - .sendActivity(MessageFactory.text(String.format("HomeAutomation top intent %s.", topIntent.getTopIntent()))) - .thenCompose(sendResult -> { - List intents = - topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); - return turnContext - .sendActivity( - MessageFactory - .text(String.format("HomeAutomation intents detected:\n\n%s", String.join("\n\n", intents))) - ) - .thenCompose(nextSendResult -> { - if (luisResult.getEntities() != null) { - List entities = mapEntities(luisResult.getEntities()); - if (entities.size() > 0) { - return turnContext - .sendActivity( - MessageFactory.text( - String.format( - "HomeAutomation entities were found in the message:\n\n%s", - String.join("\n\n", entities) - ) - ) - ) - .thenApply(finalSendResult -> null); - } - } - return CompletableFuture.completedFuture(null); - }); - }); - } - - private List mapEntities(JsonNode entityNode) { - List entities = new ArrayList(); - for (Iterator> child = entityNode.fields(); child.hasNext();) { - Map.Entry childIntent = child.next(); - String childName = childIntent.getKey(); - if (!childName.startsWith("$")) { - entities.add(childIntent.getValue().get(0).asText()); - } - } - return entities; - } - - private PredictionResult mapPredictionResult(JsonNode luisResult) { - JsonNode prediction = luisResult.get("prediction"); - JsonNode intentsObject = prediction.get("intents"); - if (intentsObject == null) { - return null; - } - PredictionResult result = new PredictionResult(); - result.setTopIntent(prediction.get("topIntent").asText()); - List intents = new ArrayList(); - for (Iterator> it = intentsObject.fields(); it.hasNext();) { - Map.Entry intent = it.next(); - double score = intent.getValue().get("score").asDouble(); - String intentName = intent.getKey().replace(".", "_").replace(" ", "_"); - Intent newIntent = new Intent(); - newIntent.setName(intentName); - newIntent.setScore(score); - JsonNode childNode = intent.getValue().get("childApp"); - if (childNode != null) { - newIntent.setTopIntent(childNode.get("topIntent").asText()); - List childIntents = new ArrayList(); - JsonNode childIntentNodes = childNode.get("intents"); - for (Iterator> child = childIntentNodes.fields(); child.hasNext();) { - Map.Entry childIntent = child.next(); - double childScore = childIntent.getValue().get("score").asDouble(); - String childIntentName = childIntent.getKey(); - Intent newChildIntent = new Intent(); - newChildIntent.setName(childIntentName); - newChildIntent.setScore(childScore); - childIntents.add(newChildIntent); - } - newIntent.setChildIntents(childIntents); - } - - intents.add(newIntent); - } - result.setIntents(intents); - return result; - } - - private CompletableFuture processWeather(TurnContext turnContext, RecognizerResult luisResult) { - _logger.info("ProcessWeatherAsync"); - - // Retrieve LUIS result for Process Automation. - PredictionResult predictionResult = mapPredictionResult(luisResult.getProperties().get("luisResult")); - - Intent topIntent = predictionResult.getIntents().get(0); - return turnContext - .sendActivity(MessageFactory.text(String.format("ProcessWeather top intent %s.", topIntent.getTopIntent()))) - .thenCompose(sendResult -> { - List intents = - topIntent.getChildIntents().stream().map(x -> x.getName()).collect(Collectors.toList()); - return turnContext - .sendActivity( - MessageFactory - .text(String.format("ProcessWeather Intents detected:\n\n%s", String.join("\n\n", intents))) - ) - .thenCompose(secondResult -> { - if (luisResult.getEntities() != null) { - List entities = mapEntities(luisResult.getEntities()); - if (entities.size() > 0) { - return turnContext - .sendActivity( - MessageFactory.text( - String.format( - "ProcessWeather entities were found in the message:\n\n%s", - String.join("\n\n", entities) - ) - ) - ) - .thenApply(finalResult -> null); - } - } - return CompletableFuture.completedFuture(null); - }); - }); - } - - private CompletableFuture processSampleQnA(TurnContext turnContext) { - _logger.info("ProcessSampleQnAAsync"); - - return _botServices.getSampleQnA().getAnswers(turnContext, null).thenCompose(results -> { - if (results.length > 0) { - return turnContext.sendActivity(MessageFactory.text(results[0].getAnswer())).thenApply(result -> null); - } else { - return turnContext - .sendActivity(MessageFactory.text("Sorry, could not find an answer in the Q and A system.")) - .thenApply(result -> null); - } - }); - } -} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json b/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json deleted file mode 100644 index 245daedce..000000000 --- a/samples/14.nlp-with-dispatch/src/main/resources/HomeAutomation.json +++ /dev/null @@ -1,605 +0,0 @@ -{ - "luis_schema_version": "3.2.0", - "versionId": "0.1", - "name": "Home Automation", - "desc": "Home Automation LUIS application - Bot Builder Samples", - "culture": "en-us", - "tokenizerVersion": "1.0.0", - "intents": [ - { - "name": "HomeAutomation" - }, - { - "name": "None" - } - ], - "entities": [ - { - "name": "Device", - "roles": [] - }, - { - "name": "deviceProperty", - "roles": [] - }, - { - "name": "Room", - "roles": [] - } - ], - "composites": [], - "closedLists": [ - { - "name": "Operation", - "subLists": [ - { - "canonicalForm": "off", - "list": [ - "off", - "turn off", - "switch off", - "lock", - "out", - "shut down", - "stop" - ] - }, - { - "canonicalForm": "on", - "list": [ - "on", - "turn on", - "switch on", - "unlock", - "un lock", - "boot up", - "start" - ] - } - ], - "roles": [] - } - ], - "patternAnyEntities": [ - { - "name": "Device_PatternAny", - "roles": [], - "explicitList": [] - }, - { - "name": "Room_PatternAny", - "roles": [], - "explicitList": [] - } - ], - "regex_entities": [], - "prebuiltEntities": [ - { - "name": "number", - "roles": [] - } - ], - "model_features": [], - "regex_features": [], - "patterns": [ - { - "pattern": "{Device_PatternAny} in {Room_PatternAny} on [please]", - "intent": "HomeAutomation" - }, - { - "pattern": "{Device_PatternAny} on [please]", - "intent": "HomeAutomation" - }, - { - "pattern": "turn off {Device_PatternAny} in {Room_PatternAny}", - "intent": "HomeAutomation" - }, - { - "pattern": "turn on {Device_PatternAny} in {Room_PatternAny}", - "intent": "HomeAutomation" - }, - { - "pattern": "turn on {Device_PatternAny}", - "intent": "HomeAutomation" - }, - { - "pattern": "{Device_PatternAny} in {Room_PatternAny} off [please]", - "intent": "HomeAutomation" - }, - { - "pattern": "{Device_PatternAny} off [please]", - "intent": "HomeAutomation" - } - ], - "utterances": [ - { - "text": "breezeway on please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 0, - "endPos": 8 - } - ] - }, - { - "text": "change temperature to seventy two degrees", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "deviceProperty", - "startPos": 7, - "endPos": 17 - } - ] - }, - { - "text": "coffee bar on please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 0, - "endPos": 9 - } - ] - }, - { - "text": "decrease temperature for me please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "deviceProperty", - "startPos": 9, - "endPos": 19 - } - ] - }, - { - "text": "dim kitchen lights to 25 .", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 4, - "endPos": 10 - }, - { - "entity": "Device", - "startPos": 12, - "endPos": 17 - } - ] - }, - { - "text": "fish pond off please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 0, - "endPos": 8 - } - ] - }, - { - "text": "fish pond on please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 0, - "endPos": 8 - } - ] - }, - { - "text": "illuminate please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 0, - "endPos": 9 - } - ] - }, - { - "text": "living room lamp on please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 0, - "endPos": 10 - }, - { - "entity": "Device", - "startPos": 12, - "endPos": 15 - } - ] - }, - { - "text": "living room lamps off please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 0, - "endPos": 10 - }, - { - "entity": "Device", - "startPos": 12, - "endPos": 16 - } - ] - }, - { - "text": "lock the doors for me please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 9, - "endPos": 13 - } - ] - }, - { - "text": "lower your volume", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "deviceProperty", - "startPos": 11, - "endPos": 16 - } - ] - }, - { - "text": "make camera 1 off please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 5, - "endPos": 12 - } - ] - }, - { - "text": "make some coffee", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 10, - "endPos": 15 - } - ] - }, - { - "text": "play dvd", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 5, - "endPos": 7 - } - ] - }, - { - "text": "set lights out in bedroom", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 4, - "endPos": 9 - }, - { - "entity": "Room", - "startPos": 18, - "endPos": 24 - } - ] - }, - { - "text": "set lights to bright", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 4, - "endPos": 9 - }, - { - "entity": "deviceProperty", - "startPos": 14, - "endPos": 19 - } - ] - }, - { - "text": "set lights to concentrate", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 4, - "endPos": 9 - }, - { - "entity": "deviceProperty", - "startPos": 14, - "endPos": 24 - } - ] - }, - { - "text": "shut down my work computer", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 18, - "endPos": 25 - } - ] - }, - { - "text": "snap switch fan fifty percent", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 12, - "endPos": 14 - } - ] - }, - { - "text": "start master bedroom light.", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 6, - "endPos": 19 - }, - { - "entity": "Device", - "startPos": 21, - "endPos": 25 - } - ] - }, - { - "text": "theater on please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 0, - "endPos": 6 - } - ] - }, - { - "text": "turn dimmer off", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 5, - "endPos": 10 - } - ] - }, - { - "text": "turn off ac please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 9, - "endPos": 10 - } - ] - }, - { - "text": "turn off foyer lights", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 9, - "endPos": 13 - }, - { - "entity": "Device", - "startPos": 15, - "endPos": 20 - } - ] - }, - { - "text": "turn off living room light", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 9, - "endPos": 19 - }, - { - "entity": "Device", - "startPos": 21, - "endPos": 25 - } - ] - }, - { - "text": "turn off staircase", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 9, - "endPos": 17 - } - ] - }, - { - "text": "turn off venice lamp", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 16, - "endPos": 19 - } - ] - }, - { - "text": "turn on bathroom heater", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 8, - "endPos": 15 - }, - { - "entity": "Device", - "startPos": 17, - "endPos": 22 - } - ] - }, - { - "text": "turn on external speaker", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 8, - "endPos": 23 - } - ] - }, - { - "text": "turn on kitchen faucet", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 8, - "endPos": 14 - }, - { - "entity": "Device", - "startPos": 16, - "endPos": 21 - } - ] - }, - { - "text": "turn on light in bedroom", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 8, - "endPos": 12 - }, - { - "entity": "Room", - "startPos": 17, - "endPos": 23 - } - ] - }, - { - "text": "turn on my bedroom lights.", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 11, - "endPos": 17 - }, - { - "entity": "Device", - "startPos": 19, - "endPos": 24 - } - ] - }, - { - "text": "turn on the furnace room lights", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 12, - "endPos": 23 - }, - { - "entity": "Device", - "startPos": 25, - "endPos": 30 - } - ] - }, - { - "text": "turn on the internet in my bedroom please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Room", - "startPos": 27, - "endPos": 33 - } - ] - }, - { - "text": "turn on thermostat please", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 8, - "endPos": 17 - } - ] - }, - { - "text": "turn the fan to high", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 9, - "endPos": 11 - } - ] - }, - { - "text": "turn thermostat on 70.", - "intent": "HomeAutomation", - "entities": [ - { - "entity": "Device", - "startPos": 5, - "endPos": 14 - } - ] - } - ], - "settings": [] -} \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv b/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv deleted file mode 100644 index 5f42eaf85..000000000 --- a/samples/14.nlp-with-dispatch/src/main/resources/QnAMaker.tsv +++ /dev/null @@ -1,11 +0,0 @@ -Question Answer Source Metadata -hi Hello! QnAMaker.tsv -greetings Hello! QnAMaker.tsv -good morning Hello! QnAMaker.tsv -good evening Hello! QnAMaker.tsv -What are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv -What? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv -What do you do? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv -Who are you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv -What is your name? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv -What should I call you? I am the LUIS-QnAMaker Dispatch bot! This sample demonstrates using several LUIS applications and QnA Maker knowledge base using dispatch. QnAMaker.tsv \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/resources/Weather.json b/samples/14.nlp-with-dispatch/src/main/resources/Weather.json deleted file mode 100644 index c532ee35d..000000000 --- a/samples/14.nlp-with-dispatch/src/main/resources/Weather.json +++ /dev/null @@ -1,315 +0,0 @@ -{ - "luis_schema_version": "3.2.0", - "name": "Weather", - "versionId": "0.1", - "desc": "Weather LUIS application - Bot Builder Samples", - "culture": "en-us", - "intents": [ - { - "name": "Get Weather Condition" - }, - { - "name": "Get Weather Forecast" - }, - { - "name": "None" - } - ], - "entities": [ - { - "name": "Location", - "roles": [] - } - ], - "closedLists": [], - "composites": [], - "patternAnyEntities": [ - { - "name": "Location_PatternAny", - "explicitList": [], - "roles": [] - } - ], - "regex_entities": [], - "prebuiltEntities": [], - "regex_features": [], - "model_features": [], - "patterns": [ - { - "pattern": "weather in {Location_PatternAny}", - "intent": "Get Weather Condition" - }, - { - "pattern": "how's the weather in {Location_PatternAny}", - "intent": "Get Weather Condition" - }, - { - "pattern": "current weather in {Location_PatternAny}", - "intent": "Get Weather Condition" - }, - { - "pattern": "what's the forecast for next week in {Location_PatternAny}", - "intent": "Get Weather Forecast" - }, - { - "pattern": "show me the forecast for {Location_PatternAny}", - "intent": "Get Weather Forecast" - }, - { - "pattern": "what's the forecast for {Location_PatternAny}", - "intent": "Get Weather Forecast" - } - ], - "utterances": [ - { - "text": "current weather ?", - "intent": "Get Weather Condition", - "entities": [] - }, - { - "text": "do florida residents usually need ice scrapers", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 3, - "endPos": 9, - "entity": "Location" - } - ] - }, - { - "text": "forecast in celcius", - "intent": "Get Weather Forecast", - "entities": [] - }, - { - "text": "get florence temperature in september", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 4, - "endPos": 11, - "entity": "Location" - } - ] - }, - { - "text": "get for me the weather conditions in sonoma county", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 37, - "endPos": 49, - "entity": "Location" - } - ] - }, - { - "text": "get the daily temperature greenwood indiana", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 26, - "endPos": 42, - "entity": "Location" - } - ] - }, - { - "text": "get the forcast for me", - "intent": "Get Weather Forecast", - "entities": [] - }, - { - "text": "get the weather at saint george utah", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 19, - "endPos": 35, - "entity": "Location" - } - ] - }, - { - "text": "how much rain does chambersburg get a year", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 19, - "endPos": 30, - "entity": "Location" - } - ] - }, - { - "text": "i want to know the temperature at death valley", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 34, - "endPos": 45, - "entity": "Location" - } - ] - }, - { - "text": "provide me by toronto weather please", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 14, - "endPos": 20, - "entity": "Location" - } - ] - }, - { - "text": "show average rainfall for boise", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 26, - "endPos": 30, - "entity": "Location" - } - ] - }, - { - "text": "show me the forecast at alabama", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 24, - "endPos": 30, - "entity": "Location" - } - ] - }, - { - "text": "soliciting today's weather", - "intent": "Get Weather Forecast", - "entities": [] - }, - { - "text": "temperature of delhi in celsius please", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 15, - "endPos": 19, - "entity": "Location" - } - ] - }, - { - "text": "was last year about this time as wet as it is now in the south ?", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 57, - "endPos": 61, - "entity": "Location" - } - ] - }, - { - "text": "what is the rain volume in sonoma county ?", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 27, - "endPos": 39, - "entity": "Location" - } - ] - }, - { - "text": "what is the weather in redmond ?", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 23, - "endPos": 29, - "entity": "Location" - } - ] - }, - { - "text": "what is the weather today at 10 day durham ?", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 36, - "endPos": 41, - "entity": "Location" - } - ] - }, - { - "text": "what to wear in march in california", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 25, - "endPos": 34, - "entity": "Location" - } - ] - }, - { - "text": "what will the weather be tomorrow in new york ?", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 37, - "endPos": 44, - "entity": "Location" - } - ] - }, - { - "text": "what's the weather going to be like in hawaii ?", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 39, - "endPos": 44, - "entity": "Location" - } - ] - }, - { - "text": "what's the weather like in minneapolis", - "intent": "Get Weather Condition", - "entities": [ - { - "startPos": 27, - "endPos": 37, - "entity": "Location" - } - ] - }, - { - "text": "will it be raining in ranchi", - "intent": "Get Weather Forecast", - "entities": [ - { - "startPos": 22, - "endPos": 27, - "entity": "Location" - } - ] - }, - { - "text": "will it rain this weekend", - "intent": "Get Weather Forecast", - "entities": [] - }, - { - "text": "will it snow today", - "intent": "Get Weather Forecast", - "entities": [] - } - ] -} diff --git a/samples/14.nlp-with-dispatch/src/main/resources/application.properties b/samples/14.nlp-with-dispatch/src/main/resources/application.properties deleted file mode 100644 index 5e1dbd85c..000000000 --- a/samples/14.nlp-with-dispatch/src/main/resources/application.properties +++ /dev/null @@ -1,11 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 - -QnAKnowledgebaseId= -QnAEndpointKey= -QnAEndpointHostName= - -LuisAppId = -LuisAPIKey= -LuisAPIHostName= diff --git a/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json b/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/14.nlp-with-dispatch/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF b/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/14.nlp-with-dispatch/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml b/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/14.nlp-with-dispatch/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/14.nlp-with-dispatch/src/main/webapp/index.html b/samples/14.nlp-with-dispatch/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/14.nlp-with-dispatch/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java b/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java deleted file mode 100644 index b2491036f..000000000 --- a/samples/14.nlp-with-dispatch/src/test/java/com/microsoft/bot/sample/nlpwithdispatch/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.nlpwithdispatch; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/15.handling-attachments/LICENSE b/samples/15.handling-attachments/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/15.handling-attachments/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/15.handling-attachments/README.md b/samples/15.handling-attachments/README.md deleted file mode 100644 index 442890d96..000000000 --- a/samples/15.handling-attachments/README.md +++ /dev/null @@ -1,97 +0,0 @@ -# Handling Attachments - -Bot Framework v4 handling attachments bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to send outgoing attachments and how to save attachments to disk. - -> **NOTE: A specific example for Microsoft Teams, demonstrating how to -upload files to Teams from a bot and how to receive a file sent to a bot as an attachment, can be found [here](../56.teams-file-upload)** - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-attachments-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Interacting with the bot - -A message exchange between user and bot may contain cards and media attachments, such as images, video, audio, and files. -The types of attachments that may be sent and received varies by channel. Additionally, a bot may also receive file attachments. - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="echoBotPlan" newWebAppName="echoBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="echoBot" newAppServicePlanName="echoBotPlan" appServicePlanLocation="westus" --name "echoBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Attachments](https://docs.microsoft.com/en-us/azure/bot-service/nodejs/bot-builder-nodejs-send-receive-attachments?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json b/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/15.handling-attachments/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json b/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/15.handling-attachments/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/15.handling-attachments/pom.xml b/samples/15.handling-attachments/pom.xml deleted file mode 100644 index 01266ebe2..000000000 --- a/samples/15.handling-attachments/pom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-attachments - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Echo sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.attachments.Application - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - commons-io - commons-io - 2.8.0 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.attachments.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java deleted file mode 100644 index c74fc3a9b..000000000 --- a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/Application.java +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.attachments; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new AttachmentsBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java b/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java deleted file mode 100644 index ca77a88e1..000000000 --- a/samples/15.handling-attachments/src/main/java/com/microsoft/bot/sample/attachments/AttachmentsBot.java +++ /dev/null @@ -1,254 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.attachments; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotFrameworkAdapter; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.connector.Async; -import com.microsoft.bot.connector.Attachments; -import com.microsoft.bot.connector.ConnectorClient; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.AttachmentData; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.HeroCard; -import java.io.FileOutputStream; -import java.io.InputStream; -import java.net.URL; -import java.nio.channels.Channels; -import java.nio.channels.ReadableByteChannel; -import java.util.Base64; -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would be added. For this - * sample, the {@link #onMessageActivity(TurnContext)} echos the text back to the user. The {@link - * #onMembersAdded(List, TurnContext)} will send a greeting to new conversation participants. - *

- */ -public class AttachmentsBot extends ActivityHandler { - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - return processInput(turnContext) - .thenCompose(turnContext::sendActivity) - .thenCompose(resourceResponse -> displayOptions(turnContext)); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return sendWelcomeMessage(turnContext); - } - - // Greet the user and give them instructions on how to interact with the bot. - private CompletableFuture sendWelcomeMessage(TurnContext turnContext) { - return turnContext.getActivity().getMembersAdded().stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ).map(member -> { - String msg = String.format( - "Welcome to AttachmentsBot %s. This bot will " - + "introduce you to Attachments. Please select an option", - member.getName() - ); - return turnContext.sendActivity(MessageFactory.text(msg)) - .thenCompose(resourceResponse -> displayOptions(turnContext)); - }) - .collect(CompletableFutures.toFutureList()).thenApply(resourceResponses -> null); - } - - private CompletableFuture displayOptions(TurnContext turnContext) { - // Create a HeroCard with options for the user to interact with the bot. - HeroCard card = new HeroCard(); - card.setText("You can upload an image or select one of the following choices"); - - // Note that some channels require different values to be used in order to get buttons to display text. - // In this code the emulator is accounted for with the 'title' parameter, but in other channels you may - // need to provide a value for other parameters like 'text' or 'displayText'. - card.setButtons( - new CardAction(ActionTypes.IM_BACK, "1. Inline Attachment", "1"), - new CardAction(ActionTypes.IM_BACK, "2. Internet Attachment", "2"), - new CardAction(ActionTypes.IM_BACK, "3. Uploaded Attachment", "3") - ); - - Activity reply = MessageFactory.attachment(card.toAttachment()); - return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); - } - - // Given the input from the message, create the response. - private CompletableFuture processInput(TurnContext turnContext) { - Activity activity = turnContext.getActivity(); - - if (activity.getAttachments() != null && !activity.getAttachments().isEmpty()) { - // We know the user is sending an attachment as there is at least one item - // in the Attachments list. - return CompletableFuture.completedFuture(handleIncomingAttachment(activity)); - } - - return handleOutgoingAttachment(turnContext, activity); - } - - private CompletableFuture handleOutgoingAttachment(TurnContext turnContext, Activity activity) { - CompletableFuture result; - - if (activity.getText().startsWith("1")) { - result = getInlineAttachment() - .thenApply(attachment -> { - Activity reply = MessageFactory.text("This is an inline attachment."); - reply.setAttachment(attachment); - return reply; - }); - } else if (activity.getText().startsWith("2")) { - result = getInternetAttachment() - .thenApply(attachment -> { - Activity reply = MessageFactory.text("This is an attachment from a HTTP URL."); - reply.setAttachment(attachment); - return reply; - }); - } else if (activity.getText().startsWith("3")) { - // Get the uploaded attachment. - result = getUploadedAttachment( - turnContext, activity.getServiceUrl(), activity.getConversation().getId()) - .thenApply(attachment -> { - Activity reply = MessageFactory.text("This is an uploaded attachment."); - reply.setAttachment(attachment); - return reply; - }); - } else { - result = CompletableFuture.completedFuture( - MessageFactory.text("Your input was not recognized please try again.") - ); - } - - return result - .exceptionally(ex -> MessageFactory.text( - "There was an error handling the attachment: " + ex.getMessage()) - ); - } - - // Handle attachments uploaded by users. The bot receives an Attachment in an Activity. - // The activity has a "List" of attachments. - // Not all channels allow users to upload files. Some channels have restrictions - // on file type, size, and other attributes. Consult the documentation for the channel for - // more information. For example Skype's limits are here - // . - private Activity handleIncomingAttachment(Activity activity) { - String replyText = ""; - for (Attachment file : activity.getAttachments()) { - ReadableByteChannel remoteChannel = null; - FileOutputStream fos = null; - - try { - // Determine where the file is hosted. - URL remoteFileUrl = new URL(file.getContentUrl()); - - // Save the attachment to the local system - String localFileName = file.getName(); - - // Download the actual attachment - remoteChannel = Channels.newChannel(remoteFileUrl.openStream()); - fos = new FileOutputStream(localFileName); - fos.getChannel().transferFrom(remoteChannel, 0, Long.MAX_VALUE); - } catch (Throwable t) { - replyText += "Attachment \"" + file.getName() + "\" failed to download.\r\n"; - } finally { - if (remoteChannel != null) { - try {remoteChannel.close(); } catch (Throwable ignored) {}; - } - if (fos != null) { - try {fos.close(); } catch (Throwable ignored) {}; - } - } - } - - return MessageFactory.text(replyText); - } - - // Creates an inline attachment sent from the bot to the user using a base64 string. - // Using a base64 string to send an attachment will not work on all channels. - // Additionally, some channels will only allow certain file types to be sent this way. - // For example a .png file may work but a .pdf file may not on some channels. - // Please consult the channel documentation for specifics. - private CompletableFuture getInlineAttachment() { - return getEncodedFileData("architecture-resize.png") - .thenApply(encodedFileData -> { - Attachment attachment = new Attachment(); - attachment.setName("architecture-resize.png"); - attachment.setContentType("image/png"); - attachment.setContentUrl("data:image/png;base64," + encodedFileData); - return attachment; - }); - } - - // Creates an Attachment to be sent from the bot to the user from a HTTP URL. - private CompletableFuture getInternetAttachment() { - Attachment attachment = new Attachment(); - attachment.setName("architecture-resize.png"); - attachment.setContentType("image/png"); - attachment.setContentUrl("https://docs.microsoft.com/en-us/bot-framework/media/how-it-works/architecture-resize.png"); - return CompletableFuture.completedFuture(attachment); - } - - private CompletableFuture getUploadedAttachment(TurnContext turnContext, String serviceUrl, String conversationId) { - if (StringUtils.isEmpty(serviceUrl)) { - return Async.completeExceptionally(new IllegalArgumentException("serviceUrl")); - } - if (StringUtils.isEmpty(conversationId)) { - return Async.completeExceptionally(new IllegalArgumentException("conversationId")); - } - - ConnectorClient connector = turnContext.getTurnState() - .get(BotFrameworkAdapter.CONNECTOR_CLIENT_KEY); - Attachments attachments = connector.getAttachments(); - - return getFileData("architecture-resize.png") - .thenCompose(fileData -> { - AttachmentData attachmentData = new AttachmentData(); - attachmentData.setName("architecture-resize.png"); - attachmentData.setType("image/png"); - attachmentData.setOriginalBase64(fileData); - - return connector.getConversations().uploadAttachment(conversationId, attachmentData) - .thenApply(response -> { - String attachmentUri = attachments.getAttachmentUri(response.getId()); - - Attachment attachment = new Attachment(); - attachment.setName("architecture-resize.png"); - attachment.setContentType("image/png"); - attachment.setContentUrl(attachmentUri); - - return attachment; - }); - }); - } - - private CompletableFuture getEncodedFileData(String filename) { - return getFileData(filename) - .thenApply(fileData -> Base64.getEncoder().encodeToString(fileData)); - } - - private CompletableFuture getFileData(String filename) { - try (InputStream inputStream = Thread.currentThread().getContextClassLoader().getResourceAsStream(filename)) { - return CompletableFuture.completedFuture(IOUtils.toByteArray(inputStream)); - } catch (Throwable t) { - return Async.completeExceptionally(t); - } - } -} diff --git a/samples/15.handling-attachments/src/main/resources/application.properties b/samples/15.handling-attachments/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/15.handling-attachments/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/15.handling-attachments/src/main/resources/architecture-resize.png b/samples/15.handling-attachments/src/main/resources/architecture-resize.png deleted file mode 100644 index e65f0f7332b79a3f65cd5a4ea6c33c7b5f979f25..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 137666 zcmeEtx_YOAr-8-0O1UTp?w5DGhp&J+% zRT=Sj)f2>jp*tTe#T3Qfy{n5wdN%$D-A8o%s_pXb9cu5t7Yq)a+>dwf-il-;#ne3w zPO{;Rum|u4yKj^bI`21p&O7DQ3VCo~K4Pn=KkF(N(ve7hux6Mklud*l6GE56obj1_ z0rPmk%OD7ezQ3NO_GoBmZ}4dcY_wck41&l-3#P3&38SU|ZE8psio5^&{lAUZ_*9_s z|2b};)<{10pD$-DVlyFd{pZ{JTvABl|NAokb*kd@|3CHL(EY!M>4J-rn;SQkSzp}q zdm8=TJUKrp|^wcl=P%YSByd^AQPmGxWS$<_!i`vhb8=&)avq-|2guGj$A^fQg(J zFO1kvd=85*UBs`{ulh}&hGNKuI=rsg9H+>?JRGB`RX3v44CUwxD1vjyT3%o;9#aiX zwu8fYowj*V$@rt{L|zJy#_*)#5PDIFxVQgw(RO=qX5fwAWw%Ri`bJpK7Mq-J2rab$ z$hzLP;C;Do)bzT~%t5!_bzY;Im4@wuH{;Of@}=2fJ%&V}nTmk{lvFN@%7mbn7DECK zY_R2jz8uOe#-@tIW%=3AK*jHIW+NvHxfmY}4o`GEnqyM~js9eUt-+~TeYD{mi9^pN zPxeFB;j|3|`$H6o3#{?4MoO-HzwgjuU`?yU7iYzWCO2`t8nogIG&`^t38q;&u@@_5 z#sp|F%T%O{vwB^bJwF}0-nO`RKJofL@2&JFZO3-w8ts7s6rcl8p;^;2&|o>$X^S`Z zY)?}>+VpJm@D@j<#oSSfU)=lgYAdx&?c&sfN`anG@o2aIFNev_AmwLXbVI*e>SD$0 z;+*u!Zv2=J+UIV!5o|lQgBfnetd@6QW_hR_s+sXQyg$<@W_;3G#KW?W)2w^c{I8io zASAeXOcth?$&=^&SU;RXMa%4E(6@`YGhHvG)@ZY?xrc-ltDGl26hqQCU!k>iXcX#- zJN|6K%ccVRC?f~{UE1AGbtH#7lc_8x1iHa0ZIk#h&ZikAYgBiRsbI|l zS*8!6A;!xy)TJs_44~v1^1CTS=tTWtXheQ@vJuS#_Qjf*m?&OrB#K|6o}8kutH{aG z+GL(Ah>VQX!NVj3&RQz%T;v7SlgZR6YVSVYOD$CTTAa05W)8T&@&QOQxa`A>bJ0TE z+uOb2IKrYU^($z~wQ3N3e0fwA3lBv6A~)O**oTOFo87N ztFm~?bw%ATeVMFS+}!a~OO9nqXnf(RHk8N_^g3@43BQUvPd>O0ThhC$ySw?RU1m4pRc9U(69WRyp4C-C?$3idgM=cmnUT+RH-^a}tf>Ob!Uj!pS#i#ex|VGY zDS0+}-2rbfe4)s#d#6EFsyr%xEJstulXjQ`SPUY9(-7KHa{I(478e$9AFmH2+Uobt zeBRs!X0cYA0YiK8sr?2+cSS_)y!bh|hwj zYT8OQ=Ub>>%y2Eb@Add%sY*d}#FKNahv?7ukJI^5NV{Xn$1&f~F$n10jaZbjcq#nn zsWGIORnNhl0eu6^GqIzYJS+~7M@AyAt-wn!C0gzT&qyXgnn~HaljZYEt|dChi$7vs z7ItRc(qJ0i&)AUubAB5CIcOXX04>^va5vBew!I$N>&-?e_wobW@d*YT_#>H6amk!x zlahrU(y|P3>s(TX+PA$lavJnO8xN3mToIm@D=n+zi5wxuJ4hTqczYmn>&{+;8t$=U zwu@$PXnhA3L!|p1aa=w0Nn$Q`R!5>Zwhu|e!NG;U`N*9(D|s4RV+PppFb~%?SWdbR z;rS;|`61-OE&?{hTdLQmYQ^k*Uv51fta~LZ4V&IK96ZN#Jbv2lFPlx{cVnoIL~pEb z77q*)Au1b#biXm{v{={N#gmHzrZ!`xBzcW?V@1k1oeziEd?B}551tqAIPKQn{}dBD zoIN0!!;JS(VyGIWe9vy?IFuW}^g|8va>whI9}eZ|-R;prO6UP)$3R^e7K|AH5J@W3 zUSK*jQJnP$G;ezW86&U}BKryq-JeLt>Jui3Frk*;xo^9!v%92p60e-gbQ6xFj=&pi z0sGOS&2{F7eu!MczkIppWs(ku>^xo?zgVB(@3MTy$741a+#N|QOz5`q{)3hL<7B>o zapP?5G^WAj^vNxXl#4L6-)MPdncgx_Iy!@wZbCCRXML*pMYq_O=VJbX1p|MxCYU3{ z_0CTFa(vOSKs0X`a{mGDqv=iBG%vNeS<$yX6HD6?kZQi96Aoz#oq5LPszRlHr%u18 z;OaDkMMtht%iZ=l$Mq0OmRp?W{l=XCMN#8qy4%!BBSq~vFR70&_Ipy-^Z)%76Ohq zquSom=>n&RO_)a}?K@tLSHNypg?2^Eia~+bvL4RXT$Kwg@*#xzx9hpJ+(&7UOTGP$ z_j7~Y$C+?ts?ZqXfh2mh&0|0L<=ocR?@sP(7iR)~Qi&wTL|bfc0@Vo*O1pZuX^CJ9@d`%jELmUU+SnvrBGTVnaQ{36;YE z?KBwY_D}^~nu9ak-i!xX)TWN2xyudBBt2W5o3D1qe#)BQ&9)I4O1}vD*Dp==m1?&$ zr@S<%cP@p;TV~$91-wC?l$JVTdF2ZNh=VvvzA;RX*3!en3;42~sHvA}s_ju+zdmC{ zz8TPQGmDJ{FFDt_y};i{RCjz#syaXtfW=HEX>4j5{ANOSjYAg|xjm(r!K+O?)WBdS zLP4Rju=>mPpk=y9*JHT>=Y65W(M%au2Dg2A|FMSCegqC<6aj|?0})^(%?YdS42*+6 zbuQ$3lH`kx5|H}0{&Xcks~j8}94fP!ExbNmRB?H4De{LR2tH3?xt`bi`Llz!K2BX0 zmJrwrtsvk*<>dTbBWGt4H3XG3;pryeaJ5}v#p5I}rxWtabIbSPd9+56&vTsCXsEf0 zp>T=(ZF#f~>~~MnFK<`?|FyF>d*cX6Y_n1#B(M{2xu#r7G%PR zS1Hx%Ce7fkj0MO<#aV7Sbmk_hSx$^_2*3$}&mp11!~1lsmf)>4`l#*k`5z&} zp*_DNyE%{5c&i3>cjwj%5zLefU2NqRWG%J4aXSq1yb;A*io}XV>$uIkn;qo%nWzc( z4WGY;{zf6(=~xQE0Q*OhV;D6GV0%7FS@~11?vEsXv*$RE{dk336ZbOL`@6{&gP1rN zAhonmsrK#UvONp|2`hI*WhQ94)L7s5b>I!1{`Fu|OKF$%Ze|Xljk9t9lZ@!%ttYi=ZX{hVFZis9342*I-Ppk=1lZ zneqAx7ts9_%&CCSBV8mjG)TDN-QG~x#C5XPHBX)#wKbDm0YnofHbWoUaJ;#oNM>Q9 z=$SeXf&sD!LnQrpsA8h?ysr0wC~s9NU@3IM6^}!sQniv8vS#q>4 z9YFYj28+a~W)+It@d8&A8P5Xx;W?mnZ^zbZy|^mR?N}4YsKs378ycI-s$OBxcX25b zglw=Vs6pUyvcfcEZ+-Zn=`#{<6c!W1mc{P|Qf_xA=>sF}6FkGdNABwCQgtY6PBh*< ztOF9xR}kScKg^=zU|6?$jjm3$7zXsk*z3+c<#dAC8m*?sYW>#<-2|)OT$k|b6}kEr zn26L#um0j0657m{n}2V&XriE`T&s1u#+9gAX+j>);Nq&(YnI%@I2fAhv;+k4(!x1~ zsjvLyI6$^vZQ^h}UeWgZ76Y~z=jODYS*kTJxbz(Qmc!oYy@S#hOV!O2_DkJbKDCN< zuEG#}al6=uVr)zS94@8nE9bIB(J0sCLMFNkr=Xxv+9MJ$&d*DE&dKC?E%8TU+Xmk6 z`eyRjnzXu~)`K{_j#z;FO}2#nDH+=_?IqNg+Vxim~2*}r9SuRMqkGw;x$eqMuxZ!x!kC9AE$v$|Y zIn=UyUtxbZTT-$$QzQ?Bg-0uO1HW7pP<#UF3_b zIa!D%k9m;Yj^;U{Al6a81158XY#;n4^dt&s6p`gqzi{rc<99f$ak~Cp7@sOPN2-f0 zndfj)Z1!t+!ILPEMo}l+68`OWZvEhOg9b+i0QB7+8-P%0huY8Yd0cTJn1q;Sa=dGm7f#xMIwm#Uk6^rFJ-EJ%+c$!8%?bu{JL`wz*hdrC z9h3m(hgIb=XPmyMziJ9?$iQ4$1(oKUgL;p1hdOy<;Iv3AhdK_V!S3(6%j;1l00lbx zIEh8vC6{x5{2-S|Jrgo(-v>I@>6%?HoC3YCvI3s|q6c+@?Iq023XRu-N8m8pKo)DQ z)*TLgKz};~)K8piN#~a)ncAT!4;K;=eZX530S`M4Nhv9!=R#(Ji!GzNZ*Is$Je6t? z%k{ZNZ5KXi{XQ*2KWpBA*DPajm!FNk>O@wzk=0sbS^cTVGpqPZimY~{U0a@ikT=k< zSSbY%4y?U!tjnA2uTJJTS#OZvLqz_>?Rq{vs|+2?KA9kR!if=o)Cm3Swo^;1#VtjN zw|lV0<5*NC>&TS4-7B;~N3LXRA3Yaw?&ldy?%jBSM@eSXV2^q@BXWy|_E*SQG>Yuc z-zGwd^lKlW~e66 zs^_4sv_czJd?jL$@|;;B9sq&&jn~kWsBfC73|hJGjZ4Oei%z?eecv2?Vv=E#f1k7j zmKUkd?MeQXUh1h1^n;7&zg;}n4T2(WX6q>UpmWLbWQi>(S5Q~2_#027!#v3GvX?J8R(Q2|T-xx<_mefc zO9~!`?<}wSQ4cKfz`q<81m)rDy4`1n$wOMtNff!hCWp+zIW_l+mlOaHpG>)%gz)3s z)P45G5yvH#&v*TtX^*EH?kph}o#oXXS|x&&=5r$}aCOxZKT9&;G$1pnoA&+M#bkoi z5rJW|b8=q&gO*4vNzqk@+~K0uWOt(4t+R`Vmy;7~XT2Et_j%_(EOPG5|d4L`i5 zqd7GIKhpkWzX|1T)nBLcgO%ebY^bZb|v1)Taa(wu~lp}k2*BKoIxyyIn`{dc~qQ%QEKp}Hy zm&W#jH&?Aob^xtD%dm!~3Ve7t?H9yJVvj@7-rl1sF)y_m7p%nAuz@|e z4By|~5^-sQN0S&3orXVmcN2QWJl`&Y=~U@NfB|g8)zyDDcMroBm@*1vuTyxvwmJLL zC>Ur^SGfVIi|JKApPBIeFPCeL!y@5h$b`!8aIo;;Z5fH?1BbzlU-JU3l)K|A+ujH< zFMk?QE#4pG4wk3z1HbsPn}vOnxV`D9yb3e!6Om8N*IQOSoQLQq4^2#l|EY&Nk|3ropAH$==CDD*lrLj?|&2D;VnM=vZ`QB0~ zSJRqsTwaFMquB_sQm>eE`-(5<$qHSRkS^=k7#MyqLClVuPcPcsunc`K%a(>PkJ5(| z8B3Nrd#=crjBdN%%6*}Ws>)PHlx*fbHr3?z(R_u9 zw4F;g3|To$=gGe=!kL@IpqBhm#x6=*~5!DacTgejg&akvQ)U=k-dQ_Aj)T+_M6U zBkea@-pD5+NDikv8ExjPkz{1x%@Tg^MsDTGcOfs;PTQGQ&gpspZ|ZOBzsBdIZEA+0%g{4Wyf~<<{CYZjihfmDRgRiok-mWrj zpOR~hUkV`kHk{w&?CcsJP&647u8pUdH|}oZExpm@Qt3-_X0!O}O&?PGj2Ydn_9Bu8 zqv~tuF5kRekAtB_RN-@ZaWqjG-3LQMk!0#rJFO~dloJ6*^u=m@4Qu~xJhruZoq4&h zw39DC`$7j|1!7|1(8Tj72h?+BsO7iETiQ2I9+57(%p1Q2S0%qdx=VC6Kq*WW;(UF* zlp5H{ot@LLF zxdpqonR1MuW36?JXk_NS3Tsl4YSGm;F@9eGX_T03w2A7YPA_Oyn)t_$KjgRBe(=`^ zqevy6`~2`y*=fUKafq?YyVR7Y;*GIh`Y!G+#XKcH*vywY$hDR4bqdI(vPbZ6 zI8;{;lLIL~e-ZaXLqtl1XT^X5^%~&u-H~kF@UOt@xNAIS`VzoFb?1XrKdQul|I4iw z@mjkU;?NZOju#Dewl2T-;g!KI!Zl8 z4LaJ1-F27C-~MVL0qZxH15pGXqpy?O10X9nI6_8*==~z9!Kg{yYno`*fLF33ZOB$A z+5Bh%nM`y~!T^$bbH(-_&%$r9`BHMF=N4Cy?_8bSB;rQS1|rEfIV^A!(1IpD<|c+Z zT`w@r!*`gKwZCMH*9Pc)H$%>+ih>1Cx${K?sVet}60F#)NQvAE5|N-qkSt(@Bm{SI z_~uY#^hd@-WWks0q_ddPSjdQ!4@Z0T3E_*-F zUbm(6yks&*BiL`ukc#2>TQ9P0_^X#(4SGV_~`D>WP@1^Gl#k7$FVV_l6a@c}z#+I*-KCTZc>Lzuvt!6hsd-rZ8%3e=F z^S=JrGL378Lw;*$_jMG&dva}@Mm_nEJesOt8^s!Rdsw%V#$m~9F`g0fc)eeScbak+ zp`RLs0(&n!D$D0{hU~mILhb%g-AOHlgeRFFHdKqg(=gh}`ngLo+~*kej?0YqOFLYr zoR!!EahY}}5R-<>c8;}(^;JNjKehAdT^2Uue7OgJIz=5%r)#fvSM2Xdy!xL@dn{xV zv)!|AaUqloJ>s9!wt|Ls@W-e1N!`qZhq1-cGR^{DAP-a2`jhahgDFFt{w1ZH_?WxK z9a;}FOW%7zX&3f_E#_^@mTZPyYlrJ4En#C2crJ3^L)iC0Y&VH{l*Rr72YtY;s{t32 zQk~MNqo9EH&&{hBa}LF1mb7+qDHa=}h`%!>5+QY^@f8a_lbd!-4wN|R0H&lB;w zKG3ETh+^lw$6l1!DVsLhgk4mv9&uGbeTq$dn#)5a1$hdBPp zjv_tVW~Bw&q?5x(L`Nn+t)%co(BCrGsp4Je)C%*YhDOePbw=fyeuVL(;L5Q4=?B@~ zeMCX*TJ_E$1}!E*QeYXhdv%lIrH_I>BC4M2icbb!S6+QDG z`-DgsGfjBdXNdwb#fQ&(}5FeLWCtrsV zkL6juYA3SDX@}P6aEfB7-7=M5)6;XwuUYr>_EjGYdr-6Ia!fv1b(Q$#S0|TnUkZea zPQU@N2Bk*cm>(6cO-%@5D+>9EFzndwTD! z_49T#Koncn!FzP|zDN0n;0#NdB5$i6`~l;Ol8NY#o3p=L2Oa_6sb#iz;!hHJHD$q- zG$93#%gcO!MvpV-_Y9o($LLL1h`WUENfS#zJcq;|lMA(&erdM4&*Rq{4H{8veFqtJ zeg60fTe4Aa)ZJdEAmHuwVKWGAZl=aiq~rd~=RRuRW@M$wR=G`_ukrW-moh(Jxy?js zEQ7{YX#xSfo$FnDuVtV*@L49g>=D-SI9Zo<;4GGL#B#< zu*GZNaLiXAeT2IEBZ|diebE`CKsO$f_jx9J^uBpwnj|sb%Jp7C5byP%To#q{jMrwV zYl@_#BvGGyHj{zmv;l2h+ei-rTELsA9S5JxNRLYRW5Vd-eG2^^b5{R`Gy^;ld?L9| z7$TSOvc)mYci+bT6VvHe$I$r-4RH}9B2nFk4IjUFMh}CshpKt2eBq6a!D!NV9_csU zW|JBE(Pv&SgPnI-EUzX7l=G3TK3<+Lam23pL|it%B)Q-r`Hn8`&SHa6q_QUo)76XI z%S{5G)oA%5P4x;an6miX;7Y#b{^@~*S8kHS;c_-{o-#HYb#^o&-`+V#L)k&UOXsu6 zqcEsG_PI|AZ*Eb;gGwssznu{;$1(d!pNKdr`xe7PJ}nO{MukTXoURH(XD|MsB{IgkxAjd?ObU(?hg5f{j5ow^(gHwb2PVnvc#aCg3Ozjm#u! zwgbZi{k6v(7+tC}HEZW>qZwRn`?@9W=;(}HQ2*VpWmgO7N`y{$S$p z;cj&}^?oBRP`v)!bVzA?cnh`|dne1xRj zWX#AU;W9p>mTtbQUq5=@ z|EhM(W2Zj2o={s00S`lAES$O^S=}J_la&xr3|p@&e3>7nr9Pf!oSK#2tGhC_Z_gPd zrGoR6NKA*x&+YGwjmZQh5>na=Ic;Y?oRJb-3_XiDJx%}Mi=S^kLD`fo;wY0qQ%PPLtrf{b(^sb!K zS=R0mil&ax?zkxVI9=~|=GVD~Z-W-9b*xX$9V}*x6>YG!Tts?k>u|FAriqu0E!KbE z%Ei?IXz9P~2{~dM@5r97#4;N`SgLNGRgw;X>aX(>XrTdK_~$4U%ZEL9&- z<#oFwjv?hYliNG@&_63OuT6$jOI~g}ck89vS`oiYA)k6y(%VQB$XfFfvdJ{$3Odyj znlC9>S>anurbqU{--BGfYe&QF`Eu}DQdP~097o^B*g{W6B_r^RQr2B|w` zY%4cdFwGMF!afa*7Q&^j6R@M+r8`LZd@FDm;ABJ0L7bSc7ZSmMDc%GG22agJ0<@-* zV3)eje|(Z?!p*b?a)+wPuKgvve#a}7z8IakT~NhSgs=T@4?bGjV@%>H>M%I;Ldd16 zc}<_h`vrv^koi4=XVWiD3(Js(8}?IKIW3 z8nnBkR4i5w45A!EF=$0TcRbdlGUYtVr7jovg=+24imZgq=Z_fqvyPW1)*aUlEb7&E z*jB{KA*;EGpRQ*HA`{LzK}pOgnVohnqiG%bt0jTVRG_i3dXDQ(3)0 zW?TWJ|Ha*_{;xh#;Gdya%J6wl=W2;;Ko{J1+6NRxBNEBMo_lAqK) z{@d;N0?I;c#)|CkjHM^iF&zwexqrg_QV0wIw>rT(fV-;p@v}I!qny=S30sSlw`1R9!S_y30x4OZh_I#DwF$XBA!~jy{3(k?3+mK6{>fBH zI3D?#mlu09l?e;}&$nCnoh%pm6y{_(KN#n|u^8w-)myJXrruj~V;m@UM5SM8q`smZ&X>qW&8L3lsKfY;mS^8~_Bq)XLu+oSzU!mSn5?e*Uk3x)Ke4iuqoyMG(<}OT) z@%i$=@Jer7B1M7JYN?gJ-fWy^@{Y!w*}z2 z+Tq#yv5FrS`VqQ{u-|1w?4A#{XFj`28K_8#y0V) zJmc8{XiYZi5-O38#)&(LdAY`qSLWr+d5SMPvE8+%L)@HzjSu?2T@#5lDKD^7b2evYOvO9wrB$2%Rs90{73C>eR*oV|8@D?+;P0$yt4yDR&dyT`VDe! z+FLGTtd39Sr>iYN4Uw5EDX=G<5Cg-L)dnmv6J7V=wo1L%Si+snXr7fa`X=63`o7NP zQ19i4O zGFwj7U&8s6a}G4Vjin2a=?=)8E_Whjgt16XM$E=r| z%g?hsYKS5A9v~dl?GQ62Gipg&M2@%|FI1zTIsMjF0e84bgh)1Xk7HO8%<6sbmnp8) z{%rUopf;SVFo? znI))TvB_ro{dvbVYL;TqaBTsz6e8sA;|I8ii%ShEMgNPfchvl2%D&h*Y`2xdHk zyXTi!(3S~1Ves{fQzH)m0QeC3hlx@Abd;rA@>f__0(AtKz_G|C8FVq6CVM)mhD=f8 zhU@fWr3Ju(n(rh`EWceSNSs6oRT`KMy;kbLLzC~%NXd67KnGr8vWr5ug#;0g4@Y0N@#s~K{c5`LWT=(Soz(=>E1+^-e zmVQy^8MBG@gPX08-QAkzOZi?!2tx&Ek)IxDTMa7mh_=D2mwPd-`{xwGY@h(v>cM}$URA{XUF`4^wzVcn_Ot2OR!U9k6 zsyzJJ(>T$UfK}%AYF(X&!05%mbplT^bzItH zDht}|O@jhiM0{2K8UrD0X1(v7p5d4jgDK*l2GHRBJrBByg`83$FN0N?CIZg`=MgfC z%-I+I;8%|m!oGUHPCuxcx5~#r`Y}A)727aRcGkk<$Ty}{)@cyoAI1v4vo zV3*$7eq}Kp(CmJ`vco=Ey%(Bq70a|IB`#JEPuL!%W&ooDCEbL0r*I}kTn3{`bxzOz z02MPijWqihb?_%~M9lz0==IFJ}5igzOzLx)@ zEe?ob%}S8aZYc0|rpO^a493A10JBd9{W{!PaAif1QE6DSoH<7e@?yI0Qx!e|T99r~ zxR!sP*xmHH;P%d9Pm7hc99?@{N*L;*LB>e#-fwBQGvC>Lm5l{36S3ubIP2j~&*?|? z)aH>DD6Y0Mg-uiDgT1er)T@<083qWaZ;q3hxef5k@2`c)%mCw)Iy07;)?Y%sa`#_8j}N{2xF@9CtTCOENmTG2 zYN)n~TstK+i0#)?dkBp!UW=`=n?)Cpi!h@#Xr%~#AzKtW^kD51^u;JcuK9SmeRxGk zy`FL~Y)|T%v+FckJzeDe!l*uU+#?o_i7M4N#ysH zZ0LV6>kIP|V3vjVrSOd(hiRZAU;aJv?5p`WrfuS{Ic{z#oUrCH(jiECf8BZjvL`h+ zouSl0x8W2wa-`evJyzTWr4T@S3DLaX5U15ivI?!5o`rEDHs@WBQY|nqZOngJ7oeVl z>+)Y8^Q28p&R=1_Uo0BhI56DNZV`E)p?Tc=T_>6^cR7lKZxQ^Df5939+G}TgcKnrt zKe1;CsCf$ij3P7jKZgx)pk9YU?YaI*#KA5m;-BB{SO0S?2N`;%HLgKwNMBnSR8cr{*5G7G2W@2&?`IZh{*QRyK$uo3doUOg%W-{lHtEW0N!px` z!A3_iSp=oI$r>tvX-EsjS;))(-xr&q^nZnEbvwot4-8VIuYv2HOb=qp6!5>zW-f4JO$x6)!8 z36+iKYaA~?*)O7Iz^Vl(-W?GYWvJ%`GTot#wL6?Xl=SIjxt$E)=1XM1+9+0{ls#3h z!RYGUPStF`UbrySPyJ{0ASFYWi>a3a0T%YX)FBWbYX<$_GyCr!WC`OsUNBvo6ro_J zM1FzXL#cJOi|M#;M(8cRTfHB{A|tu>6f#f#5q9B-rE1;P^dG-Ng?GO9>n*i)a+2r5 zo%4KX#JmW9XDt8h%Brc8+S)QMtt=J)onBw21chJE68_&m_9Lv+DvC%1_3upE=5fti zD6{_We=dT2WAj zU)vjpABwm``%A0FfKIoXc*FZxQ6Wc=;6)h!*HyZcYpqrQKwev1oOEpk7YnNZM1;Nx zfRaN9d?Fk_e~LL9+#l>TMMV70oD?A^U-nJ8NYf*gbav)uWmV3OrAUtP%7e!9KMx5# zayC7xuT*GAz~3Zoegnej{adr0eWoy6w)w}?7M0%$d}`$wJX&~jl`2^7?ss8P5j{5q z9Fl^!(y>ra#NV>uxsjbAg|?r+HjR95riZ?FLm{^;Av__UJ9RI3WKS|EF|6+O`rt$H zbblPmn92-g#@tUfg8UQwZ!TcC;6OKqmcPE8w-0%rcX~LT9Ac8c z*1nl4*q%8*M$kej?uh;I%<^mU%oe+l*cKkB*#v3XWnb0Wg6=hg)4}EmZfSLOXDrgZ>qaJjw#rzQs*d2?=G8mniruyS} zv2)YNGV}_EFxKc>(Vr|ypt=IN6y_kDx9WeKpJLmOPGX>c9v5^E z*DGjsn(adwa~3y!jtR@Psv_3BuD8427Aow&nz>@TpD%|nBr{O@R14Cjv7o&gT;M(3 z^N?2ftu`M{SEQcwVJ&ua%yC1-(qe%WFtEbU=;os-QYA3}yv`T?Y50wP&3AhEE1Y4@x00&j( zBI~!iY3=P93Azz+_LsRFY@G+JrpbONN*>PAYpai>%Zh?SA=+-plTazM;etl{ryjAv zT4hFC(;t?X`5_nAt%z1Lg@}VuPZ-+u7JV>vlb@%J{v=S#8(Z4{`GAqeWxF80&fj#o zlU9d-{Hdon`mM}&#Od|4eIA)QD+5iSSFgySpE;kZ{`C9-HA+n+gM^rqfN|(x9q&1}D$gXRrAg=y?YE4{Jn_ z2O-~TegRzpUS57mUS3HlQoRmOm7+SF@s*=$2q_j(q($^GmBjv+V(eeO>Fh@=kV-b> zChzeKUau!&-%dcOYjbhPhmXJ9v-ywOtCpzY;rUXQs7NDOeusT$qOZC9J|M|TF-=J3 z|M1iM5eLikB-M)V-PmO!?Uvs)9JFyVmt!*#wjAw>9ueCz2Utd*>9?n={!kzgiWAb+ ze0T(-T)R5fhBif4dH1;Ao$%?UT>Wlu&x9*%^ReH?+LqF5{vk7{0KK`=;cNi&wW0Z$7KpG za7jp!n&yyoOMwXo6@LhiqnNMI4C*Yvaz9%s>jJFT<3R06V&RyBVust)=jYv%Dq9KQ zfH$GRShCoFRF>|8@5ziweSgHr-&u8-_>fDYkpvWr}d;d`piN&D)n}>65V*I98qQ5I0{;-Q>1Dop>VZ zc0yR%VXF^Z7DdN~?3NJ+l%6$#NSJ5R>2*8Fhh$D<@u_{-0HpDFFweA?NHi0B=t?9) zLD3^F8`I_b5-Ek>DlFlhv;9Tr4v>4y8ZQUVQ%j3 zG{L!=A86q3O&!E`jX06%&5_KMHJv*iDR)|3Q6J34Qk$aMd*6{3FRCBo5!||;n+^Z ze~KDUs4T17UC(LSxnlz zD!9YK75OvDaj5hDHhk2?4v%dHOA`TpBw~fs;XoVMyz239A}_&prb8fhxWp1Au~YOz(dIc(j#y1zludsbpf*(I`@#b_UC6e7dq} zk#N7D2R&YAhu*ZGcd4N!LswBjq!ZX-O);R$6*ZQjFab$b5=Kx;S_p+M@-pGbg7_yD zXt{ngRjIAkPfKI+cP3!>vCBq|3zHBx#t(8=I@x}IBHe7K%?2eFA2Uf%V^E=1ye?Y3 zoA|Voq>2WPqq(Y}$kb}1k?jGfx>d9Q7(K^d@3-o{I~=bV&7Z+!%Z*bGa&%`rZf99F zH8Gtt)WXAq$}cfrL>}J7V`Sa+EUmab96CWok9o#Ha7XRbT(^cok{FA_mWIA4C$bQ8u<4#+`qchj#9HsVO)ishkx_BxGLc^hN1eA|XG z2m@k-W2o2%9&K*NDLO;bnR@#ADs?7Ov!u{cdVge#?iaD|pP`|Hwog5UX|WQk(iApl zlh7h%MwqYOLM-$SP%dT&^cBQ@tJZ;5CErZe1Kze^F|;}SUo@Ple|?|czGy# z)NS-W)ltY4#F(HQ{<1oSi4uMvBBdIpJp6saORL*osDFUgWWHQunAx08<>-vA1nSkp z5bz*6UZ`Xvu_Y<~mR=~Cb)vBM==Jo$oQ-Vx#FaCTD>sbmL!I4zFJmq*O`gt#~a zU0vI`YBfB6n4Slx-TR9UXKlqtr<+5zc?74eXZl&qdr}TjUp3S@7pOM?C;vB8)T`Hi z6i&R{P<~s6_^*8ngxW~9`_M$x0TX}eE?Kgv8+OdjLi-#mpx|!BgZn@CyN;DSzUDx! zqP*^P{$`!0-U>IWo`i8Zbdf?4CgJ}8JMhiP)p>AoXRsorjjceshsuxtmY`NFwmMs- zT+d4(gPo)bY!%JdV3DseiW?Sg{>ERxlwX<`U0Y{0q83K3;vXL(3d-c5l}ln~3S=RL zqH{q_(QLgD9kUhipd`jn`tIk5g(_XsyGrm>y}1iUoRf#n`X@a4h@!VjQD|2-w-!(m zLpDI0>l>xlb{%$sI5XntAmC?q`}V67Jp*`%5nP?X@-zN=Xt8 zQWMkB)X+gwi{Uh}@Xa0xVQ9pXu-fdR(dqvFEd2H?-z3asOpSMDAX_}IT0ZAetuR*CH9W2HLqk3wq23Lvz6d!yi_f&wZ%_0c>G;P23oP>O;F z$rR(<_;{7Du&5vKGP??c%>=PHL1XD0eX{-Ku{A90a_M94aa7O@Mi46>5IOzb?6Mvw zjnm=F{zOhluhcn!q7h0Mz(9>IHsqOw2V>XBh=?iIqrQ_!yfOZAu`JnTTnUaM{w;p< zqwQIIE_9b3kUM*v6>D5GVc{;-iJK!{izQV&5*+HGWLv67{6IoX;>xS#m5olCo&@Qc zl5aJy8Hi}H%H~mcbtn5NXE|5bziTz72GX|*JxZ1~tx7xGCzO({GlBhEon9KakFS}o)n8~e!Y{Qvh8Mu`W z^|h4AA!DY5AVIK!KwyUIzp7*plY}VfNP{8o9D?jNU#Q`Vlt-i3TYL{Sw3qdmAAX*% z_+KoYQ(zul+qT=rwr$%s+SqKI#={&Ht|9M`E2w6e24yhnOZ1tUnRw-@Wk!Gj!GAm7$_ztr20M)Le!I(Q z@lM~0_A*d`nES7~;NeK!yv#e&<#`}tqKrq6Ha^r9gRiFbU*eXLB;0~*`HvloNlz7Q zgqsg^-cbSkTLBizbxW9aniP_#h1W;DZuBd)`7L)7V#)r0VQS2Q*8exTsNtxDq=Dqo z2JJ$Nj$0aV6`8{sZ{`;Z8J@EdY5yqqyf*3Q9VwVRm28rV= zIT#WN_IcyGhZ>&Ze`GVbyj326Cx-O>j`HAx<+dG;?Pv-q0v{Xs_#fNg0cZfPAJ&a^m$llMpR56Tn# zXu6>L**7*e7W$rBp0aP9lm-M4(8IID=zx|PkN^Q>ga%po4O^TAX7VVEcuJHE@VXON zj3rgd|Ciz_$VbA6PQkT!-dn1*0c9s(OQSHSq`|!7PpzTUX|YxF({f9%5dYZk!}^-# z&Mg&-e{vgD3R<=t`JTWt%BtU(QL@a^0-iDceKt)1XqIsOU*@bJ6A9Co&Stg)0HL%d zp~>mM#X4YTo_|Rb>p0+Y*;XF3&v)=%xovd$@Zl2>T!xD@uh-o(>34aLWbrh-yz674 z2Yq~eTwPx)ktST_?DaV+lLH%|Mv)cp*^#lbq5`%UO3hmm9d7_?4BJIauiaQ$KAsbb zjus4L5ktTUI4?FJi13yv+P(;U4yoVz@3Dv6j{i|$ybFsg|8=pN)0Wc?`-u-^g~gbm zzV%Z(b~$`*w&>J~JxO%;$mr;WS!xd+?0gj+OgNkw6f&6iKY^`B!27t*j2^hO+Y6i( zdl|u^&QguORBs?ef}oG5%1L6vPGG#9#;=LAJWwTYRgFNPLTR+#>Tu$eNGaDy>@5LU zIpk{B|C%f>uFkt14)Fn_)2M!%;f)TA#$bkW`A&l>z8n%JRk|pRrGW)No3(x>Ap0V>y~i+IP$9v+@_<;$BtDPNoMy>G6E%^xN4oXyt|gM{!)AFe9_f*ViZ zud7%)=enJaYz8;V?CflD)1xts`g1y2mR_s<*W1JCCA38=cA76XzW*R@x;xz`Z6BuX z>evvlv(~Tg^HakZrhi1XAWMpi8=TCSo!Ar|tZX%zfG|TVie{z@zZYCR9`;}YIixDd z*9oLgiV$z;lf&;Ttdk7D+T zg(0SQ;pjj=|9ZMRo-3Ci0}g?srJ7vaFH*q!#5uqeb(;6b5zLTx?NOiBq!&aZo9?!d zVC7!b?iERa>Mh|X9r^;bLWGp?=fNf<%^hr$k%E&ZlVHcWvHSqWz{hoWD8|f^WIm8N zx!!UAM>GMSdZ$mZ@$Y$>nGWA8B~vSm?9SdGC^{*{+qc=ktA(8wkZ({)Nr|WLay{@; zn)u$Kh$x})=BvVP52woFSP>hsjXDx{;7OPqIy}g0%m{)}l6vqPic+c4S z;v%HkbdH#bDmo-0ZaBb_GVgQ#Rz!e&BY55BED#a3c0u@9cv~J~>MJ{%0 z>`T?@L%}u`&hBsxFZwMLSkZ|=Nf161FkZJ)6a`9|cacf5F%dGxZ8eyV@d9Xdw@5M1sm_iAIM7qWG^!5Q!Bn=@Umf6V+3lmj$5yuRO&2u_!8k71jMC%x`wEm;R6XD@ z7^7YAI1R3aWRyC@VJMK>VqIUquXNkx;tm8dd2XI8ql~N555LG|Ug?3lY7^>h)>~(j z*gPhtQubxUd=F&cf%NTWdF@LG>+&COPMxGlOE-|RU?zcMdxRB3AM!)}SGkyd(&Or8 z_0se;M2~-3WG(aOO2F!`Lg990E{UhL?$@>5gFn3-M! zX)#p-S=reJWcmNHru@zcqM4G3e%-fDhtCk}{&keYcz38n@#>HF+T8&MXqe??&Cxnk zhmSK0adCmR&V>$q=Mi(-ASZd3qnWQu)gHa-dw*q8&ddd0usq6b_P;J+yq$(O#USJd zz1R9a5f4br9kWk~49$A4e15<6o+T-$L9`ys~33zK`D{v23A4#R6%fiQrx;2^s zb}Ma zdE0D}1js%ma|M0oJ%QEq^xt0Q1lTG3L@`&KAzW}AX-Elte>=0OtnH`6gt8klR$K^! z7PZ174+agHi3x>BMR;OAd-#cmO7)DcK-^OKMGfM`ZEwU0O0B&lCqX;8I`^~?bw?R2 z?OqUb#Gtu1c@Xb(_GAjCKdY=0QGu^!y z0oH{p6sPZ>mn$z4#uZi|o~&oju!s_3`6$u7Jy&PFp$85glKxOR%bx}0zUAK!aUw*X zj6!lJr%^v|V(!XRN-i>ktS4G?XR4&Q2Er3D(9mL*TQxCGc+9W1N>GD>?e-7fpP^_Y zsDU7qUZ(MEZE^uaC=Smp7n>a2?-x~U?F7DbD08;F_4Ur+f*Y_u*I$jI*#xC=gXIs3 z4~&Rj4|R_+#I-Z>_WHLDBd0;4STN!^{?WY_<)(6_rDxTXKdop1;=khd#NwGiW{ltJ1U@kbBD(Dk zc-`+!gdD3rp+-$0DNw*}?Okrw)gw|uhRB3ssPj%$JJ?0Fzbd7@%QsyUheV2OxUFqS z6M3XUH&Fk29@RPv^K#iwTFYAuln$#yXr;K(&sszioC`lDCsdH|Da zRkvXzAFaPuR-VmpMO2tlJ|}Q;t$8c+QgSPd$Zy~;!YAM@pj6zX0Q3#C=6yf3pIXiy zb)w&&sm@kgkbbtiaqYLl=~SrQg(uA_57c+a8lMtF*d6~XTHpoNmiM;GkpbLtdWVI~ zcd2X&T-kRHW4-CO^$Uv*0YqEvzuq1L;CjPqc7DqdBXHMkp{F8s)XKGA7_)g|;;%F@ z{&~EQ@rYA!=c$g8{)qPY$sI}5O%Mx+#{)=m;d_&CZjounQu5=QgN>|#DD3Ix))4#V z+Q6IZ7pnKSeuHt(Rq#iz=Lw~(&Vw*l0ndE%E_!GL7zuMt@GR9! ziDAq}PpP!Q?@={fsW!I#E348K9S0Hozucc2oFfvQk;Qn0oeK(8O4Um&B0_;3WLt31;L&$TZ&!JG5WQaqGEi;7|AjCbXuK~&NC0Y=AJwH^lx})kzv&~w$Z#_ z8(TrEy&nlVki#V&1C5iTS2>&<8JbL}e0g>>m9h@!8j8k^!A`};ewsyg_G#dWy?8-_ zQ@0H)2OEo-CBP@!-6w#G! ztQW&SB;};UyoQN3&$kC}wgUxPQ_$vXEVTe$P~Uz{{4CU_u+!@d#Lm1sdbjoK__SqO zmUuN-Jc9^taF7h5oQxMR=5;q24U5g@&HvH%yY|P8qkQ6xxk#F%QvRqYcX~+D4~3xG zb#){Dto4EA<&urt8EhDYxPkUuIVI7n5(mHS0|ucoi2X1ZhYq?ga`NXY#+zZ;>)h)# zXAkqE*<&_B>goQ!j;*HwKk=pguYxT%a!4&1r&DgeGs8M4Lfr?J9%#vqY#2ocPD~G#( z1tN@USWQL)c0z8pmJ`>J*`EeL6 z*6!LvH-NkG*F~%@&_k(2#A&`#5x%|oA|N?CyDl!bi3}3{8Qvy?L%e}Ad$v*!B8$fr z{DTCA6uU|l$HFb&*jTk&j7vj`28p2cwY~1jR6EfXka)#aoBu24z+EZ_zmz#dp?R)Y zv^)Yv3j(VkUA^{qNjT}Ex-Xiq??!ry zGu)5G&oAB6dIh4=s?gLr-x5_ZX(G$B_jei=ShC^xYu5X=@)DIw`VW^~jGB`SGeCcRbkrUFSU)QU{MN>n>_Imufk6`O9UG`&$Q-b`1ZC zwN;4U)X-MAStT~A!CDbFJs(zqBvnk}J?Ju8RMjLuoED7oJXTydNu-HWkkLA#!Np!= zg|*23zE5i%u{EXvi(jeH{kyx#SYZD1?bcp|F?7dy(0I!?r7ls36+eFOpl{m-WW>*-$j@3uV)zvjiHAf$#5MVIKz~AiCl|n|rq)y|9OPVGQYJcns zv#_u@)EaG@>X?c$O!dc`hh(idk&rOrCa{niZRidbJ6>q$h=)LJUxNJplc!$IN)hdX z2*S3{1{VFr7i!|G{=h_Bmn*o?^VwMD@U8mD1u63XT;p@^OCRrR~4OC&-D(wrL$HL3FP+ zC#v~m4M?EuO677Bj_$XoTYE7@G@2pDCSUTIpyP&@2{f?I);-`6coBz@60tJXE!o}j zwswWt{sSJXf=44nZ-M&8tT4?~4i|bJ{&J{jQqUwhM$oS`Ns%<5@RBSA7-?Pc?DR+- zPUk9Mj1NX(sh#plb(#5XSLw7Ey**to*BSr6SEC69d7t9P~-2z}W;3MgpZ zCkp*%8W}}Uo7!FE@cxe{i{AqqWzm-UXQTg#mz|9%7@m&V-FrOg8JRb=(b;%SSAQJH zC$x1WoX>RY4~x1ZVI$4;b=}Ppq$_%rJ{8%5qsh+&S+q#oX5^T_4OiK1@Vx%II_E-M$jnc!J zKt9kulg_Sahn>OfAKzBrBY)Ja0~ea_|5&N%|8Xm)bYc@&O$izQXzwQ*>+=C6Qhfi| zi_Nz7$U$B$UoM5t+W7l*(@egn$52Er#BYyf-{~JejZ$%H??sTAQho22$``YWa2BOo z_}xMjg`o%^mJtV>>D`^e(5p{SEsgo=y-u*;;NEtI1`#5Q!dfQ&FA?(5Ivsv0b4G;3kvqXYVK|oW z^nuKJ;6L;3IMbd(@yhob#9sf!rmfGbmAinE?)bOJ@EExpYa~tm3aUA6+p^Ij!jVw| zpRhO&g&aIrs__^2u)KuSU=g4_`PX;|;YbI8SUMbsl zQyY2H3yS)b=rY=mXG@$9a`gX3x+D7S-JXvdaWE1~DxohVfzON+&~8Lt8X@});j$yaOG_+MZtIZi>aC}Lum zyF$N}5E2QUv-;VnS}PN7SIjoG1JJDR%rNJsCjN54bGvo^T(M`!(Uon;GFZ4(^YL@b z#hplS(-~R*A{_;rk8ZM;drWrSCmY_%i}0We7E!*7FN2Mo>DqvUpIob#=AsP*)-K# zRwZwXA4zNYxcO;m6Q$Mjj+v69D$;iDpun<)(JfX1Y^;8hD3H9g-{ets%7*XJoG8TS3oAnX3!^cJM+E(jecDej{2Kh-o? zF0wf_7N4@l>F(!hzsqD#+_v4mW91@;dg=czJH^B}iW}@)8?mFsNIQ#9#ycc2(JxJ% ztPH`snr&f2L7_JqPjF}`#wdf)d#jaBqSZ&grSgrwDS}N?=f@R5+CKgIdOYhOyQ;v% z^YrGB7hXs`9!~)M8;IYVv?f$K7^}vJ#j957ca&6bht@APnP-h;iNfB0?(v37 z@q^gw;S8`nKG^S1#m%Bh^yyZH^By|VxZZ~m8?;&|hd@F?np^eqnCA}`x`-4$=YJ^8 z=*MKKUk&&O{60DmwVOv-ky<0`(8$&dt1W{-M0U^n6S}y8i3BP=uVbfS!h(vM<^95{ zj{6|5Xsw#CFW;z; zKLY!GA(h*g0Xex`HRZOhw{o1(eU~wsr~Vd%e>tZ#zsbpL>Q;Q^=v7OxpEab z{Eli>CKp7*BH?hkAC|#&Qi5i!uh0ol|0*@m&+2x^fozw>7H|cxH?!}r*Fw7iS6oNLgGHNwm3i3=5$!=_v!1x z^871mXv>rdUM@cY5D5f!W@6X6{v2?`Mu?r2V&2-zD1z>E0c(0i#3V67T9Tcz|B(wJ zg*7Pw=@Bo(X;BUyrxpA>F8KJ#ft2cMi+1fscRTV~ z-ypfg!DPai2BN+yRiAf2v$s6&CYAq|An0bBJheEumDy6rPZv*PC!&T~tfHC?`U}&` z)e&DNENGl7{9UNcGi1!m6lA;1(h;{CJ43n{*SY zCOPEo5wpK#)Pe4{)ttq4cvgJR{nwkNBbG2Ar~+=i;n#k#A(K-rzDN`&be=dmjRouk*L=t6iX*Yg_Gw`Yn(TxY)^e|N@(=>SuSQ|2p2 zr;O)qU-+%vXPjvKV=e5V%90qYq}3gs`)H)g$9vZzBoG2F-+VT|m?J|2W>E`J>8j@v zQ9!rlz~zcX@<#I5o>FlJ`FTvIoqL#@m%fZ+!tjn)QrT^Gc$jUw$>a56iZ&p7&tIpf zCi@0_gGlmUFjs~{*Z;3`^IzMThKZ=le0$vWr5N8kg2|Q;{Px>UNz;-= z0eCe|hb{hp?Y)x%Y?cdUQTR9;^Xq|}53%+RE}|*;&x+l81l%iY!U%6hHh>h9wr^ua z{cQu8MuQX?x<$5ardWb3_$i9P*e;bK!hE$)^33e-UHVL=ix+#D3dBRc=Pp{6V!5?eVQM`#RV!ByhtIDhMm2no!OtI; z)<{J9LM9VANOsLuU!kbD(3#XVfBwmOzO8tdl$6Yqj;J5Wq@z<TlzcH>N#QeDdoS=>^o^*$0fZ*F2$b+X4bUFC=+Ul3nw^emomd zFDl@LxfM@|?K29fR%n%8%Dt(Uz{9Fn1R<$2hCR0Fq^-4sRTJ^~pWWC?g@yK9o?x)Q zKgl!_vM4u^>Irybce|SVqsC}Q`mfR(O~53G_=aN{>orN~JHMO$_0}kHhDxDh}>BZ2I_f%bK&rdL3RvxCJ5^;hR3r?$@>&*qaMdVc?aLz2$xNw3&bnT2U1 z!ADS%AGhFC8sXpxr)UdK)ZkI%A2hM{yE)(G$6;Cby$PC~8Nx0Nfm!Dn=0*Y@?RU;o z$#*0j%8&5qXSlwp6^Isj0%Bp3;B_X;T=$TmPw-YM&LG|xJC|np6y>ihh@A}89}XEg3`s~OQ3(o7Zk@u6p~#)_wjy! z_LTD|(J&oaaM{;HDvUa#3BFn@fHiCPERR81mSaf+i4c)pz%bTy98O$gRo;!S(`rP9 z(CTTxltnSUFQAk3RFCH1=>gKC(EHJ1U4qM{@p(m2O|TkAkV!8|m*t)DeK+|7Sg#5N3*ipj4K8dCleraR zEQBa?Y zcdR-dX{^KrvDsTclb&I+Xg+p|Jb6#!c)L8oZldN9;-FRzypA?i)H)4`s~)f8=@YaO zn+irgrds#xi@nyFo|t2ZW@bG5%u;#_TI#K$28sI;#fsye5e3G+OYXiU;}v7zBE$>% z`R*6aps>TWh4u35nhUkoDj$fx%27qYNpbHPeb_c%9xV1y1q>UlP=hyN&ztMfACxTU zy?vq5xyC6xk%UgS&Rt}Ge>NZw*va5uyC_OSK5ASSEWb~u1 zrXI;RHtB@biu)#YXA&D6>~3sTPOn=dIPOcRUd(;1RbVPKG5#(gCF8p)uazdBXCdg@ z5w32m*`Z{9!)=|gA71R>P*3zcA(x(es>VYRhZ)2ax}=9(tfqPhx>Bsf|6utbTqgL8 zc_tF=sG&3KeO&jzs2;(5nX^jm?;VA(i^Q&Ta}gEI!hls}iun63B;xK*G-bC!Eh-(I z0HyN})$nj&Mum#I#7zUKm;&CIe(?{_4*C0--mdf?5w4oP&l8E(!x5MovM^-VH66JC z2iJbfg)z-Po&@V%-gsDUw!$l1ynjTaGRCeHgE?Mzq?T9swr;wzYgu6W(yo}*nt%0o zBNrG%RZ2MK9qCl!|A_H6 z9WS_mBEk{lF)dqp729J}!#r%MPDR^Qm+a4|kd<)$v>{oqHxX_&l1N}|$Aew6f(AvI zIh^jHaG+W(+xTT|G)hiY5wc>@{yI~zU`%>5joQpBx{aUeCx{92g`j$L-r}#if4P@> zWtkhGjwIG?P_1_IY{Vo1%F@-|uS3+D1&I8Qm#oc*+BxR5J9}+bN3U&JP_Jm3(Ud8_ z4EFa-;_z}d&DZzwRPrT_;qiRd6}cL)*_jmj6VHN9El#m7QZo9kfw3$h;trV9tG=6f zAxNKa!|@0-4c_$Q#4Dhuccmeiv)rjHewId>UvU$q+juKa@D}-8;bi$v!&7rQ$puyHfIK86lIs)Q1}wUm=x5dn zpKH&d&|7X!%UUA8X*$}Zof;&souvo6t(F$;(aQ(Qz-m2FReR)o(SC{=i;^sRU!fuk7PG0soG#)^vefQ_`b3Lw4D$hY4ZqFyYeSbbxLzySF|v0xSm zQIW%J7fJ8KC|Xmv5#o;&&+WAxU7zaPOQ#Zk`aa@Na@9mK=Mpy|yh0U^XMjoIv5O?U z$n8JLDYnWRvlDQBLlAmdZ6)IHM1=VDCSa77&ulkHJMy!OHkRQtbAx6-YpGwo+Gh4e zPPM{9nP_Uv-E=|llCFkc7Y_>!0t$-@y?R0VClVus)x1QINdN53X|CbnUilP5)Ae<@ zR;&9Mn!Wr!OI2Ky6feDY2Tu1#+h)?ASz!difmuEtRC7n*&Fqb(GQ|NF8l(V4H`Zpv z8$yU>J3jc94JSc#%c7g~Zp9r&+;S+4h35DyYb$DZTOszT)XM-=a zjdnmntn$^50TgRW=k|rsk+g%m=G#tM1Ja%w+Q$&Yv`(@3Xm7$W`LkD`Aem85iBeQZDDf>!`8>| zAklcZ$D)$78FM|t$_MI2?h??gcI&4`k>^4oLnEq;F;Di|s0)RDix z&AraEG%-P8?dH+vB}8Ivr<8bOm`rFupANqv8Fsap5HTb}Gi?6Bsg5FE{}{*Kt5F6^ zhs6}d?^hW-z8*RXt>U`zG1}hKzz;3>oQx%&*>|n_F+!0bzz^MvwbXL#{~a`JAwP;yXmSBYJkg z+k)jz@gW9&ij!;d=`qRD`&iCTO|~1W1tb=`BFk2veO68dTrdq9xbcCmER=w4=zAD< z^K?B-0ig_oGdAZ3ayp2{-H1P(xYGQ^ zGA0XDn z43?|4ZIP!a}_dk z$`FRQPxx18KHtpX=r$+Aqh=NR@v~SNFC+ukrId1ts@>Aj($8LD6t5k-sO7c}Goph# z^B8a*+m90$az9RBkptm69d@mESfLs+iaYY$W@$_ZM#c8Qwb+Py2YKJyn?&n|a7c6Z ztkb40$2s_==541P(~i|?BhAy}X`EuIs~B*?$J}NvmN73VH0KD1dbcHwWc<`SHP(FL&o%sRnKi!|jUNSeBx?o2#%i$> zo!Tk7`uG=PKbawSUtwZ(fb}e(*r8|aoL+~_*}SOyi)i9#2H7_UE552!R{=^2WgJQ= z&qw1fi;c+eV^)*Bl`6JcGx`mKcC;-;WQKvs{bH?1lG5hAAA4a*g+Pyec|6C}XvD4C zxNk4SKeKcRoteK3@xE5v@bv}2u!{<5X2Ww_ckV;g$ir@nj~Xhy)29w+qE`~gozLiR zwGQ^pjM{S2T1XDwDy3FVM%s$lGa7p;n9spWa<}2|#}9Q1zE9SETmdu{6H>10YQ&PS zktY!m(LLsaslt^_sHs+~eO^XsT6KUJuUnGkk`{k(yL@X?OfodOk|`-i?2bv)G3uaLsnLU~=s*SQ zoS-N&$JDCmku6~$b0X8J`%S&l*aj;h2TK*`W-m{*!w5Tw*DO5yH>UUb-%ozlNaU!% zDEx|#KTOf@Tc{?F^>MNgPI=Pr>ZFc+rV+x<;@GVFqj9V6f+Bet2!*LWrd@C0*M^HTCQsLL1=_rJNU*hU)P@ zC>Eg97WVGz)i1Jg^OnoiNhi=GG7tMhu5y$#=PwkmdzS{%c-T~(FExPm)e{dR> z$x@!_Z!@sioJHFRUSU}WlF&@7*exLBWv8*IEj-BAudeKORS|D0PbHWv3=NG@Pf@x? z+_m3}bfD+7;qqXb|FWFT-Yl`jjd>`Xru+fVs<3dnKF0Yqn!uR8g!*99(6Vf%T6=A$ z+^Ru9xS7OLbUD6SrWTQn0CvFB7N+C;j=`P zo@?tm!|Wp=`(@lWFX8z6raoOO&s0gC~Yu#NQA_CoDwNeno|@2-5`% zy&OziE6kv`ajd{B^*|rw20#z~k0YZ|T-%NWNMbbxFcu0WsNMVC{Ov#6Ws;!TZnx-D zYB6HI#q`C9Qj#h*V~M1p$Ci$|pa;TFdL!TIQ&abYU0}rAMY-dr(3N46yFE-h29KGo z=)q90#Co(LJdO%_Db?q^{?ha1j4I#T;w9wzF;@9NH{41F8Sz#$v&-XG53{`W9Xxm2}dg^$BAdNPSMd zmbY5xNn7Iy>wiRdYC%DM*o{C$^>vLbJTZ}i8_Sjb_*N6VBZU9A7$_;T^CXD*Qe)t+(9$ zs@v^?S1S{tsKoP~lV&)AmXmMrjnL})9k6Nfzh6VFVAcY3T(!wpfW)5LaSs{*)MEY+ z@8Yq;N;3XRF#oW6Ug77Z!E&uLXo0w}`uupe{DXTi_di@jB412$&U~cw;C|aQG+Nz4 z02~S_2Miyl|CRnnveAq`eL8>q6k@A0n=p*N7nBD2R662er#G_Qtir=o-NeCh&gEOy zo}qhjy;jeAnD|yjjpy#G!rxMIK)zoWJiQ5c4J20;_w2ecqI;fQ4i63vN=(w3V}@1! z!a*GS8(I~4MmF^kUdhx3c+=biypu3+a|;i+a@yooVbM1$doqMh)Gd;U>RWI_Kx9_> z_s{~sTj2Vzf|A}NqH+f={eGU`j-v*5`Sy;eH)lE!+-SSv8v5t1eaV zCVIGh=sR0rn`QT7kK}lRH-tG`PB)mCHm+Cuy*5Y@>6kPfTosY^5}2lgJD48YX*0G# ztUSf<;k327nSN3M_>RbM+HFSPpDuyORwovyb7jV9lOLm>+xN{xQOsR=Cr_89>OAjH z{(dG?PX1nmASnrMb3M<%t&N#(kW2(nBq=5)CXUPVx!507Vv-!w1p8nSAY(E?zC%K1 zXP=2dt()=uzcorop!*_rCV!h(`LB5>(~OWX1o|j8nW>c_W~F2=-D+@*5^x1>iKN&9 zwt{`%cH>E`es#RN8hc;K%Os7QGtzQn+7cemmdnE|-wwyg30I^nBv5pM{WHB1E?A|Q zE!2{8wqwfYjwE1Gf#&0L&+dl(>@neu5(E!@pG}zQ_&cMu-s#1KPrx&F`;+GlXZv&aobMH470u^u9nc=u2 zd1JrfVxamrthpIn26opOU*KyU2->fV?#DfIH(9WKvvc1-`Q2EljopR4Xe~hAlwNwB z6xiP_8D{v@I%vM|fG~k4gzSqIXS5obSUZ-Dr=y|J6P#z4ZXMUw?}q@x*HyWOlIDnluX;Gf&0LomrB`0 z(mil`uB9sO+CVqXQ0pIfV35GKRjUedMU)JB-D4@u7^a})M3jot|4lTrne_Q0Q^Ok8+lQDHXpaaQXbpMTES*3gI))Afn<#Gw#35&jdv$| zSt#JuUeP6CySjg(&@)1Gb3#6yG2?EmczVa^u9e-$yj>TuvQ?UP`rs}q^1($Q>v zfHry;VDY<^!1$rft$~=u?}~ruElwjDymgdpbv2SA;=3unPfs;KRX@xwD*zwEK(pP& z?eM{yFTvhNNij~tm6G-7F(+Msgcc0goD2e#;=CCw#AL-d;JcxAO6+Hbh~HmE0*l31 z$$Pz2sf7kCXsP~CgxOXHV|JTWRR&rb>SSgWUh<{ z`1&a0hlWG0w+770l^ZRLFeQxi3Mq&8k7MmL7^QE!>?CF2>q8w15moCwN(9>xC#TX- zglt_>7}4?4e^Z)eu_X1KTE5)hjjP`u&y&V<>mh?HN1sH}SFqzq_Y2L^O5jMuJCjKX ztdz*lNcuV2EQ=Pf{?4zRFRpJK!W!c_A=<$FnqtxM4_JS|KIEG>}X#WA&pda`Ea@DH}K!%Ch z+g=un@KZ(~H4s$!W0xYDj_kO#4S^WO68o3+1xls-Y|u+$Xj`eUQB*F{$QVjIM8ep+;%}?#o<9wTX+&I1Z!qV*gW(e6vI@GTa#qw*M+~G8 z#7WibpVHqx!*vUBfpb%keoWdynb$bAhNIN@%g=E9Mx88RMA~%0M_e4IW+H;N=Nl|p3(rhaMvZoYsJ*kQG5m3Lm2I%T7fCMZr8lgvhPVEZ- z8@D(4ms(fr^8MDCSvQeP-f0Uv_GSW<0QVYFZyGNe*WIl}U5~*;OZ&s;!1Z8eT8d#} z_-#FQ%}MA@7Mtfz~Geq&!Pb}&aqWXWCzHAc$&5C6#Xv`Mp3AN!kv zP+2Da1pzaxygIip8DSM>@Rq+DzAkrSqV_9#t!>KRL?&Vzn{&A+2U`^v`f3G{`EdeBkADa(Edm!5tS$IjMS(cS5XDN1lAeB2yAw8Z02@-Gkm?ek;a|pgdzhL?|MtT zf2NbPqzRHsjXM~-B6IQ+fEh)TJU^18{LjFeu8gnlg(e_=Qm#noUVNU00$n|%$_=Nn zQbO+Jg6R)^{c%g~<1PjWVx4>s zkV6iQyqCz=(W_&q-j_XKDgT(i!EZwdu>q#H5_$XtC?t|91wIs1)OYT=U~rVbsr4g4YTrN+zHP2bNY~ zfZa|E!O21wn(1gdAD1pZ!GKwkW1)X`z38`Cg4HxDrJ2qRTsYZU6IerW)ajaW z?G9|F;Sm`bei$b5Q#CZ}FjM}0cbvuIKEOZm;)4)>!6O$x$!vu3(-K`potjdUur-Cbg~a~ z7(bCjlQ=dG8sit?KmGFY>i~2v@#z~E)K-3=&qz;3)$iu$sC+AbUDLLX-t_r)i}dkw z%Ct{jtM}`a9D?Gu?$Q{rSDz_Up=v^kp)7L$2~I*J)eKvYndV3gt6znnQ59&waZkM* zCCT|#+yxh^?xwsz0UV?UvB>u9K-(zDMkvvLw|?E4Za|QC7IR6 z*wkaqb{=#O9s2f`rOJZ^Ubh~l2$u`B&g6&tpfoLX)n2ce#6p1- z(t=&Yqur04i-X|VZoA$T8?&#ejo0`k+!2l{PA3uFl7Fmc>9L=8&G%)U?2%y+9tcd} zqS`M1tb8{EF(V$=Oi z#{pI=5)A9Hd1c*iH{({X-$W8`Iv4}v=Y9kaVl$f{=(D}G|`g=F!?`sa-tm>=)46GJY zC*ItXW?)fFi;R;A#lbQf$~BC+%bcT&yFQU0Mj2u#YNl4y-r$Q70_R9B7vHZBcGNX_ zD8LiD*is}d5`a=5fIm}vd8D`1PAWwDY`Y&G4tum}gt>OS)UFYbmkSLFNR}jT?~kAb z=&m0cHCksJ zw)&2#ek9YHu8u|(=y&`6xKadHg72_J6ohz@BJv3a3XK(56zD$iRgJ-h5A-g^O@ z@M)I#|CLGJ(gTlqW)?Hek6$vUO1yPdhb%>l!J9qw_f46xJzPoV7TL)3ucz$9MG&lv zIIonIGvl_w`mnJ9X0Uz)F+Phg<&T5!H3K^N^E!=C4oV_V_buHse>~XmL2X}41Ui*E zbTORImoZZ6mPX`jc*q+8j=(W2>~j0?~{= zj_Ft|0?K82EbM-QCBqia*5MfIUmgF2y5Q`A`4Q5veJ<^_IYXg{>uL#zm` z=&tHYy-r*r@2ddVT)jFYh)u6c0qe~U9FXrT!=~S-kN3?e)kz427zK^@a-iqcmWXT? zx6*sgcQL;?Ty!sfs8z7$_#7cWy%^2E-p`Kv^QDe&l-)O@>kEz( zrue(3%eK=sv+vvgqvyF0;cae`-Y z{pNYAzF)9avorV1>C=7nH65(XV1+A*m=A`mN^a_xbg`N>^Si6}xPwOC)yd1ds4*#HM;Tey|%k(Td_WMoiy~a=v8d z``AE7fvV5*2&fTVg14pd2zH+p-NAov!`;~bHWdl`-~&x}m3|YFk&zKTw@rep zA%8wv2dnc3^01gpP%~NsTt|l@VWKc4Q@Eol#xF)B6$|M6 z8`OJ2)CrFR$U8Rt321QK9ZKx(X|}{6|50Q_|HA!#AjX`FJgyf;*>>PlmYP`_pvMec zmcTY)D;;9v#MCpa0^2)<;$ud?E{E-9#bP2xSqN9G?MJF<*#z-yo!z0qN52xi%*om; z|Hi9Y-0yB+Wreb2)C?)|fW{i)7|&qA<~(2u0%|3x7&+m8a#{xGP#~GAaX5cI`Vlg_ z{@}Jn97A@`#d{_U%k+m#ec8Ge8yI(j1{`1Iy-}Og_>>i$A3x*tBM;E^=0ODImYfbWRa)4y20E!BdAGdek! z0d|Fn0Jj$5cwF93S1_+MkHGaz35qaTM9&e@QXi5fR+%O(7^x(&r=7AA>R{Bd2Lx{h{1`5~u7 zkTJUt-UG4%u1%iPbz^`>a8j_k?ylC%IPRJ`GB=mg)UwU(3|0N@rIrFv#J%0DQgMmd z!*u%+Fxb3fUXf5KuTUCTr#0D#sB@@oC zRH3bs#&|OBio-B=(iorETRPdRTT(-1kmwU$jnNPk_RQ&(?=r_=D6BTxR5m#So z*i>Hyh13f!y*|d54nElyz*t^(~jrs(f(O^s6FBbCfAyfx?e6p|531zEk_*Iy^w{qY~B!< zYy8Ia+d&(Ob*aI4DK|cvf8nRdJVkK$siXlS%j3TZw!C$6u}t5BD`3S=0I+UVe`&L% z4%f*xC(z)2N>hDs`HRtvvQ+cM0JU3^B2^n+ToDaP0&Tw!Zka_Q_^R3<8`|$G z@=RJE+GIKq##o#J;0C9#Df`F!`jE&pJ^NKUid4O=FqEH$%vH6lHOH;O;UCY~4?c1G zFryBpfJ`fFSq<1d~lI0lxA;E1t;L_#YW79(+@I6 zk-cZlQLV8s-TPiKX8+HgW~tZ;NN5UU{}%bdHx%h5GmwV$U?@2Awm2^LH8+-6kBeN= zDxU7EUNmSftxU=Cg~x%tH2x7*_U#dA(Hp>Tj*NZA=I|Lc$Pl?@vd`hc_``%Feb4)c zya|KWe;dTYbclD2U#tISKX2K;<)Oh2StosPTWY~yZ4zuan2Hs#qci;rBH?gZG|Ap( zN7MU)GOE@J8&{PTb@F~1Tf!wKoi=$95fdY`R2b^wJmuxL+96=2L>Q)J?i0lx>;3Yq zDP{RXSkKhj998-5nZJ%wd7_`P`o9L?Kbb=qUz%NADp3OtkH5!bKgrXb?G8nMl~Ruz z_5{wek+lo8@|G)=dSNDpCG-spr!$@`B26C^yq z0e@3{9HH^{v6WGRofGevYWMP}D{=JDD%Pn**6aq)yLoWZ3xUog+M2mtF<=n#7$`{8 zdz6#BP^;I39|*is-(Daplkav=!d)=?ZOvFj7}2o7ZEw8f6r##!2b+j#gM+YxmBQMi zX$;6gFSTa3YdkBho&u%QZ!o5FhbL7ifT2#OC?hGP)%PCYBYNHGt3rIJL}h1h7q7Ji z2A^rMnsO8{f%X*1`a^HnSk*&<;wSlc?jBa^h~XJ5n?jgOCjx zp}qT zvq(b=F|>z?#IjmgUFeWJ(`GV0b7 zaj?v5Fg%G+zV0yHu>rkAk>Agtzvc@3=K#?J+?vf9;3;lQWxu3g*hp0v#VHYf?Y@IeZ;SHZbH~vh&8bBk$C)T=w&X(;T6QltY(g?5K2!VU z?R#@Ei-XYO(7T(OHP>v;w)jf}2JLRijHz0!%7OuhB}Jujh<~5S5C^CnRmc38se}CA z8$E2L<@gF?Aw6BuUMd7%?D6Ufw3j*3J3LIN#fYN@1`d}d`#paMUd@Ai2|f;DV8yYk zr9ym_-h%1r5|_G~AXK*~{~mU%5<&I>zGq!iJubxwd1~=kTe?5cX$l2WHkOjA$|%dA zH*9kzT6MD=1m~1lkez7e4HT6*QfQ>PkN#YMIf^q*IVT2tA7ViV^_T2=e5$cO!Enlh z>QQ+`A!}xeUyzNb`gi?m(e`s!wMF0dR_(Me9sRjk4aEmge?)^q0vReZ4#D7p!#Q7K z4k_?}wscrlgBFKk8uJYE6T>im>DUr4+VMXi{mwfjabXJf3PFBA->F2{HL;EW_nFMzPao5@U#}IHy==#kX`_heq3m{&jnp zc%#2zX7b3Z^4+*kvBHwxw;Wab00SQw1jyuA1Av=1ILrU_k8=uB0?bF=Y5a#1c1`-U zk(KuJgb01q^?c(NUdny5AuH-$5k(_E!813U@mwhIT%=dy(KG(_fZWT~E1%7LIn>jw zFS-C%YlWKH_VEU`Q_mbeW&s#zTQPNhcZ@*g%d7pL!Z*bwjtv4q{)KI{P>YJjy;kus zNF}cwXhoAq%8-UjRk*wEkHy_gyXx?FP$k=RX0;4p5M#wEG$BK&|J1dV3ol$H27@qI z%x3kwIAV7`aa4IkEY`KWN+k9;JJu^x%A98>2nEVKAcPe8p<TAALm^lc_6d3ABVc@F7av&qgfs$-j}>E$45R!% zgd|n+@a_yO9!r&e1{nvw-bSX4CDSo+Sgo!-7GLSBE~?}F%jO?3S*p?_TU#5k8$2CX zRYMvDn8TH){ih=Vp+JHjJUcEmOgLLoMHTlrxR=(mn93{DWgSK)pkHQLT_md5En?(? zm?T&skLlZ#M;AK7ey%qk`h^&!YT#kntOedB?&6j8^fB+na`UsV=>X`rx>1o0x25(o zM}YSFX!jqTH*@cXOm0B^$Se!vr4UCkpcJ|&fktr^qWdc(IAmkk!F<->6)_)>ogm0m z(nQ>d)7zw>h~aNe(K$mpyjva4?)i7yX0?sZ(a12Q90GAMgUB#%e3De6o;rpWT9-?zO{@ia1~Tx%zS% zvy~-Y%l=1h6?C|DK>r8t?G^PiO*yTeWYl>ixQPS0wD?rp$j4&_jH%a?No5qUwV)y< zCgxYEssCsqk*f@aj5;+{u;3iN2bB*KgK6T*tG)i%kb1Hv495)kTJW`c|8!r$`A zw7Tu?6|QIeP$EMlVg{t~S6mCLoU1*7fgW9NZ_iFZ@Ler67($?bxNO6HKX5%?dDtNS zvx+yvlbq1x-o@K~a%O!1FZ6*i`A)V#!xq7dUysOu$Qa8CV$M#z!LnWC1)o(SpJQ{c z2hq&hH_qo2D&c#t(P-44uB?Hn7y=dfh(xqrmt@l}!Gx5sq;OI_LX$P@O?k8q0a z19jK@HS+pSB98EKZ=lw?(5@DI{0f0L6m@}LWx5$&PEI!;wHBiad{uE4>O(t1Cr+Co z1Fv5P=6gn?tNywj;U{wp&XSEIS4_QU_giC($>K-Q+9s@~J?lEsa&Wy8#>Sp&H1`LF z0n0uh5PidMP5OL9gZmlB1vu0tfk)#CPTAnhdH;^99F514%K&>Q-Oo2u z1?zS&ZmHniR>zDCV%2mSezXxRc9X5}%o$oK&PC8#6=6^oy)-ZPu_Qq|TMMQ-swLFv zmU8V7GST%$4_R%a7-iwAD$cJB@-~OD_tT_St~HDpeBi&eiiG|_P3)_K25sy zjWq`7&j~000(DjC{hY%0{-$Q}yT1??de1bZezyPd&LRfX17Mc1Zb}I|z$`daIq!|5 zj-5ub%e1Usy{6bxDZ6@^u*VU1g>dQkVa~Kko!MiO5XMO&%zy{mvJj6>H5UScoSseJ z`xIMoapF!}p!ZuFh{nhWh@;G9S$9@`o?aox@{&Ttk4$6+Yi04!W8Sxg~`d%ATlO)E4 z<&@_C(GK|uuRQ~m#rYOvNtGAemk@bC3mHUt-`{_(5^MiEKgViOo3P{+v(su{u@M55Yl*?Xp0`48mP(b^UXdadzCX2i-(O+nZL(nPv$_EmJXi9bOL`!6Ik!1 z*jz4FBxCVAbwASnM?UukI)%LVaX9rHt>q4r@C(-?r3tkNyKD zLnlH+||_TN!$##u7Xx&M~Ad9k(X$a&UfL9fDR?X2N6$5MI=( z^?5AK1qx{m5b{c&_IQbB!xm)fLZ>zYXD9d4*8ic=mLtFjm}-1bKc`f~w1YE8dovK_^Nz}(nQ*R);9oG`1so|T=*K+6K|3FPa`TmT(b1$!PJJ& zJo7NLf2kQ*W$)dcudC3&e;gu%LwoK<1;8w~xWewo82_0_fNsA^?FJzJP7fvJa>G1L zIw5N+Pcag;GP#hFUyJwR{S5g#UqCdhjqgL zdV_zr9C!-)(rIHzcOaj$3PD}Pp`rIom+RKvKgtXcJ&?iJ=FDM^6n<)*XLu4^mV~H- zV+-lA)gdY;ze^SP+q3W&Rp8xwXN+$-XttzVFhm36hy{XxpI{Wfrw$r5^uP8d7rq3X z(nY}M%2#7~(^IN=X^ug>51!qD^Nb0PZ6c@b08HV_ErrEtvA_nzW!y=W_FWzJAK$)- zz2W51zhY-GVpEKF9xv)SVZnq zAZBLGOtHRu5U*>%F1xQkq*0QT8p-!-qkP!jY5Dvy6ZBD*S{T?{X+(-g;dPUg)a~`M z5}T&JeoV#=4q-7qG`~7nU+6veHtdc(;KsvI0!UIl*g;?MeB)tc;VkJ-*PVK*?;zCc zw4rRg$-zgHZFmgd8HCC1@r705cq_~Dfm$no!2wK#DxrOA@K7hN1EQ>iOJR!?7*)l@ z&5`wpa(N>UUtWL7Gccwy#9WGH>~vEZwHC=a?Qtzymj~)_LJl#efCytg;@L)9BUUjn z#*sWM3;4GzGv5Gz+xY^Lgrn?_35X(EDWClc#>K^jiu?2io5835=*y6=MUpZeqsQ3h ztng*I^q1d0EXc(68AoepsvDEiMB(-C4?kiWw;Ii@Rs9!P{u1dSAYNf8oy{b16-CZ7C##;#tOUX zs?#R7AJc=v+(K~aRX~-art8 zKv&FpP!UNSskb;4@6t9(NIAV%O?XNR``y*lh8|8Q6bW~+mqW#15blE@MX(96SHB8nenHVt`b-A!H2F>TGx zl_>JYsqLpP0uVhq)h8-;ARy0!dP;hiXid@!DpeU^qC*@o!*(7L!;|eYcV6RzPlw)h zW?@Hz!u*IBulPwq*u>fn4&=*Zaqk@BrbPbFS9dRV98PU?03MEObwGTnhj~7VoO^Ev zni>Czqxp?@F6H09(J$@LVNOCZUdk1>NUPV2w1{JWh+5Q&Hm$Gj{_!ZY=A}tdd^WP7 zUhDbuK{1fzPL9q9x!-zZVL{__+_cXR=&lYoj#h$ z#Dy`{$4t82=2tQM5iMGsC0L{TGK(_k6U3RHFTYJX3b|H*ghe?<`6w|?WT92{tR61$ zB&0vCTI0-I2f($5M~U}iC@FuP31u-IW0^EJ;X-y@0R=QxEu$TuT;xfa`OUpexEWs)=VHI(hEs!goT;e4+p9S+!f4El~c0myhFHyz@EOMexN zIK2=Ui7~z>DD*iPj6Ke;>b)|#8;_pb*3aW`Pfk6t*|`x%IVa9Y;QhT8VaRktCNPEY zH1Z#|Nd2oqh%HkrO~G4tsR~;<0BoYB9VE;`Y?|_?NMTMQI%<9kiqcYqrT`EB&{rp_ z`Yxa)J|W6%R9);^RIklF6AEs(*a)-_8}NpjeKW@rhSVm!Xsp&pQ5ur2 zsAK`VYi=&Jk_9M8AunwJUp8;MK=JN$+x8Da*q9^pxKw4x<6Mxcbsa|b({czJV7UuZSxAH*OVCH-m>AyY)_5J zaUSUUAZX%Ye`W%o^Bt?yu{H9vVQVfo5lTgDDn`?s;-KN&Od#`f$J?)c8j3c#WGLrUR!89l zMe@%_>^C#Q4fLR>{uF%t5k?NYrE5$dZ)rT|X@fQ~?o42GlxYw5sUNw=c-^rxWnZd4rv16-LW=Bpw7`2%m$&qU`t^p*jlBXv!U z!NH4AOQoQoOprng;3Ue-(lIXJ?ioCK)K&tr48%y-8^w-{_S|@ z<>GiIsW27A=ttAXqudSX;3889GB2h)xjs5>TUsz^e)w2B>-DL^C1oxg48jRh>B)=r z4y@wYqDG^{(2MMhkWD{~0TJJBbDmk##MjHLL51vG}CSSqCo&GXvwr?K9{4 zr5k`uPm`3OqXkmGKx+Vmq+s-icK7pzP}5X0yVsQoPgAJi`=D2dWo=ZQ@U1B>$a1_u z{ujRedE)g3C4Y!dAGu5-%pM8>Cu}f0D(As$F}zWznhZgIE|;~kWjrYyOJ;HxZnvHf z>iP_9)NCW(oP|*`4h;XKxT+L%`{HDn=xt-8U|?k~USt@oREX@Bx0pnS{M_N+ze&4V z)zxSB{gE)bzkTSoPTxfvO24H+A3Pj$@hAJ>nUThfsxx$@EvuT4aJ_J&b; zB>2jdyb0Bt$ccNXK2Y&KQc3DWvy~8%co?J27o$G>d z7rQU@Ss5NSx-~lAZznaFyv}?0@l<*f{l^;{@dtZn%?W_JSJ7k7vJXJS-ju*24ehP? z&+hXz|F*0`wTom>HEWGtBhTJMg4jg~p=jEBYv;>cjbVSf+RW3sqOS;0%#@;ocYX0A zm+k;hp1KjAxD8wP$(s2O{GKPm4dUv{-_9` zC`6%nn>|}y-q=g0GpHQt5l#y?W5Ug!&dgvCC}n-o=@y zd>^O!w(t;mYx|FF-*?IwuoJ`#d^qvflpgzfb-AgxIhD zds;bGM;p&%<6eNiepYg>caWTFzDO%Se=#yzY^FCmkWfafy*+5#*7Ggp5RVt1V|0ZD z)qGIn<5f?hwaHl7%uWY-UT=Qiru?5I1-AVAkRb|?b}DjMX796Gfu^ZxOR#f#6 z-PbJ~zsF%oD9qp_jqoEWLQac_4ALL5P-q)cDI!6_3>|?x_&xah;{YyImsr>f1CTMW zPQOB@<0GNplQl_*L$Uh2S=bc+2oQTC5uxLqrAcc4uE!+e9}nuXBsp?9(=)F)_UyRYJCwlD;{|sH#+=sn;4K^Z zvedNRP=??)UD2noSUq!sOT=~HB5bt)q2OL4M7 zh#H9%Rq&pTmdLy9BY(a?4hKecSIl+#4EYXc z>Y|+HRAFk;m|+P&|DB!Cqt$EhL)EJ%qpRo4(BkriZ2W~oxgYsJO&tz>^@mA|tLsnP z%on=!u2bYX1yMT&hhD}Ss+tKdx`wQG=o}{;F>zw=Ss=B#b0)vnCH|Y!)xK|@@{-^f zuIb(@@PIT6?LdFLNY>lq^NX3Pip&R>LM+@h8t`iI@*xT=&sJbE+QAhP01laH)yVqOJJZr5t?4#?)+A+S~=IH zBQ}?0w$m$qyxjs)5=3RTl-BL`f)ah$@t=IT!afsCxNyi3f3BZpWE<`8ck1r%xD5PG z(^D1l2aIQ?0tI?Af)H)_ACdSyjDc2~*|ZS(diFJbHI>s(54@}z@4ganU5}KKP}UwA z<~)P&4vNi|j>z^l@3%&(Rv~JtU(0Sc!VM{wKvrdDeH*J@&*$H+ZuyWCa}AKXz~xIJ znlg1Z8?8?uKger*U3+|ljQsih4lLNCk?ZQfSlUAVsWkp-h)+}MiJxkg>G}-4Eu*{i z5rucaXT@-_u4Y*hzO>jB3!5NH_OcbnAi#%+;Z$_EQ($JVG>fhOyZGiDB`a2eIJ{FV zWwaP6M%WNp^e4vX)-eUCh>s*|w7Z?)*X!8<9q&lFB!p7(x!L}yOl4>1pEky94S4kde@T%8{+^(1-p{mZ-&Uk$ zuE;qjc_jB-E+g&6gFIyDW}h{q9!S`GO}hR*GVUNkX??&w&W#+u0%I} zCowI&;M%boFl+_-65_o)2q{dA*E>CLdtIzrUoYPUv*X9okFNw<=lKwBvl&N6B}q)a z?@D$G*--?===(jsX)5`?yJa|_6`!pK$;XRDZMRTsN+}0y{ zX^$YBTUw2@oG$*IqkiNFnJ;mm<67kWE@hQbHwL=cKYBC^JhfH(!S29-kz)B7(~~GM z2C>@w+ilZ+fR`G;Zm|Ejn{+IC<-b~IMp3HdkIm3#DY2v7-j$?ZEdH_o#184|Pu@`f z=^-CHAq-{7nJb;z!&N^s^U4-Gyl#cV1D&gG7mOq~KH;3(f+6@@hn5-2un|9gUiaOH zB^E#2b?xr(o^<7rD5uh$@R_{6jh`wzrv&i;s9DA2Bz?y0PiK0a%cH@{)5QA2!LKo| zy`KggXOmkIh)(?e2h{JVDpKz=V(*A-J0kZR!2z#NA2IRC$k;+m)Ya#EXbF#wLeeM3 z$LmZ7;Ek)KC=VP5z@)yZ!oggH4L;9Nl`l_eFXFta;4wcixWUJa+l|tXSUb zXll9OTbkMPod|n|lJ48N;r^P3*Ugx`*WpijFWYeol=Ee5gqG?7r&G_pb|JF9; zgkLAQyv`5gopFjy<{GqA?QkBz9ih$$ExpBKY&$1qdBgkGc~Y$n#M>;71ZXWSZ7e!C z7pqU8(jEu_d~6%({gxMjuyO}Zh=)`#W(e8)mzeOLDJYa!MU*EnPPFHW0?ly1&mSHY zpL=AojMW{nw-RO%!c`B?3=+5rC4ys)X9Y=vpFR4dk8VeBL8!xj%%`d6_z|gQ{su$& z3l@5)bKeNF#}=#g@5aNCqOW1#;)W`+b!l?b9K2C)IL+E=k|3+dfl6-jM#FyXYV#Y~3k#?elG+Zu%UI z5fVtkxmR)EUCe6JcVEb@|3GL+8uQ9XVimql1fu6(wxRzaW|;DGh}H{4_`xs{a^Djt|6%7vY6!+47=Exx@1Nx0U5}UVUG0dx}3FB)v*RiVc5XHKwwABS0IoAQ>3~T?;?nm#3*`Wz?#cN6 zrYT8aT{&?^Qla-1@FvF>tr~C)RxAig{AYW4TuRDsOH}QQPY-roIqv+PwiI1jee>mP z#(#X%{X<;-DF-|p1qUw@oUJeLnwk9BgA?o_2VB*A|GeNcN*_bb*!L08*5@W@0s&9q zdhTVAfyOb58hrsbeL>;9j;%evxOAW7rO&T`*MeDYl-kd~7T_r=zhL3CS1xSm20xtN zZd~hI#U>3S^L-={@~pxqe11Bu1yMyb++R*|&EO0#Uc*uh_@k^Q=r&r#vdK^;$|>ho zRWYZer$47b2QQq+%;1SfAX*i`?5FBqA8nbf$eY-XL~N-}*WDy`kuM7W;F#TKKm;znt&CeA`W1cO3ZgZ8P@Q8($PG z#nB8>t06U>ydhz%PdgqB5|V33A{HJN5{seS#Pkn_QN(Ixgq-nMfC0e%Hy8s83tEK5 z+Tu{6Y$2I_G3u~p6%G!O7cTJ=&79o5BpGpNmSY>{^v0`~-Eevev4EQYmqMSkD}ON> zuYcg|Y+{bu#ZZtD{Tg&03R~^uNcu0o;8E^GG9yC-OZY$@E2CRul(~O2%KOz9#qgOK{ZU^(+g|7z0h2 zes+ZLFUI|7;$-QK}@NzXcR#;NBOZ`=PqC&P31WfcU#D;q1bl zSgD)qO;V&E{|rT^4^*Q|x|gTT-{N+^o~Rgp6RM3Lx$9XnF;IZ2>AI%9xxkN)-wq@S ztx*Ne&v370ns-z&gOZXGvrM{+GGpw&0I32d)YIMf{KE_lDqN5BXG9mB97p%)Wz(sy zYRpnGfxV`GgE!2TZA%Df7n(!~c?>(w>)yDpOKskd{i=|7cMopHR4fi~-`2DGlp+}A z&tGTCp4J%7xj>;N0T>IHUPu}o5vucA73z(04&@ocQUl$RLH?D$<1zNaGQ5!ISuc=a zRD=U$Cd2zbng=Tx^_GX4srjrhV-`Jg~~c+yAZ&GN9R`q960PT6m}W}IitXNugSoyQeVKDndSAR5$ru^zs)>U(_{ zC`!g~bf@Z;6f+d^#5st@eRwJQ`S5yKZ6Q`+iik9HJW<|(~HWt9g@ajO3yUfU!@|}_mqP$ z_!63Lo)>t+)9STC?(&*8?>G7O54mc)-C!kHp^3CCj|*!#z6+5sm1x2$FsdYr>uPcV zI|0LSu6NfZqz9=%2`wQ!R`wPBHAfnu4IrL3NFnf}!dr z7`E8R;XP~FN3(Y#ybz$4uHI&Ahb*9rqj!r?nTg2Do?l0XJ2nET@ATe?c8P@4^ptw8 zQftMB92wi%FB&7c59AB`-loC}ah!~JmR%mZ{|QHdK0aq?A_;Ckt&vnyOMK`84v{FJ zVC->eiBuh939Yv0O;GghVQ(N;+);FVc@A)#ng2@|ziRHx&B|=Hnd=9gN+b1`M$&T> zliTCer3aOzS5%Pi`^6XtU-fGIXb4=98J(KK#XZmdcGWoG+;cd=+Hq#gvR282C=hx! zGh31Bb@HrPwWcO#7B$WU8e-~NjmzRSt0lK<9z=v;CBhF@RF1}R(_MbS64_$#;}$L} zV2j#O@X3wnZms*E-GL2O<4|p{t`ib=&#|y$(khIkO)u&crKTp9>4N7K#N)elUv;3T z-+xh$%um38CAcvoQ8^iUh5l?2jGJiEvM>}9E-O)8?9QL9n%GrY;;WjFr!uiY#*S~= zN9$7i_~bo{f5A1Xp{}W0#h!?PiptR^K4O(MyXP5M+AP?WW+ z_+s@jYUC?=1-Rx1VTM)QG+x?{%F8_S;5du;dUUc8?_8+=0 zMFTMc143s;a-3|*e(6c}NL@=EP-og*{S|=$(GLBNmibFN4&b=PPBj(ry`!e0GUPtp zg|4X-4&8eo+1bvEogTN0#R>V0o!F5VLcZS$cnBh`qDPqGshbeC-Ma5(iqtgimqUPm z8|j+YVidzz$z-R(uKV$j$W*5uV}FTf!{XKE>n??b`&YQFR{?CdWve2^Ld=g~!F`=p zlO}-zIwD$5-B>Qcu-%xuz6i9X{Eb80U>vQi# zOTK$U-;lOSt2?KAy2Ns1r*$4zHZ=c`U5c^#C-g$lg!eX=eRHW!y?rmcx7-%${RMk{ zl`os$_MXydM#3-cif}`80&{){5Gj7ZQvCcFj`yvl{|9=F-47vz5N|4#Ucj;v2b8Vx zA{NDkGnOu^`C+qFKMcR~CkwR6hT9EO2E{%&_utOi{V{73bhc_YgOCc}g3%E`Hh0gngiyNv=@Z_k(JKwy~C_!@*|=-m%luK!SJ3-84#C$8Iq z0|G^tei0^koqLsPn>7zkhN-ni2hSErqVqi`1=ZOCHB!kKA@;p)ZUdG2m2!XKr?cAR ztv*A@YlzKMa(jAQ@*jYRVZHCYBL#OG`oHUP;Gvq66#J4I8^np=NK6>ao(zxIfs6)< zAFc>d`xT-+5Nv*pnEe|@WU=gDgf{PHe85t?_0n^TkdObtYy4{Z()AiPNwR<^(OQf8 z{L85~+2uXeY5dt~>@UBnqFNPK=2&eEt9j&ww^W#$hS>_OA3QTdL&V*bPZH?r{$dKG z;Y1?XH!J_-42&lO2P7NZti3NlBU3V`#BdWLbZ`iZ?4J%wd%61Q?cHTJ*?o3R-DQ)1 z3%30Te!$#QUOKutMa-#unlfqIHc?Q9;=Po|-s=CmtJbO($E=(3=&ohyPU7i9n?m`z zWk+7G+eQ6{$ncZP4-epD<)eAI0K3zcFh#)6ap)g;RCsV3d1p<_Mvx(RQU=6RQpf2* zf0Q;iVURmYV8hY(uBaE%tINKr2!NMQb@>9B0bRoSLIG!!e#Ds`FM$KzVjB5==dK03 zP*nxFh3JJ+GBP-J?VhKRm-+1-k}l921M zFySrl!2`(Q@Al@e6oUec8@g|wm|~85C7*NgNsV5ATIY&;dN#}Hd=Dsn58>iyl7Xw9 zkHF9&l4|$;bo8-keZ--i0aetABG9@|mxTXy!)Wu=p-Pl6b$(r~E$m_FmxgI5vi1s& zCB05@kyH~puSx}brT5C7VCz&FEZ?^_$h1)C2OG2(AXS>d?^0+(>+`!M0(=zpTy;gx z6)$p^!Nyw$+UxtVzpqVqZ@ zB_~Vk+c7zykQsdDJI})nHxa=9NM**52A9w0b?syDTBypL&FuF}h2rr=!Og!W4rconh z!d55)mNHFeDj^p}^P3=?V8QIE!3miP8fHeMNqQ<&L+WSI<3$PJgTtjpX~VW9zh2oq z-V{a5x^#>@=unuWRgZ6$K^Ii{JPd{Wg|=tIb1NYzbGjOgR)bX4P4f|b^7B9`w0^<8 zX9OxYX<7JuQD2yCR6D9oQ+^u%C_W57v<9_h3TubUWB#8pt=Bn z!x4S;^oW2|p@6|@m~ze4>u=wNZf0b~sNicD-nl?p;UP{Rr54WS>_DB$=Es}3uBwM{ zK`MTY#QF^w!bv0D)%0*881h1JDR5}F4||P^TH}Fx1-Y0!MXpkX9M&|4DY=Qk3M*Fl zI-iZQw{@(S4*MgO@p}C9Lb569JorTltBe}Yw~5nqY_t068HVM0L(hG{o>YkhH@uGo zsAkgon86+qL$AZaLsTr3qrTspsTs{yXj*SI$Ewur&5MMrC`Z-i*Mt6EQ9@RkYr-{U zSNT{I{jA7Htb4zJ7DIF^pzu!~Me#35u0c|k5@3~IegK9U>4)8DAlOS`f7LiQMj5;J zatfF^^-ihc6#kOt>W9Fw5?Q~~03bx)s5Va5#d2gIB1*<@*pwIkkweaN@2W;Ti0IqS(Q)hegw{wTeW7bMUxe-xKQWzIr!%1s z?aB4}>$MH2Iwbr^8sc<>Y}Qe<8yEM!{0+&aj}g`ur?4c>sYz=7_)2Wu-eiJY(i|z~ z{jvI^#|1^_bry=>wRPk&_E9u4&Kbqo95155rNa**QDx~%w1UdX0Yv zv1vs@ZqcIy?ueZiz}VjT4`1-$ir0Uj(wOR65GwMG(J}^prLxBM?tt;K_o6l;AP81@ zn;xoegnWo?`5y2d=+bKUiJ$f53?t2v>0Wyv>AQtfLAXe`zH#MB$LJvaftnC zE16bdWBD>2^r)=fc4Gbd(z}gd8G6z!Nw`X+1t*8t#uQ$Z%+&B3>-D|Ld-R;qs*8q+Sg!S-bx$?gTchy(lhy&lDviyboCNFZ|D>3rhio49bLGWK$M>P0 z@+En-oz`>=66a+1)alffex|An#tFHd0lw^FaGA8TQ^>1g)WE$m)^>hJ{4k#Q^yc5{ zl&-5$Q3Avt9wO*pYO_c&X>q50*iZ@JSl#6pO_!Qn=*ykWpDu?UT-+G2_T0VP+?jo5 zkCJkR@O!w)Sj(UrYZO1_d5NT5vJ6o{Tsc~!Ki0-Z;kKWG$r|by7UF7atbvvUZQIQuK5wb+-v3JrAeE{fo*!QA@(g3n(*33xORH#yKRo>_?u?)0wq zhw2|Z0tuGRn@!x`RR{6MFkBcoj-vR9>FZ3vXzismq3*RXI`xcjrjUYeIEtPb;gUpq zRz_QU=he|>wa@1dy}ugX$(u$aZ_MbtDkInWeJwyfB0UJ_WZSW0b`w`XYtOZ~LQ3j< z+apOCpNo!G+~YSL5@FJ6ZET`Z$+=YT=DtLNa4>wNhkX)vjnhcweZl; zO?di)88w!?0SAptOv&ZaT=xCPsv0drvpQld@U#@blofKOc->u zj|Isc&i*~@_Da>g1!kbd>>k05hx6&Ql_b45QVn!7-7<^~E^>UnSi^3kemrW z&PofNd0hs&9Am)!&T1e#p<$h*P#ZtO+f987|Et;B91lgy;;bKpnH(0n(+zh*Po?%a zKk5wjAYN3DKavdrUw9L&q+Sk=q{}X7wR8NAg{qc3(LZx^kD4>}5+XUQVH(Vow3=K? zh))k>Dmc&lrblVp`4Ic{4rBbkBTRU!zhdRugAvhff`nazLOsjNDGcjX5FY%Sw2!}| ziS!wXcqPF~#yR`rktbJy4iQz&->l#R7|`>=ORX1f_l)-&TId@jR$JD;P9xh#MvJkU0o@T#AN{b6^eYF4mIh* zXhiAVnDApElAmmc+5Z%wXRIFQPEWz~ST@8$70BVLK?-_OG0M_Xax);xT?iV_|i*f9cxks_OnHVf*z$tYJk<>lp7tMW-A zX$WxNc^-K0=(&1NmOAwirg4+a+Jp+oo#_@NJhKij9LXWT(;QU*jr@B#a)7t zTxds|C=IPsV(gFzbd1(w{?<}N1u2n0f@(a8!(G`{tW7l|F;syb@dgxNVAS9+OdA!AKT_#JD%?DN4DKHsgNufS#PeHIRL>H=@2AA(&6~wU>Rb?C zc;SUrD%2l$m*e;S+&`jrJcRyZatju&+Yx317j8T<8n2@76%G%|sbJ)0q8u-FQ7TYw z_lT*YIHDGkb_6>AWqm@>F{ppb(w=B_cX8 z#J5IL-z;9dSWG_7`OBr3UV3f~F~FyMk<$&w{v{;Y_I z2n-uGtjZP|O#zikB_^t`e0^TQ=lu*14@cX!ZH0JbFDVy}EDZU#;6UavE41F04Cb!#)yc-D1>uer2&}UfGHS*^V45(n?(TVRr?F zcQoOOo*`%*uEmOjTy|)4vQ#n;)F{4|25Xx*$Jo8f_3MrlIGpsnX zZ4mmADE(|x3DW4ie;%?U)9l6tor4jo_u!X37ICiX-NO^0)F3QnIW!g7RDP>O#lXOU z12JI00N)0ZrU2)+Jl`z8!}S4so;lx$j*b?s5jd}rL57AN*cRfsdbzKCP3@vOZ8AD; zNe-s{k`INBm6$KG9uT_E`?XFHx7y;t{5ukHS-0A6>UKPG(u(oO`JajVz`{pzEc{#9 z$q4FvS`sGDzS0*fsc&SH(PQD-!RF9p)X!5-J$1~yOMDNwk74iLy^)ZRAf6A4#UiFC zS-pC-7>=8_=ikta2YT3%;f}=Djx6Z6>GTvR@JeEFi1s~1l!4n-+yckyA_ zgeS)LE{Tyw#qoJmZEkDIBZAU7 zx!Z*}r_+svcelpY%zR9mQ|_Hcg44iWPEJ2x55wu;TcxLUd>k7EzD}N2%73x|Ag)3- z8EqCchl%O=c@E&~A(4pkt#M6EFLHIFp5KJ}Jw4q7cm1WnO@;l}udN|t$u7mhuywdi9}N03?4jKv_$dOWwOy05)vXtB4tr%GMR)Ykwq~J#M4eYP1N-N zyYACF*i4+V1ox@G#~ZEI;^Q7W@JaWr_@Mh1Jl1wE21n-8R=Jd?!(*Fwr`H;AQ{z9E6ynI0Nm&%l>V+WsQFu7MdX{%>Snp zx6IGOJ4OWW~Wv?L%G?JC3i zbh_{`+TWiB-iALbr9-54iYm; zB}%iCQC3_a`p`vaoH#wK7!S5e!Nj)v(b8B!1%f-Aquz>RaV@Xh8@ zuNU%EjcO8*pZ;BfCA;l(JYQTRfq!SNFyJPoGBmE>|Lu-_R~s&zQm!~+i|U|Fg|G88 z_#j(@4|6n_c~qD|@n7d_kY-njMgZQuC6b0goi-VLd1V16F3J`|Z&$7b5=n4S@Hda) zCMR4Ad7Ls$G0>#~*WuwNG`x>b@QUrY#`h@UC(=df{_VcjIH#S-7h8Ambxpou9=88^NK7>$IWhd3 znak|B`jJ7n{DHx~SV?^$n~WYyb89ln@Nd6+_iiy!Jzsy z%a$z@ecah|cr?7Vhr}bVOG-)-9!8JHEow_ON;eAa8oYg|4Yn4CQF-7#WvvxxDqk;C zxokZZ{KpN{@lFMil9OyQ%K5{}l`9W7DV8)I_*vurgWNBaJ^S@Rb(@Uxvl*uHU=9hr z7gE(YWOj+B=lt66-u}0TKY3ezD&zG^yxqZxvqBuO(zX)GsS=1Lqjf3>XKgOQ^|Ssx z?F64*O~UD-&Ot~FQB!&A!TRJ1tV^?b`z&~i0Js4-<|7(Q87fS`m>ygMWq z=Y5ifJQi(ySQ1^Ao)L}vyJ>LotSporEeHAB$6U7e1!luQsZ`;Hz9Hxtp~Bi^3%=b` zfrKbM#&$M(b)n6Lr8~-SsGMGRdb@kHG@?VW0^jbefRcpZZT-UV_rWr3&UOAv6pHja zfoMI_oue`;o}YXY|084c-}$Z{g<2=my?V@r2PcdH|LCDXgmhyru zs~cU{8!3Veyvj-UD?;9oKmGXzWl>>z6_)m}q6=jyHWrY+Rn>xRO3sWZRNg;8X8kE~ z*k(T-BF)^w!?!FsB?2J`>-4QlBEcuTYn7oF#-P>U^^4>2*y&-orhhp8c%Tg)=t(aZ zd+GRvq6bDiSp3~Zg)@n+9*IU0kScK#!h=aj`i><$gA|l>o-m3Ruzer`ucJ@@6UfW# z-4phMuX$;O8=qgBgtsn;L{~b;yBEdaj4(CM?iPya7sulAZo!yvZZr-)(E)e$3Ifb- zjOZ4EDZ@x8Pe|NXaAEHBwPZ+Z#EU99d6X(8 zY!(MTef~FJjHEGQAp?>J7vWG-{O=Bn3$DrL5iCzcDQ#yqdxwJFx@{W@3-iVI%52%X z1r=qbz9q?t;E_aG1hV+!GJw5*d-v`YzX>mM;4&jCD+@b!?i9b*f8~3;PDN!#PPqqD z4z$7UiXiAn1o9pZw~~qo1s-Ub45RwkDK8o_UZGnvqwC1NHQ2eH@jnAHG|tdi;QWCb zhOH=~KlH9}(s}-Cw)Nt5z`ApS5W?QW{$#J}z41!gOF<@JvE74BWeU;QjQ&?2elO{S zTq(w8wcT!m!_IT7D#*@M*X)=9f`3EKH&iN0M->TSr;A?Uj$lh~rC<>7(h=odzB4~ebOB)o3yVMIZR9gBCAU`Gy5 zK2Lg%w3ma#oz|ej`14{g`^H2Jh*HBwV$b4mBQjKj=SRk1(Tz#CuA@OT>C|ggcxGq> z9vT{n&o7IGXuv57J|!;gZo-TunHV``AKJd1ia(2-&`=WgrI+BWX$R1D@?Lzh+k$s5 zk4KC_fpUij1r|5{M`HHzfx)=q;}opRA(dI#JLvlpW>6xE2Lz~NzM8rk6{R-syn~Wz z!TFWmsKNSWd$Dh8mM>P)*m&`kS1{qR$MD1x6Y=_MuaPh-@U5wuJtQ#7Nnr4RB!0N? zO-@V4V-qIesi&UAxN+mKphXpy;j@ zA=7qBwFX652k_?9S269anJBTb9q+vfM;}vCx74XRNFrgEK%!9ex2ps{N?ceE?;V2s zE{{XcP$d>@E+Z>ki#yMZ!0A!650(2PyNBY2-Xz_WN<2C|24}a>(hKE%5b2r1hgT+H zLcbs!%y(e?IpJa?C#6b-6?e77pa>-v?KR`GTUz4oZbo`hlz47*9G)E-$}NBp;h#2< z7UplSz@t~R!1Qb5q35I9xTaV^qEHEqi8iDs{^_N8u+T9ECaF22kS6n;!m)Y+kBCd#zbmz zD(<-bCcN_MYuI-%8H*Pk5pubCdDyvQ2TDuG;P)~8op-VH$HgcuEfqDM47WTeDJer} zunEsS_bhI{^>%#x$*0IjKLi&U1-z}FqT*ulIcn5Jxa5DAiSI^}kp!lmc#7|G)tfsnV0k^y<}1%(ujb1OGle%>;|U zAAkH&%vbsAufO5|-7j8uGI_3&Otn(C(QDz+|IZG!!OsQJFsWQHk}x!rD9p9Kk-E z9Y+6eEH1nFV#J1M@WqE8V#k3T(u68#bvi0r)S}$c>vf0AGVU|Y`6tKeX&V*CQ+oLw z=f2oltxw2_mtHB3qvCvr;A8Rryq!*`7hO2G;)t(^{U8T+@5HukTe0AqSvZ(o1TCe* zo1ZBV9Av_`Uw?`4MFxeh=4@PjD${Gx(C*=raU8U|BPkPvgBA;CD9nuf2w zVo#ulY;O-l$ye7K(v|1NxQolX*OX0wQA zkM7BcK_PbN?;cMQ{iWZBYYA0ue4O3_3v%MIxg-?RQ(9xc+1rQkR1l(#g_zBQf$fd> z{^lfnba{+e_tqt``0=(D=$1gjkmnI(!6(+xepJq>)e20#Bo51OOTrJgBw}zo+K!CP z(_0$w;Ls2u{Nh42c;Uh*Bv4uV)UYUw>=;hME}ot*55^6T#Do!Hw0}!1yfG0y66l$u zeWqU$gXkc-PY&Sr!C^S7wGP+x3&)5yMobwQja!HK`%a(QwM!&T9(qNzB%HXpq?v3~ zg;FhY7tV|P(}oH;c1GsR+a11~HeB62OgP8jAvuF_=1dq#w zeY8g(*X<`I1nLXepOmQ|5;cT=VjsVb$?(rr75eRm;|B}g>{M7GO0t( zXl0m6%j8ntfvMX!vDgv1w9z zu)oZ!?6&9FNf0U!%FQ!re~ng+0f`1I{HqXO?x}!>o6*qAl2S@())gzcFnZZocVyJpcUj=-qDsKK}eubnDUuJ$m=S=!-_-s;jRSO%wfj7WdEC zx@`yUz3+b9fA2krjfq51NVwRhw8ZPZ<18yLKcDgqmk=j~MTH`s-#{)j|K7Y61$mj6 z{OofW_vm<3Sg4>?tFU(6Mkw5NJU4kVro8wP=6|=4w#z|zxfyzThbL1SPd`JBcI&5q z|Ne*bHU8wlqR<~sCeP#FRpEBxpeq)CIJ-~`_ZXz#n#z-Mml|KBx5RV%J7G;>wAlV6 z4tZR}Gei$Z=NKL4-O>W{uT8|dR0{^Zn+}WFjmy8x!0Kc(*520|vu{X3Vz@?>mjbI3 z&tDLOVeNFdcVP~8<~lIvngr;KO0)^r;@q|*4ylYZQrUKCPZLVY99T+a-R`^!yuG@Z zo=*=(b_&M5XGG$0Dr+57mQ5cMB_^onLEVB#F!H&Ev^Jtoq83YcmtlXg0}Hp7sPOYO&5d9FC7gd z*|(fcaGs|jVb!rqSA>U};IxBT^OEuW(-ZK*D^v05 zCo}Qn#0hxi^>;|HDi9Q;$9G?Sg!g8CMIuo}<&qj@g_(Gn%Bp|%rBk+O$2(JB#+z@w zgJ+(87Vm#B15Z8qINteWE-6*7@N+p`&_%St+5NiUuhlD1P-YWp(`htV^XpPLO$j)o zUvD@a-X{NAN^|Wwl{l}n5iMJra8>UhIDA5jy@>4a;=r{r znWc6dva4`M_Yj0dYBBzt2!v4qcQDsR2UlQ2Y6b58As5sCF2d?;J6!Z|(i7^P6OjMY zQ_MB6I=u>SUL1qY30lPP1i@s`l~_FDJRy2Dx<+cyuYEASzcB$j4wT~eOgn-!3NZu5 z{a>YFn_Yv|w2z@-8*nFITcSn`6MP!eh%@`F(Ta-5{#FO86&)6oF0jn z4jnomE-p@Z1*?y@Vv;?Hp`l^u*g8S{=E7p5ABuBKk!>nynZ1G(0+NkVSuu4?EY53h zz!`6+;H9OxC@i7$Q#qAK0_^1f6T`AgBCpf za~_JQc1EJB+| z5|KKk=xguWT^x%~SCnT*w%LWvnO3Cc_y$js<%mRbTsj0P5pPf;wM>Cqw%W0!z>164 z+pw7I#OpWP!L3MJk}7O(@;WMN1eGanry2XwveCA47es}a;Bh&zZNnc}{?pI+<(FTu zbos9^+uUM6JhokP@(Xd-qm%H%f-liAMvIy6zJs)KE#Cj|bA0jT*Ldf}@%U@S516}T z1=-wsA>P;w4y@2xOV`DiZA#`jBp!;teYL{emsuuYXzW?G9g@xqI*qsLie@!1!% zF#p?cF|MrV2$dnFh|O{Rc;XBQbrndMGgJv;_$A8d{P zwO8YTud`9eBYti!!`- zV_X{~(c6|;=0--56_5W?h$k;jz}`n&l2WkT!O3S19${uGRx@)j2rwEUa@>;0kQs8uQ$j3z{eha)km1&WFb zMIoA-N6!Qux45(nRY``7eKPP#@H_x&Sr zs-?1r#h=SJXQGLRI~3(A8Bn}EEowQA2f^`Q5PgbDP(rU!lK7*qYOfoXBfXXJF+S{*>_0r$J|BU(bzr{CmX5qEh-o(Llvi+1QIP6XgKKpzOJ-s&~ zBBJ0Z&Bd+***JUDXta(Eftkvwo@b6k-?nksw|hIxuA|C3A4keT1qnF^2Ay{i&KcMn zRw_Tw9d!}HO&T0bP8JoRyp79gM^d})XrCB?mA|c|e9VY_+c#iuW+^TlaW32rySQ&k z%5ZWr(vU;;eY^Ha*tul`{#>^ai7i{fT2z3%ydqJyN@|}vzssR|6)x>YMj6*a^3y=i zm)YvX<)0kF8Sfs#@EM2D{f!j7y{$s@=bQF(F8aKcf?+ccq4(R#n7E=$ybPm1O~c}J z2R0li!|1OvP)>#0f;IW*_hvFK`1laIPCtkn7LakKQQ?)P+353T3ae~2k`aaqWfnZQVO^2BClBAhzh2HcSPO{S zD*8#kcxffvY*0xW1va}K?|<+i6_&5zowwh_>HP;lr`2HK;Gvj2c{0BI@=JU)<6Vrp z@B*=Yhj#5Td+s;*g5v+xhmihl-LX@QRQk*_laQKn0Oy@MT=XL>C@jNEFTI49ro2ER zvp{&CtTr2+oALv~Vzt0Q1^Iw}eX(WxZcLr}3Z9tw1c}3RTI<2tLxy1c?){iKb0!vk z_Z>d@_#^KztX3glS}b`oda zK@I|qc!sFFNh@&R!EdrK>eF<@1*x$3)_4R_S@Hpi!It4_4F4z%56;g*Mv(&w5`Nxi z&x7x_l;Q0$(dg6OgqO~b7M_9Kc`hOB2DJ*t1yshoa()!rgpyH4yOlYwNYAC!13jJNp}~>3^{fb7*E5(jD_uQhZDkf0uIgn%=N1O< z1)StPmPnctqGTYu;LQV8w9o^q&kDwwQ7Ty2B~RHl&og*>q#A#n8-f_M8x zRUdi>m1;dAf`X8dnE@*aJ{M&wSKl!XZ@oDk58ZPkf{gV3``%TaUo|2;oZe`saGX?B z6m$1w#79PpN=CQaMcPz_2vZQ1Qx4%|;q0ce-6+0SN-pQ2dYzW+AE#)Yp)m%*q*24- zupHTp@>6JoaL$?iv48t|?A@1)HLKSmuJh^W-YSC1GjDT0E>Epip2J&(zgPW)Z|2Pt zedjmt$w2oWU0@*1P`%1NNlmF2*?nbC5=J^YHT}rm2hD>bs|%TBPGpoiQO>P@XuS|< zREQlYbYM?0KlDJB@DS3c*@X(Xhm0#XGAg_e7S9lxPWx}obHGFU=2_^0Z}YeN zVk8Zn@sEtdu;J&6=?W%3_84xw>1GlC+@wjk@~W#59Tkn|pL-4?&c9H^Uv)CZ=!w2&Ik($!X!FY@4lzuj@xg;i!Z*6 zU=k`9ju?US&-aSVTW`J@-MjTfWLPMsO?!jHOkdo5(+zn2^*0a^6@%!=FueQDyXe}Z z2g1X`#6#_32JLer`0d9fZ^iL$UU}5G0QbAu(t|dYKJ_SCt{f%-h#A zi=nfrc-o!r$l1RQzi-$s24vw8thq};5Pcp#urK>?yIj70i*9K3LD0|!8QD3|lBnYW zW;7%gb4yDQOrlWBc@AyM6_F|uc|3rchr}KCIris9oQJVzkf)H~CZ^`1e3aU32sMU? z$~8aSufE@qbCD3N!lxg;hfRC4FlNjsIDHkQ3~#O18{we*`|8^spmK8xo_+RNOn!a} zCQq7(*igM_MJTC#>O~NCh}PlU_9h{exPTS~B)v=u7FZnkyKh?|*0_m_Kc0hsd;UD? z`?1^l3Sf?>2VETBx2^yC(eQqUkHgz>oPT?N@WnIY`%&i-$M8eq@Rt5zq87F;*#74s zY+88`dV}n1R9o=xtTU*waK?IA&C+wz80gcd7tTBHJQ3Qp@8F9S-Z?HL8IB(|Y#4?NIa7!}KP_7%;ewGP(VmXqwQCm=D<&i* zCZI#dj$(bE-n|eJ=^dP;P3u-9_=byf@@SbMq27@kV`HK*?A&unM4o>*ogLb@LriR( z_%1mX9$bdY694aWEL7I`)gz4`G zDsbM+R54i1Q$OZo*?tn&9KYX;Gv7aicmDKFp+c$leM%$}&L22e2~yB|OAFjpe6LxF zL95IdyS4;N)6FCTE%+hLjH~}F!G(WXu!qE;zoJK{U|)RoTO&A}PV^gm4o>SFj~Q=G z!@^}NkY8AW{Jd=J-n|c2yMxSYI;!{oVb~iO78Q@y31L{VY%%tw<%?DZs~0c8wnIhe z)u#`MLj_`^!jYA609$t*L{ZKmd^LM6ipp)Gy92``V*mcjBK)#u16)okepvWD%AIO- z?9h(V#%>m_dZP1@5Dke=!Wn(KV)MogP>05$U-$N~iSpE&GWIe?C$vOjY%u11{S9&o zN-0}ZAU87we{I|Zm%snLq}HhyLC8EZG{SrK%1`TY;79Oei=qG8_GFL2ROg)kg_t)= zr@}*L`iJeVecpfKCwQowSIGCMR^b6=vQmoh&6{g|F_NYTH;IunY+^{{oq6V&qOYZ2 zNcp|G9F!gxWy2n{YtstR(b2GbR2Utdg?NJ*AEmS<@uY-C=_ccjTZ1TYZ_8w~4Jswe zOHKQW!_^9+0+EBilJRh5oxXECeWyzj;`66n zWk@TrAKv%y{`{S{;ZI*TcYnM`45%Yw#Ni|o$q^i+2uFr;Gjk{9A1WdCN+~~CkgdR7 zn;p1vy&d;&bz)A20u|mDj`9=*!r(w3qg}K5y0sDW=-R9nRG7^o@1+}pq34_hb1@0d{(}$`W};+hiT);c_RVt5p$jP%YkRondd#YW70@)rb!>AeNIq*md9N30en zX8v&}BIAO6v69;2uDk9+N=k~zU%vR_3xtMx=Mt<<_V3@1>C>l+_lcWujvhVQx29ph zBIxeB@5Z4+htRHFJG}hz%Loo`p3ma6Y15FEl_i=^@qJ;mN)NWXTj3Y?X?VN)9;iHS z(N#cC0`RYrP`t9Y3wneXVq%*Eq?x#0o=TFAf)is4-`!qLAnEh(}^m zkx?qT7&e{@66seP&}zjaFy?NCTd5UIgdch25j^m~17aJABoLI8lpr-V6^V(7|Iy;W zi{E$-*C_DW?gBiK5sZ8f+rGXvN^f6Y@$R|3`4LBiEA05VZ8^?s5ds^DN8Y;HZzJub z;)&d~Y82$=AUii73Y7*Ck&%drj)c=rVw28Un4bq-P$*1#ov=yyTpA4zV&yt zC&D7Q)kGXDB;vSvrberQ%VtJ$>LF7>QEKZ+Q^5{JeaaLc^d_D}`6pWw)ZJxC{}|QKBA7 ztJlL$;x#=Z6Be5TVUbaYkBx@Q$peP*1xx-5?pl(bo{rY7Ti4hw;@_d(c=FsxdNL^q zO|X5icz|6Vw2ac=_5oqu$XX^Zy#z}?`v-bCkW8JSJJvhixw?{ zo{S3~Jv1UB0Wd`(QBC5+)DHbDaBdQIoMVhg>Q3WNh^6ro^^N> zu#=%SzU@JbipdeiS7Uy?MJ~l<0uLuj{~MDSlt>yX{v~IsR+<{Hccp!&`)kJchHC8oiCQlgnWjql5;9MxbM-PUz6S zEh0jLVY667vr^8pqGEZ#DiVV}am(l8`Tuyd;x_Htqf6(`h>Hpn&KwqRoJ~0FJeZXZ z9XfVKs}}KMIV>U)CWDqlvPZOi#sf|Ttd zo{u*wCR&I=7o~&KqSopW7au1?2yeq_R%-MjP2IY6L3~V<5QBV7N!?Jdg0Nqb0S^ud z2P){H@x?V6eAdIGLnF~Tyl!0Hi$B?jtV2bjDWjyWP(W|g;P>x$V)LqGUz|iDsSnt& z$jQkOHT*4GwnThF0t}`QoEct(nBZc3la+vMn;u$)M>G#rknp>wWh!(!3+87hVL^5r zLR3z#r;r4rijFrVG@mUmR*>E<0OC9+7Kq+$POwoWcphAjoyx+a44}jDHECN`2{r`T z*sJf#!jrjo5=mo$-zI*Xc_h(x(IFVqJ{p&Hip8ZJ<1oFm62YEQOl+&f|2oBq_%ZEc z&^|Imj6ll!ozmOrdzX0{2p+(N0}DZxe*X8|>2Sd1qL`!Jd+twIc^@y{ZFp1fR7bRq z_GeEii$MP5K9BK9NeB)$h-3459&F9KpSP;XMceZBVwnQxgS;J!L4O<{i{p5mKaQt) z;&a+;{_}d*N$Q4rR}Rz1#33$DCmLcml|n)o0d@~A>luQ32SxZ|YnN?*9Ky4lUpP+h|L6}O3>_tu{%FFEl@hmz(^ad9#7^74F3+(5!3YG=)6^D&mP#bOb`Pf2O1 z?9+Gxc+}CgYuAdtFnm9Gbi8nl16Ral!cJmvW_l}Oh;iLKHxE@S+<2wqZp4x(oSoJh zpJpb}OYMP4P50U3hD{TKYx+gu=0Rad2vK{58Fw5aBg_AD@h9i|BJeUtlEDMJH=Ysk zzHN?%j~P$L9@#wv;YQy%IG)lTq*r2Ow-B_7(1`fT$MGLY#Bn$}p1vF%#~g|GeLo!M zi}!AJ_ckzrPEz~SYac>Ci*}abk}uM^>=m8r8#Cf|(siilI#M2Qhon%AZ%ys;?6q^T z>+cL`baE2<`hX2LyWNTFA3qDDZ|UcYl?0l`-iL6>^5vNG_19$ByD)U<5ZrOcU0Ahd z4L+JNL-hOOc@YN>9)$m0HWo^yO3cYrQe24JZ@&{;ckaU5Z@i8do}Y}^xCGx3y*0VA z522R_AAArC7A&CrSA|P1xdi8&a}Lbh6p5sVQR&9b+k2wWs>cm24`FOw) zFxRfbN9irFsVEXX!wNAnG6y!b3Ac~x3!6tT%BL+k<+%0ZEIcwM7XPGK@f($0VhSQ| zg-UsVfCqIU!>?xx1IBdK;Du!+-mVc08-l!!drB{N0wycjh_oSvEJh_TwZeEb93Nm4wd8m=cGlEHioX{@G&@wx8+Yt z_c7aY9Dn6~INki6_Z8dFb%}Mf>^~13i_3B0>GYq&7tfywBy68PCm46n&7=FmO;~Ar z?^ee}9OWeY5OPHT_al|%VB>}R?{URWeNde~g#QIUv-|||WAA?rnhM@`i$`^q9=wJ^ zAo>tC2tmlawkjXvzs(UtT{q?jOw{aBn0HeW#`dV2iRd@4I*6yQnv1YV94+fqw0AGJI7rVA?#e@kHk&~B) z2OoYI=bbqa509IG&*>Tq3-a*5LyypT$KbBpZp4^N#`=!tMGC?r_ywWCU_c1ngNGh^ z5L$Xx*n@9Sxv-@)1kdg5BDQ^{%XV}MDu>xcqJ-dPq?~=##AeMwb0|7c0nj50r%BEC9sh|e3wMrrQLP7 zb3q=K?q%<<0vC4=M*AoY7HloS{v3x8id@{WAiS9b;j0%#(eV`so3syRE3fe2_xoE3 z@8NrY6l2=5e6&mQPTQo{DluUrLSA84l55z>SJ%X?)qXMgG8y=)at5M*_)52h&&v{!)kyU6%9G!#bA8Qe&MIxnt z4&9eRI@d#IM&OOn5g7AXCZ)%QLOSn>$}M4}oTMORG5F6v{|Ey~(zxK?EG;b!ZQ8UE zl{xi6pafx4#xEPB7;HE+DM83QuqYdE|B??F8=sd;+(fPsb$BrSvRFJgI8wydHsvK2 zOt^R!^0Ugw@bQfoE~z)T$>@k{dg1Pu&iBPg0!d>Rgcpn+g?nzl6{E*oN=x26Zq7H~ z;NXG1cw*vHVtN0;R7{*O4zs@e91Fi+jD36e!cL;EU8^MAbn|Uuo60n1LCA(XH75pLY?izVKXBSV)i&>?NF^9fw(Itx3e#@lczC7#LbC2AENhAmohv z&KL`T zoq~{6YQwFx?R<*s9;?P|+UB*@g?Rgt1eoZs>(lJGbwChqn3IkbdmT`ah+;u_$B=Lg z?`$C5sKmyUGQ6_12!p$Y;FVEf_&vpr!g3F$F3Z83TU#Qf(1ErQ8uU(7q0`h<5}qMg za9t9<*;0hq5Iusm4xC2u*9?lpMIBApnrXvx=SSh<&r|X9K07j>YKK`HiqXHd5j|qm zX!r6#JaTR{hLG4?N+R!(vm$Ww>_ga|>q6?p_V{IA1qw*GjqM(U_31Y3$+O|6(?ZbZ z<^4!0a%0m&t?~CEGt5-}-PAt>iO=oBg+0SC@2UjMqWjY-T#X=|3#Y%8ju~SUa6@ks z-dJ0Nd0UI|cdGN>6Yy7oC|~6$qs;=&-&v5Ib=FzFn7Ty@LP=w!(T_5EG}A}N;;gP@ z$k@4mfhYnR1tO)KUMkcljEJf;G06P*`DSD$7eT|L$4TlPW3Ub@7i`Dg&6&O!iR1_m z-2V^>z;E#o8L_)|Zudr#(5#%_B_$=oU^F5xKVM7${P%{9NZGfiN-@~j;r@}6Cr`%8 zl`DNQfq})lngrg;OdYnHLqwBM#zMkxR7@@|OG+b=t;U z=iMibQc&*1Eg-9+e$D{LdV9;qWsI>tft}DX)Ev49-=fr3dkD@Q44|bBVC-ZYU#m!qZ^T29(BozRku5e-`2MjU~{kH3-+Mz;i+G$SK9uUuEK!`I!hc z=@1;E!Pm6!6F=tR>rEwCoovC7mMTP>R4B68Fz(wdOkG)oz4>J*w|a0@uOKYgT7+-6 zmg288D=wpD9)doj!i8&QXW-g7nedR&ao2(j+&n7-Kkg%O9IHi-cs=^WtMMLP+w6bJ zU~wrhw6%`zFOX4c$Mqkk;M%!aXc?-4%j3dJD+*9-v*NM&SwakoHgyt-4}ZV3`hxRf z8Dwb8p{6t_4+sv;?h~LQ^~91 zz8sPUfwjVpZUbB3*;!ZmVkCj0Da=Edn_qyhzy2C)e_x3iAI!jStA59x-P`fx6Hklv zjK~zxQ&f@TzGa~T*rBP@%WiQ^lrZraVF2nmI2*V>a zn14eO3Q0JXlCV0ftqYxA&A|Dc!|~L4dJK9k3w_(0@XPH<_4$n8az2PJtJH&O_hUCOkPb9DOJ6LwKSQsS{!m z`}`rai8SH&yCMXojZ5Z^Deu6NE#1p zpKxoN-o1OnWHR|;t4D(qgp9-K!OinB@zvTQz@!pYrwvgSid*NRFC+*jUl5Ha&x;nd zymiHtn-^f~+7xJXRH)XnaBhhAzq$nEK|cw&g1}~V;?60YkuOq(OmI`z;vS!HA`|BqC|@ z9(-_3E9}j(;ORwq7}X~fGcSoi;!_9kztbb}>V+nBek}tdJB8qd^Fz`9%|l2C*JIs% zEphp0=~%O^0>RO0;T*KmIam;}AiR5M1V(i+;G*fNn0j3cJUS#CbN(6S(-JH9 z6l2o7eEjxA2W(2s#hmR*%(*5OAFeM!dWi#bHkFYk_Tb>ec6f40CT6cKhaprA9?P~( zgci&0Y=Mqb_KQl#vKL4YPTU0^=yuKxiCD6u5Np$1I555yLZ04-vLZJkqctcfvg3+@ z5qSEn2=sV%FQSr25Iz=%sL82l8)?M)`w|fO%t7&6aL?-#m0wa2HXrI+5Hg&n2FNs) zNa_?GP_3qVph3EoG-?HAU7d(~hDCWJoLpoyEWBdSo2n15jK|Z%>Q)Ty+K`TaR+AXi za=*daIkA$CtD?Vrl`i_{@VFaoa8lq4;p^e+s#WOlJQw6QZ~X~Nh20k^Y3}3|m*TeD zZ^OqQeT1io`Vw>D#c=-z7WcpY`YWD%@=4JbB4Ci%*;*Qg=k|3#xl0AT z(hV(z3i@vI_=-Rw1UZQRUMP>}K7=1$o`@~u+G6%KiFkcwE{Y0)AGep|j?*LX$#scX zchHOis|qU~X^R&|M!`%XPoq*`OSToE20b=C+!_@&J6`^+5MN)Lgk|GeW98i~aYjp> zw?dJjliH}9E3;7==$v0}C`PW?fe-&K6{DCssH`E`h(mdH?9F#!@olZ~@nvza(e@&L zrnDCGAT;#*^E4*h)RoIoZo+wO|0oiJ?a?hphcdedsU+4~hpRE9l>yiF4n^`)tr5b_ zQ@_o{w9!deF|G}EjBkhYJLq7syHQ->;=_t#=g@chsRX5TECoHh2Q#ghy0#QM#$`_c;MwbvZE2NFpGf%7HKM`e(q zv4vA{4mC&*VP!t2g7Kjra!_3CY!D-misF|GFe>O;Q33n$74cN4)yzFxQz}ZWc=hh@ zv145t45q+Ode4H1|K+-hk?PDtjLBF38QIU~i_^en#(}-4Ij7v=cl!UqV8HxJT?E zYv=dcBHEzHOurB5VgGgONkkfG-y$o; z)Bg}E?@IYQox`YB@?f4Q6U!d74c8*8%t4zegr||4pz^-OR4#G87Dj0-BEiNhV?s1y z28k>ZbKC^>so|06m#D@i?`P1psNi|ME4sb0A3IZQ2ntann$l-+c#vJKjM73&Cn_*IMblX=tqfJA499aLq7co)#?>*4Kiz=OURVjeQFFqA^km^n{fa=N(wf%gft77Lu(^xs76d+~42o3*C&Z ze=W>|lCO#C4z6J0jS$UI%FKU$ek8gk5#wR{_KQq|hxz@RB((+-I z=qG7p(|F9L?((HRx;)xLFpoxNVHoLFXS_I^5fZ5O;;duaQE}h1AbC>`;_H0LnLO$y zixr*7Kuy~3TijpCp9%Qsj}{N-{LjTCA8;`dt*9RDZjXu+7q>*^1y<8wtfuB#!);u*;GYQ$Pc>)M@Scp zjer2dCEN>>q_tz2F;4-Zn@Ebjz2BT5*v`C79M+u0vq z584}1@QhCwgxWq>oYQ8|SPaJY2HdaFg`JIDY=k6&qMRiMGita&*{7BmR++P*rbUPx z;&w}hPQ`cvoqED+d4zv38si_90Vg)Vlm{?~jF@$)_lXD0vNth&+dYmb^_S+tWT@a@ zQ}uJc-T7hssAddnT@q&uH`XU@`i}IGb*dvP2XkFeBIqF{t1l5hE_P=7Hpbk=CjlK_ zFLnWoGw+c=G^i1#?Sn7enk60qq8^62@D=+IB1d;uK!h z97nBRN1qYgan}MjYCGIzR8P*m-z;0M#ZzAm-{Oq@O>g8mFY%8#uJkCw#+UtGBPr-e;RBPG9^{C>({l*4$M7)Ji-rgm%=)$FO%OPI+%o1{P%hn4bz)T@L%G9THby zk*rByE8|82N=GH(wL;FkvdYv-XnYUn->g&Swb9)PE5aV_ZxQ~@j;ZHlU)=opAuowjwJdQefE83eUyvDhw%}YLi;a0@k$E;Q+8a3GomBBACQI7l;8sc zaTEeQ=Z05a@uHIhQVJERZzF>+?E*JWJO%X|?La^v^Sm4~(0z^qKz{EX^;O}+{gq_JKFpY31$Jd3%xCIIxsW|#j%p_l$!dS+BQ28{2a!l`O zY)hdEym1RV?8e3%kLSIqEOsDZkzfK=%J^G>l1bLP5MuJ#Bsl7Gt4~wWkG5 zO9Is_Zv{Ph9UVu(Ji6{)_P;w>5ma)CKpaM)N!aC=BtOkAhql+iiFi*mU(4h^SbxSb z8)$RUPE>-Rrf)x7TZp9gI*7|}A>!1BQkxSle&kcfQmKa0{*gher za=7B`j!sfb_G$DYbPJvA&fhy?*ZoDeMw5YftA5yv%2O_B8l^_L+wMA&Ppx);* zzIFnM6JSmor;>}5BQ6bFA?oL-Pd{&meNem=gqP6Ue%=Yd?Q+NI5Kyq23kJ?BcaExZybHi5|lp$y^j&1s1sc{Em2QUk_*nD zHl8(l?a%oJK5Grt$eU_Xr_apHu)FVn>bPI`Tk7;{K5REMno|(=#9;+z8MK=Q+(lSQ z`CYTuyt?#jU$8OBBU|e@48@X!)^XipZ0L7)s&TG_4CI#-La%t8e9eKAqn=eQ(Jr9p zgT0FYM@#F0J_% zcQR)_?ZA~t&p^lEmSz_9=v?Dfxghl!YUorVl@58V+Z#7v(ut{X6mi^p|G0Jd6Kk0E z2K#fm_I~$|96rK3o(!!emyN$Bl$F z5dREtzQah={cy^XcY5!$rL}?HD$pTO1&-WJ20C0;!^m&n@au@-vC^1w-Q3*=>iFY? zI#FhU?A}-sLG4IsQM&J*5*3KO9czmg&PNG{#GV{7%{i=BxOXo}VK~7VFS%kk!WDv7 z2Ntf!eEo-lo5%6j1!^RjeA&%4tafp@aJ)7B8Z})BrN%yJYX-8twyc9GJ7mEu#VE9* z!l<*kKIljBHvSLmSxfKtABEnpKCNEwa^f#_l2r1v`-^_Z5B}yUfe4lXD>etjj8`NV zaAvo$Z;`F$;mC;}79olcR~mJ|&7PS+hsrb2dN~hgSCa4Q7z12VF9ljsKQ9pn&9Ckd z{61HftKK_tT~&m%>gWn@NdG&+1rfxDeGF`Ej~M)ZQgerbw8X`OznR?3D6-uSXp`Yu z1;3!Q23-N5(Z3Mqt$^6*XbHf67_d%smXeiyZn|=#6fdI^=d&NuEzVpl)L5c7oV!D@ zNu^EYcgxp!`a!QjZ|0RMQ8g2F+KdwzS~8MAp|F9W^W{5AC?lk`4#u*H!XFuxNypo2 zt*ZI0&A~M9t2D36lZJ$;3c&n2@R8u9o*yqk^c5S{!#S?9(qqYxvEh>f*cdZG!}t~6 zOUWPcvxhw$b%jh-Sll{=js}TPP3CkZK-_Y=5o~55_S6xx8>6P1+>8+@Sj?<4Yxc>i z<-)lk2rKO~XRndaa19WxqxxQ;uw4mNN<6~n} zGh+B)?;x?Zd_WVvK@UeG3yj4V((70k`6sg2GMk5?Bz4W=$8q12 zrdxYXTbJ8>a7+&ZL&br^h{rsC{}D!vNj?u=RulWXB&iqhEb&uQO%Ij%#f=vCwF_#8 zR#~c>m1V2oy3*j1Q~SLOgYmRG2q|*W63dg?~ySLNK$ zfZY(a4426Ls=G=n3{ggrp(i7bz5`Qs#KsJ5(Qma#%x4PyBB_E~@@VFWM5|@!(iH`s zjOJ`DhMVD!&R{f#`+}+D=#PE^hNMh4)T1|+aXN~)GZc*iEN}XafPC~d_}wT7ZQ^7_ zt>HnovkjE_*A96Rk0UDPQ!_2m$oQ43}gBJ_8MUD-*r9% z@LbKv5BRlG@INuVV=vl^ChljchTc350wjPu_Fhy$;$C94oB zC>>ha9FM{h$;Ymz7M}|=x}ncIor?=qC1FAoN_t*4-Ud(d7Q}0=C!V#9jpKh!iV%Iy z_4%Qj{@JKOv<=0sFPdJM6cH;z z5*czp$rj*nE(gUm&;pMbK=|hE5<`TNR;ABqf$~7*C!pYXR7hYu?*bCa*NG6@QZrM0 z(~)D__)w&fz){!p6`p1y&N}I6<%PO-<>XNGMj}?AR1|me5!$}FB-)lHKHb|es!G$| zbz!QvcAunYCQnDaVA>Z{=u8v^yx?VIyg#AmzBw>g2i!1E7cAL%Cb8eZClp*5k{q1w z#~xbJsya?wtf{?6(A%xiz&bN`5=hE=fmXyb>p%y50&eGj1ry((RuOH!$Q&Zm+91n# zYZ4akr4|$b`u(nkfbn}pOQ`km>h1t}U(bRPZ~AsV{wR2{A{5<4qrMgKPE^w2^+?sM166MjN8{Mf*2 z9rO{)p65JtB)_OnPM-b}sN}wmjE;fzZs#n zC3uc{{H006_ki@JLQlm;v>N)Af9!$El`{b4GkDY9p)Zd==?>_Y~Rxof`+l zFK$&euY-YWIli#4kg?|05)y54yf0Rf8U-b@h+BL&fzkGZi>P?%iiC7GVGfuLJQapO zjd8kUbOD8$Uinr3XZV;!>1O`t^b@A118N9_63DtAx%kV+-1S64a%?6Ejxg6YENE^; z^e#jYrRXL~?4o zSG%zziCz5B@io<-)Jk*oLlhoAn%P&qdP{_>>udY&N0L24yEuh8>N@X%F5TMg=JKF!`oRC)8AydjZNy*+3i$A2HEt*;LRI~}_Vt(3nt-9l zv~<_E!GWlK3QZ=DC6gA{0 zLKq^z_c-F_3sNRq3`y7K6eqxY{^Hj|vHrnWyhkZ{svNW~@+{zX!aAf#| zwV&p^J0s?UB;l2>A(nc>XsX3UkP$&D}%_VZ?d2YgI8fhLQy0Nka~ z7w})-nOgYkfGU+^$<+D?RiKY7Sx`^Di>$Wg>V}2L07JyT{*w0Ffcrj9J<5s!o`_Zn zX#39&QGImLA>oC2pFMg#xbwc>(&q1AdB3i?W_>&e5y8}kp0rs5(3a4B^ zR!Kr@$O$qyNHRf2rTgcTi!~jJp8RXiB#FFuLd@Hn!ATwd&4D;6>FG*y?$#IC^w*)X zKTtmE4-*=X!WV2hs9iYkoyVNR7eB&`JoX5tAT_t=b!sar=HVc?X5ke;L==QL=~aSFgBSP zbQ@G98Nik-+~MP&4}e*E5^yZWC5EWZB+4l(;|8?oy=aAJJhm=3Ij%57N#Gh*={I6( zG(k&XUvXOfTCVlIJ?6^$LnEdk6h`NPStNvY-D#_Zrhwt67Px=VVJgsb7KVhBR^ye?_30vHX%C9wf zp#_Y_X)Xf(F4p9o%cpXA@u5Y>$BP)MQH3JMQCwUT5)mO4O&|ItS0vn^!+sOh+bT|S&G5ajQTiX!zZj?S3TjcmuZ|7#Jsnt`)MI~VrdDj*Iub$=&V?k1Q zbrCkCwNd(mu;r?Nm5H1>xt#B`Vb*0YO($9;aJ6*o-4K=$I=MVPsGLGo^NRyPIjUji zK$=kVs#^?pC;+o8QwfJb>}v{#38_LX>Y@^I$zkg@qTEyb9%UMg%rce{CyioPBDo3J zrHe=0PXA-MMc_fE#n!9Q(I%Ho($EN+E!ry8>|Y@D@NdRCO=G$2wQtT}NrdJ*QS$TP z;0as0SZ}aRIh54UsEPeI7$fcHT`?v>uQ=Af_)T*q-qs+GzU)$_Flj(alg}RD2|_R~ zWN``U>;cz#^!23Z1|AgxbljTc@HuLR{S`?v7f%cGRyD?p9#%0qGJ7c_{IcA^pV2lR zfk{B`U#Z`S!JhL3Jwn%C^i|nRoXF^-a4@m!P2XRP!rC5!C_`Dus{N64do}bcrcGDV z#5O!@e^1`Fyk&56;n6RfY0A{}^o0kXG+?3Hcn{xm+b={uPdu6_A@PRa?XW+Ebv8gJBhIaqHHpF)pmHpp_HHHw7%$nzo{sB+%u>iVV;vC;Q^}%NcyYQzC3zhPg_-7*|{50P)ar#0UO!?XTvNE|yaokT1n>lu-5O{L` zPH{*RGM%&94?q#ZUF230+np=}OF-yb_R_I{%>Yjm(sIb&@wl7wH8hq)#VC?8(8m64 zGgrWPl3d8MVV*ia;s-Y|d}RS0z-A|_zC4(wCSnYBSRWz%lq;)5nKVc8#`}$_bsiCo zV1d~E#E+zf*N7592tX!KXN3!Bp1%8v9~BINcgz(Bh`xW~8C>imd;9BF6xxIWOVrk@ zl|-&mgk)%C+_uz)C8Qs-BvX`zOMYA9Gj0l!Jg1$pY^AFycOJ<64A36cEcRvj8d z9sy8tz^3k(_4pNVo-5(u`pDE5cTei<{8-aRUMcT|S!wWQ)(eI4QD7d`**>7exzkNm z)xfpPchnP8h>@I`0#06agxs@U5bk$+Lv!NT-f&Wzl6c8TMtI*6T!AtM8%Rtp#0i;% zJen&)9PAFOv}YaOLx!-jPjZ?=hzXlEvmb62nsLCI*5km$hCM$&!kD>#g&Q|Zu*rYs z&t(>pOAD%{Bc+cRD3F@b+@dWM^549Um}@bsz!ly9u&hnvnd-w%B@H5Kwsm1a=&x&V zx}arYU9BE!eq^K|=Z2^kYWcWAMxCr7jwmtGy~#X^fVbyC+OXJfQC0^hL-1@7dxRW| zK>EpSiXlSrkT2kiCuW^r9T+E)8Pr$S#M~R7S*BY{KHL3>gK%heFoT!hjn{Te6*AlKu|H2{yKvB`A9%EL-T_ zX8B*0&Dr@b&b-|)paU=Q7z+$T|Cru0=u}C$> z;vFxwq?dle@AzOrO%|m$nQ0c7u2)`IY?c0 zd7WtLAj}5e9sy_ex0d|H_Xk1%rL93J7ShKwZ$>Sq?^$nN20wR-G|SD{@l6@nB+b+i zq{|8xkLU?lnZIAt;IbMZfU*_U)KP%67EOdq=`QE>QP#6tyl+9I7|OIrH%>q$r|}Bx z%I{c#;<{wH88~>j+!?T`KEbKH_7-1&jrcGykdRwUF6<1m>l*XtjwQcRCWfl zoO-L8S3{U;Ita(!mLLJwF5#8;-X!~wxAzk>j*uPcsKs8Rs4Zp zE8yPf|8!JJX-sK*a)DJ{Qn^>DI!ecwi6Zm{;jaG;5wCfMl36Z_G6s&m+nz7oqJd&k$3#>rnMpUO!Nxl(g^j{kHW`KMlm6pZRK|f(_gkYGj771y8vaiFr zThiK$fUAMzKu~CCyPAcF-D_g;TlaX47JqDzM#DWGM;y}c-Anov) z`rCSrSqS!mQd$zFgl3vR7;I$WZn5}9(EWMVx8y#+(>Ha=R$D?khAwZ`J73i<@f&{V z?rGM=xR+=lct1qFBv_^*T-KY6KM(K#(Xd$NCEFU{wI;8ai#~Z>xwxvRq2R7r{R?5K zvIAazxTqjQC$s}!Nnc-%Dzcf4R?GsnXGXd*e`!^G(wf)+pGAmk(IBD}lg#)~TmGf` zK%CsH8|1)OcOnmgD!$TUS1O?XWrul$iQp{X4`;7G+yDge!M8#0NB=CP<8W}9m|u=H z=3pSZn@??Cz_pCJzw1pam6DT_FP1D4%vM`my&twBE~g~XqJZ>k8lf7QqDvRznxNez zS$5#TT$d*`gNU zu%P2z?Vg%2F}09vTqnfA&H2}aAKkGLqy!P?=~Y0mG@Y58Y>~fs>HKCXjLBwbtLV-; z!sO<*`()3idpMQ4Gadgby@q!w2}$ zxTg~pdBWT%OOkD$&qY=Phe+Jit$;l0zSqMgMeQ`3Z4q|gPUFa`(U7t3%6U7qni@8~ zHQE$;LrE>$-7cRKVIjix1dxj(61d!y;_&{{!a-+>+%e>Yp2rJfH1ptd$9=4(WQpcx zdN<>gtq;S~i+*_23d?X~TLE1iPh%;Bj8kNP)@)z_HRYrImv!9cF;M<6U##CY|0jGe zv=AH~|BL+!?}KZf3;MaKqno8xS8kD)CpbgjF}_%mKv+XhA_*V`oQ+)b!Lu`YX>!r}$cmfpBsneT^TVdI1BH zU7&3(M@S^3`#T8ZUtm22(znsz9d9Eo^9oioaU22yhVQ1@%49>=hdC!vg1Hfi%@q~F zR*Y2A`NjOb@m-!WJ;gsdz)C*R+QDg(N)HDerpStOJ$Bcdc@lWWZ$lx1JbtAi2#b&i zz8hjXPH0z$&LXa$9gOcm%f$sr;fvzwuqGxE zU-2khyR_PJ0mh(LScxEMzqfd8yOp!qfFdy%1j|#VY#2LH?*p-Gp0o85QKkT<;o9TS z+#|`P7;}}Fn-rBgCkY?#6cxit2$6Om*DWo-1E_4QCxH40^s}ZDHb;gDnIcL8KV)lb zOSjfE_?xaCWfGT&_f*F1sQMC=7D)pjkF(k#vR))hA zyaNk2o5ZZyr*Fl;h>901fRAbm6*t7?V%bfn3qeb+>_h?2d9Wl$8fU)4UrcFAq|0gr zrBc-W^dM#s710R+yT>V2?`)^oj$Ou2{x7bvK{skbKzZD98_qO<&##Wg69`~fgDzx5 zcVVuMztjUxrcTBQI0|vCn)3Xw5Y#m^0)V*oVve3r@B>^NKPmH%7HKgD2WI_dCzgH+ zKUjWI2RRpGY!VU{De_Vd09h%1?uKSM*U;5HGyr;ajS%-_mSabm+Nc-9?AGd$Dkru1 zAtHhiWwmD~kr6m0}YzO0Q*Ce7Y7wKo-ETZ}qyplb~v&QzV<0YqL>@MXAKd`ZspShE zY65RP_W1mLJ6IMN090J z3Zp|eD)tke*_0c)XtRIwzuNAe)BVYXF$>@#NR#_c#FGFCC9&$=#vLUALacB%I(&<1 z-q@@+q)Q%mddc4Dk7=(u{B#-fv2#0V-R?5T8@qG%JxA#k$%)YpIZk4-`n2vYgN*%g zS>AhREukRcZ`dW;dJKCJtU#!EES#B_ugm!9eUZsu7v@JZ3dGUMT0uj6-!m8dRL_R< z1tNJ4vj|zH{U7@z4AoqYM1UPfm^2g5X86k4K>nFy0ZPn}MS=*TlC*%a_Zh<{?`?$N zPy2qUT71%Z^71>fz?MNILK@3pb9QOmLO~&&+gdaU!od;;h!lem2hM>QIC6G7-x?uG zlj4M`|8-{@;dI8a69ZG6Zjvh^tW>8O#0gkIHk8U8EUp~ZEJRN z?$-ybiY90=j>G27rB`xGca}4LM7wuAjcRt>J!u+pTjLXZw%>n9mH{$$$5R26ft-m+ z(Yu#0G+4vrA?qMZus7!6#Y9K{q zvH&0}2`~A&&6)^CmCGh)>n^W^CtV^!6UjX?4EOKvr&`K-m);brV1~FbVJMlq==%x! z!nJ}j>h!hoJ2y}xun4P2)uyN6kx+iTww~uJc03bSp~7}H*Kh3Y`=HHJifSfTm1;3v z{;el7Ta?aMNtmdF9Pni3YCSKQw=1x;#~>g8sg9oWO>9v1GUywX^|t|N0sa?#fv%t+ zd5eGZISR-Z%$irwV#JevGE`_O%|l;Kbq9TR@(=P3*>hZJdi*a$ErO}7h5rSJ;Dp5G zaaxT8tn&~d@N8NpsmQv^uNSB<>HzYE^g2zFoE@H-==6pgrr%nu6Z4tfK-VmbuHSc* zF?sUp!@oV1XRL;J2jF3PydNGOW@EP6hQvo9nK0%km)@)9<#?sPj(^(Xb9C91SVc4v zvr7RkrPWelY7BQ)D-nN-+U`#yNY@0wX3f2O0v|!2tRDE*-Di9^IB1lh07?YI3CYc8 zbYpB6hN^6qhx9Bm|5K+DCCmIYv!duYrYNZyK3yQg#b$rphnP19S;|yhUA@+APvKHn z_1m~Lkhsj1Z@XtvWztRhe1CqQBMkr@Fxm-_AXXSIOBoMNA9nJkI1ySr)+mqd&Un<{ zNBjM+m#Bwnq?*|w_&0rN3-h+SR$dIQr&V6&CuQ#aJqCeHQ)ezaA&NEqjTvzveS;d7 z#P9j*?zz5yXi)x9I2eYM^((((S+`)n=U{Lt@hMV^BO@7g>-pCpfA-g|@Is{ke)EM{ z(!hA<Ene?AnnDyH%HO$hyy ztE+3=MBgBBX(mehAGuN~N+7pm|MIWb3DL0d>&wP5S*ENU<9}~gg#<#H?`tkh#Km8~ ztI1GQ&x72?A=>WB1?9$KH7ITUFZ{+JfBta4Dk&*JNH8_(eD}{yPR6wS{CTkqbM(j~ znk&x+RR&-|aKGKhZ;%19tWR5-YEyk==!#4SRZx?5|XX(ICtqf)Y7g8CWm~{U7+ajuXVe5*LjSbDH&EsdC5w$P?rPZRFyK0&)eaI?L z>`KwKAAKx954Bs;8W`q$*=v8($IJyT{|R^m1PmNf=6L+KN_kV=>`Sk=T?<$^xR?jA z-hj6j^NBp;cc0gd3L@Q(D>|Kpi-1HqYC1F(mf3nMMGh+@m3i|2;Qzx#7X1eFNoS=b zQt*daKH;z~IWnO?KmIsMNDRQ+99|#(#8<{CDf~_

paC55m_>ryRNvbU22jABe5< zAoZmZLcEk+B|4%I5}5%QxsfiMlQmsW3S4=WN52BNnCrK0|HKQUjTnylcZ1L(kYIp%>=s_m#2~1Xgb-XzN6{~GIG%jBk7@* zvJ&~0EZ}iLAi!b<6JmnsB$QzJ@)ps1+{qC1y&FKoNJgm5JPFl$TkU~(bBu@{LD+Gc7xMZN@{S)^Q}iD* zlup7I9_o|DCHngHruT1yCtl`U(-v=5;-?_469&uk{6UgW6;L`}o0s$$#iO!e`{X3= z-rUXa$uIj z;r;0E&en+76SBwx*9ARKA^~b}AD;X1#RvB5gGoFqSW%1Mkj~=1a1_Z3?J9qcMxZ~V zP#mF3X`B5IN+;|hzu8=y4C)V<^gqNB(~9X0>bstSo-z0#Ra=hF(wIrjThXP0DDWAy z5-GXR)SyZflF0UlEMCH}91@m=Ajr&Wst{S&_bxr877?i1xTL(*?|`1%Vl=ts`N~mg z-dPO)mlpY_5E!KXYk(&=EaCoeu~ldyZ$tr8P+W&35jf=Ig6ibSk_5CU45026YY>9n zpXCT)-0^A#0#qicxi=v-E&b``UtMO0cVPbjoy(|@FoS%^D6u_xAzv^T<^k%z)C7we zg7P+NWx@dc6T%Z#1^7 zg`ZF`GqT9mn4yuMs^XeXJ)8f9NWd&8r=+%>eV>)I@gz#JvsXbnp+Ghq_i1!tz^Iz+ z`&K}5F)ceI>CN@uVg-7kz>pqC44zT!+R}NmmlqdPPdDpO+9KEPZyuz#!_UpJmS^J7 z+oo0;btBpSq=wDcl>f}qy67xUkD zNA0%2JC9HApwM$$dS14`tndHr(2kcQw~dq&Y$llQ{_IB|3!B@xgh$2Cfp|ElY@>CR z`!DDcc;rB;`!Iskku&L_$#^$3h|E;paQ8*eKvX+)e|pGcolt`@K<`dGhXJZK}+FuuzcJVA=3F0$nZc^;(gNe(QPSLJ}w#Pp7*dt zi$GTHxs?Ckh=g#)s+a_!SG70Ls@Yt2-3Fu5$#e4vb7boQcPnvOzL-N|CUgPlqs#J~ z_J6}Mg3%IHre7eTAPsT0{D5+{CWI#K`3oIH2qF^SGTKzna;arU+Bkb7A}ikC&e>&7 zSdpcmMuLhJJ+0EZCNvuz(7pG~fO0?XQaN>!LNi9iMNDOcl5ZC1^A2JYBr6 zYLt2$5_k;|uwWHlENDJ1mermI@{ntftw~$V0C~v&xbsD{|W$ zT2fdvubaJ6vn@B9DqFQ7(GK_A+H)CM{BOW8N2U`giYT@k+nM3ueuI|ua^pmiG&Tz# zcI(xdRrBJ!WgfzE-er{NwpwFNO1o^UQI%i3*)}M=To0Y=KCC575yo~crPMD`$Kl%^@kp9e zO0YlOxOl{j%0@)0H=u~#PO%muM}-^qmvf?5jJ!;u7mbL0YtxMc@AUp{Kom4@u+<{% zBQrnrC(iXfR2Oo)hmp(Y(oE}0K{*AFP4Sn*mk#U|N!Mn6GVnk5)1yNgEeP|qy5H5 z#wqfSYxIoLyO&{%9Qq4CCP*IwElPtMLA>P8=XH&z)P~gHo8>zKL#Q zF42-8I~FjVV$}2ym>3n9vJY#ljfTwni-^ z!_GO?Gm)qiw%ptUJsiS^U7dL4B@!eZ{BB$s;fjP&PA!DI_qXg_Zb7dYMNHKn1L%k_ zLz&PW8ziL(et6+$exKj`-`=u)I$^2k$pb0#x#Q5wG4$uoSr(^ZC!_2HPhdwaeY-m( z+3n#T%)!TV>@_;o7cUYT(N-T4bBl8*s>*6E`ZCFRF%0Fk8KQ<~Hkx(5RglFR0pLrH z0|N2=VOf;X8EkLpZ|s+ywgS5w>5`!j{y#jM7DxgqGYnJbJ)I|j-6)l@qv=?IE|4c3<~C#K)pOqIAq4f^%%tFov+G|Lk+no=B!OD~Ty zOf06GkuJcypV{quKhie}{4tI~VanLbET!A3XuSsP@5wfQ9{eEh28%!Yo{~ACX4UUF z3%#FkKD;%5u{4=}EU}%V{@2gYeacoa)m?Q>2?b)oQQh}{_X62Utl-sFch|?g48Q_) zy$5)bp$<)E@uL0ncdo6fLC?BQ`}81$!o=i=IEWFYFNp=^~Mn1+^hH zq!CRJI4L=JCLt*J%c-)Mv^5_Q={!20cd_qBW@ciC3y^MV`%rgW zQ;dqipbVo3`IOwgxkZzq$Iv#j0Toe-?%xoK;yn8-54deD)qtD@@A3u<%hftBzV?s9 zy8SGHYT}m<&Ry3cbC<%d_b3)-YNPh4 zt^mKAzl-NC{`Y5VW?X~=B5AGyIrw}oP9D}DAzY~}3r^0=XyeSZ~Vsn(aD)aNkp_w$j84c98TcW=yom(_X%YGXZ28M%_rUcWf%k^m_+Fr0Fwu_#^WxqxW z`iv8~vA-`dSV_OdR#bi#;H+9B@{U-(X=P!P10=xnm2Q6h2M)m5W2m^-fv2dj5Zp}t zze%0B*hFR9B1F;0{{p{oG{xJ-O^nDpdcyNkXG=9&$|^NsRYUHj&Wfl_Z#r_(bIcf~ z?);UJ2N@AJl>4HE|L{Ry1K+9ZfTH7if$PSRB*wYx3Def6WaQFCdS6F0`7tM__v7yw_jsL-FctNqU-BFV&mXYaBxiHxkFF*zig+PxekZa zGhWjF-)AM7svW8`9L^{E2YJ$jC^{5S;AWhG~#iz8D*fNm6SLMqt9-o$2wE(yQVc#l_w$C+hfpxyaXUyc2{ zjGiq&s>4PsKT?pmc?`9bY zZh1`-6m@!O=^4^B`n~* zLBYJl=Pi466J0l2`1}0Sf-PswN!Hc14n2krOIPd-AOz{Qd$L$h=EeauA|m}NT)-5w z^!)HW94_^zzf(A8`6$%*e-w8BCJ>VIww!|l)~RU#)t{Pnveu% z?4WaBi%1#zaLo03w#fDKb2jy9C~*wCNHTYFz$<(v5Xrja$qn?x%=NpZvDBYyc^u3_ z$EggBaA2f)-N9Il$O)ow06kx>&Z=x)($N*nRRP54h?OHT8$)};z{hvhq(-O&(Kl#g ze=EW!vSFFX5qhCuL>wc$9OwRljGd6#cC&@gdE_k3@Z}0sZPZF|j}<(U@lK@2cwgVV zMc;m8pDmyYo$x7Q67*C}@c@`qXp&$JFL{cQ2x3M@La55*qJEKb{@B&w z4y%onK!j+7?gW6A=sr3oJ~U3s7K%ahfqe}tW!`YhTY_j-M&BXpPBbeK|yB1(YEsn3JP+z3qE%W z{^DfoyutLnIouLB?lz+sHjaq3AaAeMoAK!unVh6N}Hou8<7Sw z%0enN8`i;*SWbFYo{%|$RDmG;KLOB_sCkkFm)P593NA~hs&);L73w5_iK|443<~<; zUmD|D_t>qvG;|^iMsVghZkV)|#I<(SREs!?jfbyhg*J>pnz}>tpA-0)SaG1zolb0e z_CB&DV}g{VQtMPecBtkcxe*C0GiXNbxFMWYU}(JsDi%nv`}-Pz=SaZUv+vR%NuK=~ z+4zutrZCD9UILfX`fX>SQ}OXFVaKq^AroKeW))XLxesVF?Opxv<0xCj&D=*ELa8R< z?Ts-!tlW|<;Dr{b9Y69OS3keimZAE zk)2agW`V9lMetwDv$!6koEGI)%ptZP)e+>o50AP+>h5|+E#G!BY?xtN7eP{#kIt@( zc#Cc8iE^U=^hxuMzLo!YzK2*RU^NiT)yT}vB`B?yfm3tZaM-~rYsm?}XCl8vn)u zWxibNAF^X8vY4JkCA+;ll2RC`*Wq2)qEpTN60iU~Q~SBU0M-nVt=u%cJV26fFCaaU zdL1;9X;to;ktNCV$_@hw?Xe@l{SkYsGYCSH~nx><8nO4j)e6yOX1<*4E7f zibWECHhF4Zq?eA(1DV&S+ku`~oe=D>JwViyK@0CQ8U>ur84T+W$(DV80pjIQty!O8 z7aq{f7y~HRiojnMS~K~a4cB%@-BcT)_+gjmgKu|w$dr+@t8Y!%j0S9->%ZmX>;4N# zqWRUeKt-QAnFyo$d6~ye6p9sZ*-G;&uZMEi9FL`&0oVn}W1u+x<`2MTrwNqzqN%!7OoH`>37ba#Vvr_v>z(%mUYcZYPR z5=u9Slr$Xb(A}MfL-(PZyZzmnJ9GbWn9*@ybN1fPcRg!;Rgp*q;=?luNpe+)TE8`0w)UdzfxrqwjWZ|-2@;cm7zzlvz@;$3sMM=N zupZA6cXf4b;ZLvwbizBpCTAqeM7j8%QH|*vV6!qTe45IiaP-girx;yS;3jZF@pR&# ztxW)GZ|~bA(}4;rw)|~WWH9m(pvX&h_!8J1R1BE-?o~|1z4M!#K=CYJ09C2QusJov zPwYH%`of*e+Sj-Bh;aw5Nvei%`?So8{8E8zppTu6ZOj9Jn{I(``hgoEe6o$m-p_E1 zFATv|6an+j*S9M+4FOXGWV4n&-`;oj_x8JTYlVmZn(^m~M&QOi!sx7oQcX>GL0X?% z9%}({!I0fB(r_#k=5JWx4g5}{S)SiP^t;#keSTN`bMxlFJ(c%_o05i;d$o6HB1V!6 zhQs+5!!e+c-*NRNcqQn;R@cozZnH6G>h}CvQY%{9j9H=Jbo@m3X`|@2Eba44K*ii{ z?b+Bew9jD+%sV#_SyK4l$UHRmMr`4pRQM$!X$^Q^T<5lSzP^wG@+=kr>fYIjKHV0M zcoSqX+Q||22tV1ErCh2P`1!T#GoltG1C4;a4J$l9yZEr@hb4>wLRTk4cNthrx5uz)r zw^=9gS+0ARJ)+HrVJl17=xyeI2jev}@xhHTL!_!MAH30u`%i$lrfJx0u(;#-*qQ`w zZdPVatP&`Ov{wCr8dXA{(ibuwb!pRws(OChF#cWL>t+y3BZc4nr(r$VHt9zzeJp`@~=L+Elexk zBY{|lV=fg8rStyyoBaMR=P2dacET$qEgh{L^H(2j*!4+V)Qd$Z7s?43M|MQ}P!cf) z*qF(J^aj{Y_~VOb{6z=utMuyV59ccO1Y@MsT_Dz4>Fh@5qaP;nOgaO%a@t7lh6YYz z>2chuk6HScXQa=ygu*F1<7#1w#F-<}gdchNZL<5Q3`AJb+UGF-h*@-_B0A9GkS~l8 z?F}^Aw9qjG%F(iK3y1BXO&s+XP8xmndKU;cX2kw*b8>Xi!Yh2g{oVZT>Oxe-|0UP= zkO^2gfp;#Ql(YDQ{|)x#<>g>!0m@auK{q##)77ONH?unn*Z|PR`+NN%6~4*lg0$bj zL7A;%zT@@94@TaXYFxdFqE;w_4m^wccibG3FR>KH!#h9h`prHw;yP^|`0l356OxW) zKpZXY>s_khjdL&)aw7}&j@m&G2pE%6)PL%6QpA_BbjLi=F()e*-K1k;%~`LxZ>Qng zL}*r|s_TcITXPACx70K=9J-X=)0Gc>^0;5fMiCh^6!ME&!%n%uo6Vu#Dt5;Ni*T0&-eIj^H!n{kWTj6}pWkgCAJb znzXeR{|I-Dhabre#bb&MDCdf@?GCrAx5=S^UB3Xyr2OcyJTtSy>_ z#u$(SLMm1IMCF^k*pXv%xFz zam=PXynHJGlOIke)yC=o@D&9sF~Fnt2LliqAPOw6cY(@GU1v$p?vRiJz1Sv-Wa*E$ zeOqSTGeFnzzYXe++qE;xE-)|!$=`1d@Q?;P`2$maRWl|#Ey_p;)D)Wp4=)TrVw30O zP)tme4F}0c%&tNgajPe zYccT$yg2UTcN2&U*aUURa|Z6cs2m;ts?6defnE zFO9l68Lq|T)St+OOA-*hZ9yPEDC=P_7!&tp@57Ia{tZoV*B@*jBUVjbugo%y4~Jo zwHMWRCsNLzroBpH-RQ!nn)kcICm0w?SM!44uj{&=7IeA z!bhXq0;j|k$U%M=>4YfMo+^t6UfY5y;q)HWP4wxTQJ=18oUVyoTvR0 zvSP;zM#~RGf>wlV0_{r7^*}#_#>$7c8}`Cczw-s{AN&}8e>{na#`Nfa!X$*8M{D4% zqonrZwm)nnwzaKASopp4+I!aye{AKE8ygmnmpPg0ymh!cja8{V)1|%=4JP8; zHd|dUc%zi%IYiezQA=7fvJdQhYkAQQl(&Q2a zg%5*V`uX(mgZY4oWS{IPmLm0Oig^{4;3pA?xJ2cox$MH3O7re} z_r_V+EUq{Leuq7jtt9X6R_zsi<5B`EkdG2o4r`oRoM^NBn|dX999?WPbxHAjuvvb8 zy@Lzr9*O}eR^q!L|RQMlDDno)E|96m%KjT)eXxap_e7-sw5@1x`6? z$4l*xDOKM`S}-_LFL#!Vyo}j)JldT*d_In5qIay^&DX2y#W)=vV-S@q3Cj3cC>1)_ z$6iIGg1%pV=J#gK?9GG2ev>o#J9RSUmlE6G_cJ!TZ}VPxm)B%qOJ?~Sgv~n~=8HU3WG!A;-UR)jOfPv-up!1_oYJgu`58ud3V{3?CrRNDKo2tt`hoe zId*&@A_k2Z2jQ1Ggi3>kPu_A!`VEd4Dwz202x3%*<+O}~9Ktuf7B+Jr;$O2B5&OT< zxm@o;%diQN8|LB0GI=7$9i{SXlTu10xOM^tq0q^44~>rwOB7lKp=u4rz(L_WAS${t z1mk1{teq4MlCT(R`k8Vh+0uZ;{p2Fr+0eO+_(RH-%?vv3fR_f{;b`eUPkLQ#U>6Y3 zi|PhazevfRB50x=%DPDr5fQEaw6FMpgn-NTRYy>Qe&ca9r2+7+DB4(LV29()L6N!8Y z0aOC3i5EC;i$l$i_KW|~P>^F3>%*ypTL0XGWjb~C0CMktLeT|&r>C((erYNbpbfJpdb6b3A!&Y}zsp%R59!3r8}f{VSKACdiz%!GJVUzg=mVqYalj_B`bEC1F{ zcV$$wye1TF3yLqwLF6lmSrMIteADM6AKr`cVLF_Vh3&~=_On0-Hp8Y6Q8$9%DM8oF z9Y&XgZ}$Tm4A{2x*+wU1lqx+lUWRdrJkJ86>}TN<(c1{c0@|@y_>TK#m(KE)E&tPI ze?~%0cj_(ZL(OZ*WLne2Xf%I%dT!EfgLq}wkB`^4z-uc7-NG?)V{ApMz4pw8E2;_& ztNd7tors46>u&`WBA~!PM+=l(*Ux?4cI(tq-X<@3dibfNzemcXTp zAG!+TE=);QQ6a!g)_I5=LB~gF(Bw!ryv#Su{szU)yEgJ$bF6}l7(0+g(u z{NH~mgp3IAVDyB3t7!>vOg9XqaLxvwUj~odt`cIf=1e@#rQI1vU?~kX1 zCVM5>m%VICr@{QwA3iL7@6|`jArry~@b&CF$!=4l7?V>L{k))Lg?;M(!^3_z^d8ma z;ym!_tS`OB>c~?nJTgzFQqY_S3P>t2u;kco^A6U zf2{3d8na#)V_qS7?u!U^F6C$lCQ#26PG|Neds(y=0kl3y>az;+Mt1gjR>blxH?1S# zf~KaMV*C!@>la4LT#_16pY(Ay=35sP1PyIqk+jQWDfda>gpCA=g>iH|Ak9CKIiBM! zWB`#J0X}4!9HPXe41UWj`G}?<{!?;cye6IsHiRg>c5^NQ)@O25(R(Zk6v0dR9BujNB%ZJXV75MUeM>Pof&1Ad+AMoJI~MqMw{1=zRM< z&S2NL$A-l+Vs6X3{F|7g#uynNB#Al2Fgqjaa;(uT0bCaCKc4_0a>V~!-qA3SHZ+!U z(f;Gr!OWgXM9B7hB#@8_7nUH$90V`)>>sAf0>ltzxQ=HygHrX$!Z+XwEJ4$bc|&?R~4&Ov0&OAd0FFmLV^=b5Edb8`e5K>_D^3s7@(9R z^6Jr1OyByRDBAzaWPal1LZ3=Ydlwm+aJc?%*tb==d>|1pl& z+BT{5#mD=}s$1`z&pGVs`nNV<&yh^f8%cg>h&E%iOuhJ*9L?oJeYv;e^aq>C8mx9&!r5Jr!T~PpaiKe(Ic9s^R~B2%Y8k5?*@9aJ|5k6Vz~^fnKqf}$5JkvRkng!D zHZi+iZqR+W)&Z*CBy4o~U-E=YOX-tslzSgc0;#YwB&e(Z>j(_|F;9t&g%@`KgxAKi zg<|XdZ+6nrxfGA^T(4f7gD4y~3HW>V;628Df$xCW`GpRB5!)U(s%*Rox*(VjPF77Y>Y{ z1QjZ;`7SEBbl&$LxwFsHe4xR^#G;{<<_e3MPvjU3 z*3f$5vR$Wd{XF13A))d2*_|RqHPpTj7DYRQB*tY@A>$H9>Az^=Wn%AogleFsD6JlC za1{D`w*%!c#G04t0Gs6kd}@ie%{!6fM)dY5JRp(l#d=4!zifh6hfDA+Oarx^Ju;eA zGh|xk5`DebElYZw-vwFZ#3HH#A5fo9Nu$Qem-D4#34k`XVC4$@4P^?UW>O&x+B~lf z<4H!cN!5UhqvbQTJf!~fNJ^=^CXyg0{lt?;l>&q6byd7#XsOoGRILtW*d2)!@zc$b zEx&M~vq7w{x#PeGSR`zqvAwx)!RUN`sGErczXRdEWmoq0ICWXtU63{<22SiL=;hve z3e0KIkCt8yXt#Qsjw!-OD5w&=t9Ml1EeYYYq{`{kpely+6pE(c-nS{?I(qkQ86)H4 z1Z+7#Mx4;a2-%-v(c2ib~{@dfJNRkXmlp*^zM0(4hUT zJIIO5fGe+=O>pGMA16~NaVylO7SN%8*T;|}p%#d7I9q{E6YCsnTRV< z!_llH39{Cy?P~6m(ApGv-NsM4t^Y>ek}ujU9<{lN0-Wy_ zk+T%|saol)^sjMk=gNX7_MdEiU?}Cj&=+j|-fZ`>j=fvG+V@4A=5~bvwIPrRF+SwZ z4_^;-Yme=i>mtveUg@FV>2lRTu)$KhquY%I3)9y}XOq|9#X2W35jV`J0AQb!z&LE7 zZ`+Es7T(uKn^bDn72pL;#-_ohz>dBeWo6ZSLXJQu4=F8=<9q5rM!Y@i#;gdGk;|hj zQO=HGp0fkPD0wJgA8{)sMWhJTPIvb^7<@>Od4EGMz3%`OD7u`N4OJyq5dlKT4_IV{;>w8M=mGE#nTzv4ppZn?^JN`O@OTV=z)=1rI zD{@!y`R=JO{O%5_!s&+qs3#vg^jj!=L)U$d!PG9~T-rFv==(1ta*LrlB})CV9Kwm! zL5rWL7nJXJl9SmO2RXjg73WZKDSd_5&5$E5e1#8(|AD#0?cEN@-RfqGAfGz#ijlRz zW~-ziU`l?hssz?PU-E(dCV&~}M|jtC<+4zq=y94RPQd<@**DThMj4sD@b=VoFX7cZ zq35r&<=*LUFUObhJ$}}K?rX}XnkGPOO$}iQWtiRbCOQq7fVo!C^(ela+(0jRfcP#| zFGpCPdC#rbpWdf?O0b;7>5WX(^K>zSVC+{)jZ$}}6DT;Z>(h41qCjd!R6eFCV{6~KBDlJ25UH)0h>A8vWKb0Y z896w;H27Ue{1!%JXV5pf#7_`BxKsI#HQh?jsTj9gk=w3;1w_fyPD&=Ga?YQz`P8tH z(9ojd9C6b;F%Zb#v{8SuR?=_kVX4)_4v$bsnzLz|5nsW3-<_IGygOLpze|D5znE8; zQn#lf=QAo|<_o7HNLSY~no3s3OAU%hwLX`-R03RYmMojo>3Qi%fN1)_BqVWbZlT1i zZ<+%;i2Dsa4#5-k=23M=p0Ho^{hqGP)&4s9=U=?Cmfrl!Ui(pV9wON0yif^?7uw8>`8lG&jJ2BilM$HDyC#2q#oGSHjFomBNfZ7{&KKin*sEk-z zlCo`zG>_1ES&wrqaTY`{Jb5T^ksR!7N5Sj0f63-L9fF+0Hmi0qxaLB=Fgs^LA(~cUnYD>-NdpYDcF8PMkdnVFZ_|J^UQ|LHV@>%?}Q(kJ5o8$ zT~mOrz-~6$odkz`0B?8QA8taJ2W4F!@h$alv+3+vR@_UvmFic?<~Bc4T{i93 z(#kURP62vlB22LJwJ0g9y7+_ZY*a%2au5FvEB)~A4_eyP~DuZg|Zv9p%gBz znL~TUx8ykVWhTJ*VbVN>b2dronJn`^-|FwN`_xV)7tHn2xPUcHX=532`W? z`IdHNP_CL9<;I7C^F{c;>#GDDoGA^=Yim(0J@L{&KshO#;lUGmBLqy%s71@cD2CAf zCi7)EZcV5W+BY;_?Z3()Nd!yDLv!f`YiPLopUw{|zF;)szO1)~hOH*rEcY%QmTNLi zmUysSovdKe$Ru>xRV(4YIP%9cE2xyNEJM4^dR)%W%~~<>kuWg2jN1KT54@XMY%2xP zrCJpdv6g;IqxLdn^_Nlxg*CdPMZjY-M705JaqRKh6_JTegub_j(SZSv>MfmNC1d)9 z;_ZU8qop$**ilU-Dy3_$q}^S8HndZG&A<6<;k~=06vhL_kk#bU8lnR$%8?caZwgY^ zzTV$C>%?GzQ6et|nF@~b)RHJ}dwrw2E!h!8F%qXq@Caf(D>G+m@907`R4;gnuCJt5 zRvZUJyf#8}L|O|2Pe|O^7`xcU=Z<(JVoN;CN-ulYDk@d;eKJ>iTh3PY*z0Z7DUA`( zr`2anSsST^_z?T(QU6Y-UPS}H$_%h)LyC)!ex2g8kheMZd?_=bQT~CN*m{Ud#N4XtXRC|VN}Xy* zxShwHVn7inCw8-FK}Un@Dt#D1X?Q%4JI!k|*|o#cq6QJS^2QLP0GYzJ`gERP^-|PK zV$F}IfONv4pSK$NT=TVCBE0h}WGYxs$%MU!(hsk?^9dy+WP)PtwhLmR%juLrGbOJPrBzEN9anPz#+xdlPs z-*;mEmplmQWbcagV@(#GL%cSA#&hB3MC6uIz#)o#<13l?REW^P>3RI_ijos6b;}%U zlki(BZH({g60QlmQAC&pB5bsdV^eDDtCGJhYZ`|^1UzLx2zV7Oj#!9dLiiR$wB&mJ zjQP#pNy+bojg0ZBO4d>yUQnI@SMxtrm-;h7BWt8 zV!mXX_Wn{ECa=>AD!Gt*?}d|go=Z=JoSZ^GM@1mc!6vrm2<~Y_V7|E3Y7OLAp4!)` z5&5=SQCzte9s$R&_ym=&c+5UfzKbnPCPr}BAW^R}bgIjQg}(H)dh;qf(~)j%q;F}+ z`|EH8f9fyP_DBle3gQl`S|To6yh9y~$R*dy&!nyuU=I16JZ_vJ_)YAYJHOC&zuOp0 zhl$Fo#cyV6gBt{{w@gf&j`nv3MOj&7Q^PAwP9_3naZ;{uU278e;Ut&|H2M`~IaU_G z)&N#x%r73hRtaN@cE6jD?#m4tWT7&Wy=2)6Edb&9?bFooGJq7H;`wes?jH~!_iY|$ z0231#pRJ-&zp4M_`4QOJBm_R6Mu?qQH)N=ldXn3C2!=&Qc#(*B{X4 zMZ?MY-_#TrIPvRhjVQ6BR*m%;#rED#GUu@hH4|Ajun@FXpQ*jC?(Frq2bMfN(P1-- zwGOBhLC0M6G-_AFvkUScZPg0D7`;Qhr-^vS-cxCXF3+lW;6s2-5M!g@H&ZfJW3i`f zqp6_DAU=L=HKyMX4jW|KUR_@OZDj{*^O>QaBn9XOMt(Z08?6$%B!u z8C-@*51p4nC6nPiZpY>$n;+uwfvN51iZRH>`uz|aWqiV(8_d>UPtd61K5J->D_NUX5Z52%Jq4;RPHYDe0*ztU0 zFq$i7f`rD)hJZ%&ULS)_2V=s4|71?*eDK`t{07$Z=(aJ?%s?so9I>8k417cA`$VeC;IwB zw%rdP^GG$RM>xS_P-0=dhlilY{O_{bH)HJh82cn)2O={huPPY=%JSF6q%YY;R5Rig zno-!2sFj|0DTkM|Dk=@?N;Xa<=&bLpSTc)*nGy*kzFsRh>NOu(o0{h7{)MVZ-=82i zVO@Osz>1=$+uT%#wQbg03^dKtrm%z_P57O*$i|2deU5#+Od07W|7GwTV~=Gd-oQ-v z*2n#`{)N+xR{|;6G0l%iZ@d zAm&UhEd2CWABisI6(%Gn z0HjUTIF8UZn0B4Nc=nY1@|M#3B1_sm%`A<#l7yBRcAxP;)|%pV#v^6 zA2PuvF-(Wk9E2U$&FJbT$CU`*20Kal$Fs#&c!%XiHwU<`X6h%R#FPKcEc8wXny7 zccJHr+;pZST}+2&VFT3p=Zuz*)c?ffvlGSt`!TCzuo8T1h0SRsV6$PRMxtF=w_;tv z?QP+Au`zL78C({5Gk&JiZWCL<;-RXgjv_jPbxX}(aXapw+=yahR7*WSD`niFX}{E? zumnAcM+vK_!09*IBy>EVVnaHg9_;Y8_ss8VWNYjuj9D|p2&RPf@Wt{LOd0N|``Np?Y%I9+n43*i(KqXde|@r;z| zQZnH?PGe(Zr)tb3iO>+z&vyQ0cO|IcA!B2yDB!RnOX2%P=;YCC#^tFH5_&w=3k_uu z+3nR)JPFUC`9s{#G9!_+r+(A@8Z)Bn#X?KfdMt>7hUG`dJ>g5_cvFLT2%u+=PgV0w7dLlH>S-s;uY4u{ZU z{uKEZ$JCSii?_OEgaMC_F)Ig$%D#Nra(OGo^Q;EZ^`P^+urQ0OGFjM8`iRt`Ft;U{ zr(cU_p@}eP;cS=@rSCVn zAqfch&kxQT{^b3(EhOTDg>gB6%Xw;QikbqD1HPG<($dq%Ob7vRU7R60;12)arTJO6 zm#3h_=t;?+uoz`3d^hO~5xa;U08V>qWomW2X#t%$La+LX3nlI0ki>*8C{1Lc3-k-M z;nz_DmK+*!B4V6xE-KG6%lu9+{)?mZ!Y{pyFuzww;oU}2v~I_-o&QardQk~26&nj; zLxooVWMuXL$5=t4SoiK{QhBTL$zly5_v2;M@40>$>+8l_ei?iwxiQVdT3oiP{KEJ9 z&Uc8B7Ng&3&f)vo?eF>SJl062T77gTDE24I>saP1p#C9A@ypNU1Q~MZwY3J=E=jc} z0;=KvVd5iulwvKX+DH5jQMRxxl_(?2lgaDWqu^~d_AJ$9KE5_9_A6A+Bk9(p@IN9t zcq@?Qh<${wh5xG@?U(G4VP_S&4h>SZrMa*59OH{2g}a;P72&&-i(o%`qm$k%z(>Rk zU_aMa4Wr+)GBShrr&PAq^gh%+Zn})-3X#SV1X4G~6AKYEc^xBle*4e=3O3N( zIkVCAu<_bOm>ZDrDTs13^Oj1Jq!J0b>+S!xq>~FBIuIWmq{{fWZW!mG2mef2rUrn0MXmiu$UC@t4U|ert*>%3{I<@&pxh0Dwb{-*uaR zdLpZP*(9)UN)j>Y$B&o~O!DTL+SNT9292hB3FOTcd)X3bDC`$Gf8+}zGK9R?e>K{~ zH(8I%YzO#da8kIr9>mE5kr^|y=ic>O zZ0JBDbCF$y!O$>@2{zenKK|R7vs8n39k0(<@26Be3YNt8?zR$oTwJ{EQm2v9S2+jm z)BufQ8c69CdTWe2nYRn0N5xiz@l@zUJHX$E6A62NBT_0+*$4zDV)wVBI?}PMC4uWYets_TInytzH-#c-y1k?PCMkqg0Bhg{Kf?D*=6kM-_Vrd$+Hx z!^j}o(IKZO0+%_*8n|-8>ksSGVH}+R1Fpol_4Y@UU?28VcaN5KIPE{W!4luma|17G z&)eq97N?6DqC!{;Vs^8%UzXTR%u`=AySQD_5jj?)u2J+Uj`=r0jPb)eCfau~oBU@@ zDJ0u}Y9=Wk-QG}d41OAoQ>uh4mo1Bdv(d-pEN`CpQ&h4;LTpW@@!TVfW`MQyb6wk}1F)t_NE zK|vMRkJFXrHTmjiWE{v_vax-bXoFR0L0R$m>3s_`$)t{ zG;w=Fx}q$B&?k`Lowtzevn92JB-b$na!%cuv5%yb$r~@F%L%yr0nx*c>OChG&@J5? z_w$2VsbfH`$_lRofa$zbM>d@~KA&IzF_S;7?xrtlA)<73aj166MvA>Z~6rGbqFH}}1C zZR^7P0FbaqHroSj$(@gdCwhFv%mGAvf_~kz9427KRTj~g5lJ(!W#$$TQ?AM=ot5J* z9NG=1Pw{)XMf&dOS`eqmiZJ3=j$4*k;Cc(hbrbfUUlen>|Iw8r`?%gRD@ui0ZSc^* zwR}+AiWVP5ploz(Y;IK2n~v3}gKRyA0s!g9*vBgVP1$q0_5NXmtujI)@lLup>q>^I75(|pn%?zQC%#~W;MX!BM5g+ zs+O-c51VM`3hAX^_C~<`1Y*1G{(m^V>8MAX4f>%)g^x8^hv!b6j~KZe_e7MV*^xUV zI;MRW0Gh&==OTtpHC3)Vs8zF-F3r!V+v!GXt68`|+*>>;1mu=r0k}jp`S2+vS|x zoTzf(2|bY(m>5dSE%xY5A$qs3fhUAlrq#(*;GgRL)Y;ecXo!#!3jnf!Q&JY9N+=}l zks#OrehIKQV@wENqM@Lm7#jRbXt+vGT5OeCe2FtMdXEt6Q-a19y{b`x3i}X_yMbd& z&k7zbF`soX8+4Vo))oq<%=V0-#jPF)U}!^!0C z!P^Zdd{Eua9cgoDgq#`8-y2QTfFIFUw$|p&_8z&S*eUO_*PzX4fFB7sprK}2kM?)( ze>S4)>*p625xKb>^|m?Ibw4%>KK^D}oiZ{=a#$$*TBuLmQB;U{zOqZc@3|qFaX+8= zW4@dyFeqs61+Qt&ywKZYv1Lcaa`$JRXG4Kqy*@43dMFKYInw!c#F#C9rCH`eW&cle z^nNj+pJ6*k0IFN+sQ=oGCv%gWECQjSVr$~s?oD>S6p11`kOlkqq3Pa4{GFao2=I(z z0Sh4#z;5$$_!qPsvXY#4*Y6;&d*w8LnWy%zU%TVDg``n~+H2*)+DMKvJNqs zRnH|w;vl@FC4}j#%`?RXS`r1W`23K{C!nYgfc#14vU)JgILHI2dk4smu*E1@=Z}^D z1wB2n83%gsY8Hp%R@%(k+Uq@&1N#@MIBockBy1?$6Ps{2QBU#Dp_mxmbsS1Pup*^> zk7XRSK^JF5XyeHW(0WTgWfmx*M35P4MxA`Ha{xzPEUrS`U^JkN1e@=8_zA35x`6!V53PLL;lLS|aWU1ex!-89@dhm9gDi}A8~A@0t<;D~jEJ*^>O+Se zgU7053ctiBWxHlgr%OT@)20iw)c^Gc0w-vUK-EFD-m19io&Jq!0(^W3KwIk14SG4a z_+Qk~O7d5s9~+x;A|`lhDwzLbTPz_cm--cBPOi(yzvD3$Ok=s?;0arM-vk$vTskx{ zGeU}?!5jE!fvR`M%>PGv@nS*yc)10giTIwA=!zfYC6qvK`F?SO^yh)KGwaJcygh%F z@r@I!l5Fv(p?3=GB2Es{-e)I#u*dnr<~oz0)rDK-qJcB;NHr)yI94*u`fkad3g*Qv zO033}Jo$Cje7GoTk-3bFI80@4yw{o~P)b|5TkP$Rnx#KT(Pc5Mso?XKRui*E7HO3< z@F=bQI%#5>!%A14R*(37{df1T(;$Hh4Jho!z-cr6vqAX2JAW3uU9syqW=vS;a-2n} zMxJ`vgkV~c{Xab)C7euicQ*nt6@vNVqxzHAVL56iT=XeD3ti0g`}gm)P8*_A3WJzV z{ae4F&VWA++>#tR{3D(bcqb_#7YGCYg*6po>OEi6RUY?KPJ9So?DXc?3c8_4jVNny z-SB;Ob0bz;3ezQ?vGf~uC)J-pn-7aUo8cZCV*?>OLY#K<21w`XG1$N((id^V>2vq0 zfDG<_Xn&H%L#P=>}3qz($lDQe}2Pi03)7OIqokirO%(R z`0lJzx~nT1M4bwSoNT z)X2uuQrwa^Ee=gP;k)-fyZjRWcliu!cFgF?r!V9d z##!-S>-O@`yA=EzAhZ%9F@QZTk}2`JmnKm(`sP+RA^BOzbV}$$%`tVz`I^^y?+uU1 zW;Puur`vpH6xw;1QMpXuuslE|#S_E`B7hU*30?w+sKU2#;ZA&iA7{snR7lwh*GG#) zOb}j=FVyr3#J=tcf=)!^T)uC7%48PxcHc%q^L-F;FDvQqb%CluZ>WewYH;aAxyZO2 z5TQdr#!58yq)&6Zq)idFB5+uRut0C?@8DHLqaOT)~p@B?-of{5!J zUudD{ zFiV?{!aE3JI>)q02hHY`w;d^$R0jVv#U%;v%kO|H*~+ios~uSp-nRGx$lP~;vrd|Y zDxInmesAdTXttcDgANT7(<{8Aku8k%Mfh4lSaSk(e8FQMo@$v5RQx`KTagG(tjAZdalPe+%KxCkyx4Rb;d^&>=nK=N*X*22oHOkU5Jk%KO>!hwGcaN{q>Lp1 zJBjs%;0r101KDUK6BCY6->+{Q8SuJ{GvWt~v!dl_&8A1{u!**%58}Y}I=PYQ%OM|#!+X2QFpfv7`mL4D0bPLOEp(k&oEB~9$~;6q%Zl$)6TCE#0x7P!wa%H2is zD@XqoVXkzQ$!8GnS3EpVmK0oQ{uR!+hD&@`B#@r|heBYp;oTh0C$6MtoN-!`nrWxwl<=5J%fbzh!(UpU_9 zW+IJ^SCm8%5%@Lzh3=uy6|RRNWFnpbD#6)kiP|B5#RbVHuukK@ml_H@YFb7H5zuL? z==GDGmR5?tDZdBPqhc-wAX-39nl?lg(Tv>&-`bMxg$vwDZ4Eq1hUX!{X3S69A!R%A ziS)}=?`fjjw4w#t2G4y<4EWbi4rGs%Llvq1Lb3>B(ODa(!zhWR6?m)duR>o~1Tb*& z?DGz0ZD&zmtu=-J7gE;McOF0p!f`vIyy_WQYsWe$boEsaz~3tc&yb723>j2}{Squ* z7yNw9@7qO@xEaG8LF(n^O)I9gx{7nw!Ud(|Kv$WRr~X^l6NK5gSScGzR_1W;t*&(% zo7n<%&_@jGRGWq;6UquQ{zvbkpbW!^st(rzUdH;=KLXW2^` zb(<`+4jbgx6*AG*(OqVd`gWh7rMv zvF-TN>=o5@2hPmT=G?xh(Zj<{y6m{{ypau{pGqM%3ED|fQItNEfLPmX}vrMITVB9tN`Fri{ti-{Xwg)68~r6*Nm4&E=eqwu z%j<;k;OmSjf&cWm&E0*`MURQYoH6m~J&}5U+=$;DcBsyx&2H`pNz`n;$oa2vpaly~ zr&!kf9BLV3`1ku!_dX}0%zoQLawL63Hj42vS{n)#HTEtJWq4T_|3*0O!qLFDIkb!L zE`{i$Nw_v7S)ixjcfse1l1<}G)_q-FeK|v|%9VH7LhhgTR)o4`RSwks@-_CxZC3I> z=<`*bYgSL`rG%J)9s6nIY!SU~AFPbtV< z9(v7avxa)f0}waMlY!O&p-9 zQnnBlIUt@*K#!0(3Fd9`kkI`mzCCoTTDWHvFxjH0Q~w@&xYF;BSH*mJqTn2h+FB(z zDH%Fh_0<7zWlN~r@}7dcIAz2{(GVKmVq_vmVp0UFIHOUAwln1#4(c$suwVcr0N@qd z7N@{02sGXG{bLP?aGHVNxgRf1HSBB{&afBKJ}uAP_AO*ToqtY&f8Lt4xmIJIPmBU* zI}KPkr>k0);Df(RLi^Hkyf-U8c>guvr`99@wF;sx>9ci!Mle&26J?W|-t4|g=MNn* zz}C+dY|0u;ADr|Sz58RjJwWHr-4enZLTHFyMbK-YsMep62=YBKU-xlBt|C2@5d|P2 zyi=n9CC63SNsv`6RzR=&Q7avn29yRQIGyTGz zQ};9Pw9Ts4Y;b8hy~V|{{T~s-C`x6bsp8f*qO>n)g4`^Fx~@MzFOZ8m>QxDwcAt6@ zN|L3&{8*^1{^6F$G2M46{MF5=6GtIEQ7ES(`u{NX7F<#N-~YCtbPwI#-Q68Zhe(%n z$I#NlEvslG-WO zW>gd(S$zvICT>CZFfQfwO-N$=C0eX>GG>H_c)X+gbk6n0F*SGbwyL`L?*b&%T^_4@VgM9ShW6!MKah+;`=1;yJtVofjta| zND}Ou|8@EPvw*{{jKcupcSWw_w0l(dAlC z7;{?jD|lC5=|CYo>HO)r|D7J z{I@tVL+qfPfi4hhaZ@3nKedSUJG^~HC^~zPy4pM?f_N{`Q8aA{vp{axj&EKzLC-zG zYF^{cBae{0c=^dX|?}FTyxnJwGRzcD-wb(xE zm)HszlycXt?~P0?R1X6iWN=>-Yz7|%W)-c&Q`<2FCmb{bF5gpABq^{b*?_Mwy{|V<&ohf zmA~lO8gNTy-I%aopWDjF=46}--StLFpwnf~;IDK$*x$3cDS&<>D%+#W5}_~WZ8@IhdQ zu!a~7tPgKTL4&in`gSk)xBLoZaTllWeUplKzlDhrTxM1KXJnQ4(7#)zUeC1AU|ndhAkYyvUV6hV2*W~i&@}@|v35$Xq0)6< z`KyBVgGgT;S=B@IL?lcu1f%-~!Bu30s{7I=O<8XxEnzs4)ahw8n1t+~k)Q+C)jbb< zbF2E9dwf&z-1YJFgBpmU~sAj#fQE0oqNX-85y2oO~Z6dP8 zAn%U}eO4lwj)MDC^S_bCr|-2^ef-|*xB2@`meRIdMA$?`20u*us;ZD-h2szuJUm)f z3HwLg|BA^neD0R+81BwWJbYy36T8_n3rAM(L}GX$5M)05EPwCC5CxX_;;m0~8J2Ft$LDQq9pNV(DB%~Qo4DfwY!(v#FrSCDE_Jwco?4(gwZ(y)mi!TtUUco2v8pWxG&-dM?*x_O~|f`%4HAvwz!xmS+{t5jH9`Y*M~*A+xW zv2FoUM3%JrB{3Xu^}u&r`McZ;9m&}@k|H{`$(R)0q?sqIQYoxXi%6qIZNKB!GHs}L zgEP2xYZeVM%>etm=(}}T8PXj|B$bPC=fj#Wr%E9T+!VIBrJ#^S`8)n%q5?$z)8Z?l z1HT6w^}dy}9&4=+VUy7guougEF}H04f9b}M&O5(v$sZkMdzHjJb$(^gUNJFoE%>$- zA?52^_7Srt5u4Lcf6N&2jK2Ib>^Mx=eR;6{xAA7d7LS2oYc)Z5yduCGQ`d6rSHK<6 zVZObd(bIE!Gl>AAk%=*5Ysx>Wolw6JN`~7fZum~LyuVh=H2#yD264=ef)pkTZHmzs z4&$yO`=xI}0e4&gTwHnjIg8}cg6;?|2?-NNQTLr_TjArSnk<;YcEU@FDBTe|6Peer zIoT2nIy!EsP{ez+0h!;$j0UaKrzhBB4%fwM)4Z$6U+tu+o$j6n4Qk0rVipp5UDX3kr;mu^KhQa6VkV4zQ8E09c{!j? zyWRM7imi5x9p8o}Pc>dN1c6Hd79}z&d`FQ&0>qaMYRJLne!oI2R1!Is0-+~i3&6$- zIH!`fc?Vr!PbVdfGBlxw{Y{XLM4Dg3_WtAWd}%DSmLU2r>qTbLG#o(Rk zK6ZdSXty9^7ZD^MYO@*wf8}^UJK`0_^wu6I1fA%Uxw0epBFb_&AD5~65Lv_fE=P2WU9$;TQH~`+LY81x$LtI+Lv&&H z)Bgqb0DNa*$5K7R*3Qi>pfox zG`E$9_|HTe#5)`(bRT9*Vqhv(6?|Ypn>~6ncYm^5ETavu;XgE5Q}d2LB8#1|+*a~@ zFk5XjI4--ASo@7N6*X#R ztdwM3-X*UXtKR$fMQ~EM;D!foL$E8nx2idc?>rpY#sqFho1-kC=qL)@aS!b1n+=Y% zP%V)|XT<$JlLo|98K%y1Fu~wN8l&afcPHpo8PZ2$sI8tzJ|^$ySv+=XK+MQQn8~VKj`KUd{je7j(ND|;YFcrZoj6WKE z0h}*k4m``1b8Pb8I@*wk_Pu`>K-=5&oWdp6BZSZ|u8ShOAsM3XLtrr(=5}!60`gdk zzw6O|^Q}J!J<`?Xn1UeI#s#!(n^HRci#?@o%9h%yL)6B#2`lO!4uWC405+t{ibgv0QGY7ll#Am+jo#Ev%~?1$NSa8?B6_ zZ!;@OgW@R$b@tBINn^0|>55LAaFAyvWgjD|q3BxMiNAvk++4UKgHqs2KO{6L(QQ1Q zy0NLYN>Q6|<-PZ9$DU;f5&c$x^gUmInsY6_TT*>24@Va}#q>jsdhN_mY40#Mk70zJe_T^l zu4IXcc%)GTNt9_-Vp6b3guPeTR&H8SH(}*=uV%)=v{Ce4VVNv=)J2~oDYrO+i;)ot z78a9}l9E!+hb0*gT*;>j#g^(m{nyp89;^!#E3t5O<(jtHhfb7(k&t|t10gYtWw3Z+ zJ$JyXGHu-CV4LT#eDfIqi{b%8u(GpjVJ7LjAF)H{_4 z{^GMr<>=VU!I+qg1$wP88i0iYz;O6C0>meF`%o_kd5LwD(Mzl_n`>a=Jk;OmBArqP z%|g2?heeeh!290fr#?AKUabt;j|9BJoV*0Z{hh{LWR%gXcndaht@tMLw+r3hD5ku3 zy57-kRck_?mp(&hC1`{EdU$MZ(eW^P*5l@ibc>kgBDaMJQc_Ak65NsMXlP3pOfZ(4 zhcO{><4KnLOIcDr`Ifzzi11F4Ea-Opphn7UrWo~SG|}>**AnjI0`GN zl0M{oIF#avk!8pj(c_4De@c^|=@5~@QSnBd9-zg9r9&4HF{@4sUuBdpUN0>Bj@?n` zgBVd7cOitS$W_N11y7MGgE`AZMW9Bo>)m^AmfuI|;8)}zx9LBpz{8Uhb{O_B!o#%P z1SZgBy}$Seew!K=L~RFL$gQuF97JMhQ|MF#zrGy&tWKNKhs86|!m;W6GsqkCtXk<* zI`&zI_xR_!tK7USPxLpDgeM{nUNiwWdj`df3l%#A^aL)gNnd|`B$InM+;VCunW+V4 zi^1~wnbJhJD&?qQxyf&Sr^JZ%3x(nFK(6s5#nKjlU|e!ZhHP`8#2Wnf+oZDe|j$bY3!E~I`{+2fKRD5HunQMZhq#n^Cv@30`O z6*qUggBE-$ek=`PY+s2)2S`RJkSerZgTS<%9a-T7abjVyayDJ~KJ6G%R?!4lKfEb_ zX?ueE+Bh(t`_u-1z879mExGdhs3~@$5}lL@gDH_H92H%-e(#BW`k^IDyr@m|At`ZE zN!=PKG#Y<8^SmIW5amzecKeYOGi`p`Rd?7E8SO{%%brk&S{-uYjm-Yn0$c52(so6M zrt@$d3X#~j284&XWpq=lcAlqDAu%yBO-)URsLWwmH*wyh->!OLefp73hBDnNJHP$U z0oYV!bY`e7)G=>we;<}&fzf-+zOce%44nkX+_v6ZZ6@ICr42$Z;ZLk9rtw8~ZK7FE zb0?o^K`&2hmTtUa?=658FFDuKqZeubbL++NwSs}U|rjx z#iQnAl_7J<7?c;pMaMRC^;8r+mX-^-aee@a!|e{lpaOx_duNLXjjj3fhcd$LH~0MC zAPJikaicBod!Y|s(etL8w#JEZG4q&4?)Y_6n6hPRasM?uUs9nr7L1E34QxP19l}dC2eF4A5;nFNWtiQ&X}(gY?%t z7FzoxZ9GxR_KWbNTG;QbO*tx_G85*Qf(x%P#zMWS?>Gt|6MVEGb7-UfBE#{|ho0yJ z$N$1jGC{IT)fzBm8Xjc;6t2~<4m$=$6a~0^ghiLW|9mkoyrE2sCqy}eH%58B(GA!AN<@kP^Vu-Fjimkdz5T8g`C07zV zm4LaYi{xxl$O(Q^D1%q@Br_bSsbxhM&%=6_Je492ob70+v$bs%iwY6pL=Y0)vy^9% zmJqVE$fHX-ATG#o>L19;i|3cf`Nm}<*Z}4-9w{f}kkZhQC+&n|F!sZ0(vO@`V|eCM zDHFj}^LZwmlX4mNdVF8JtimY3{d!$esJs@L%A`NR$@Fi~@ox&HL(va**VGF-Qr6~` zp6`I)aE7dBjA@z4uzDb>*7S*rWBroPv*p0fys63D@n67{ zb?@uoie=j5-blwn^-29vQO0+EKe>g0$2A=_%N&t3*Agp6dc`(xD=8x37fa0g+F`Frbu*Pl~LdlL|#@R;5%c->Vk2<%J8NdHf9A!X(o{qJtt8 zAaTGaiNiRpLKoiLVw$m=!v)JeJ;{AB;?M>F_Y0&=KX+9^saUVhc+zBr|{?siNUU@0FJ&RPCgJbSX zTL%?*ndvQCnWJYKrA%Q$5CI*!Ccg{TqDI*B`1BWe>KVhA*U+5xxW*-nRbe2^sCf67Heu^cNJT+(A~Zbx1JUcZM>q>vC| zgB||#QQ6S&D^cC_X3a5zEt3wqXi9X1Auf1v)yN14@=z>v z02b!N%v&tL;AVF;0l@_S{#R6)Fw~|XL*S4C1n$n-lRlt}1;xL3^cxb*{7b^{d?awY z(m!W@bu-1!<+$NS#>JAHYpGyc zh4ftlCyZb*5z_fs(&Xh&BBA1Hwz-|sal5^EXAfkn;E+rZ_K!95tsy9Qd7`s%W-pm$ zmZ#thXNqOT3vZ_>5v$k0HenrEnnYox52*hvGO3Nd!LrTS%wirPh-wXJ!8q|=9)}ow`|EZv^pVC$BGqA{ z4LiK+3jWGUT4{(?VC3!PWB7o6gVZ(?R=g=$QYi`6)6XT+*nRaPKXyvT04j8GLVEM_ zVeY#?e}*BJd#M!N@P(tx#@868pZRWN=MhrBS=o@)speFZ+Pe{BJNHQ=>=&SIrP;3P zeD*`}?^mT7CP}VddGJ3m=nRI8$UvnsGT`~Ldsz^8ziY{9VVek)h&gjfk4Llq3jAZT z&!;4P=i*Et4TOO{xs^Z}YHD+pncozfdGqc)KBtiC3yXB&jdaOr6*N-q2)8xZSUeoD zAS(uE#R~V^MAyswn+~idFZk-?ng%>drBBJavopI$-_2C9aFy+svZBe65ZEWx-y#DxldoTN?@??0=}`ZC&L8l&RVa=fOVs{IF#W*(R39dVVIv@a&HV-^jWoFV-3;(l&0Z8IDUx&1@gr!z4@)kMiz=Ag$q#qDQFA7zmLH=bW&WN@(Y?mV)3 z&$1vof1obc>Z%frlex9^-g@22u@rX_kqq>X%Y4v}jfg13pc~^Twf4>%NG2db7V{)S z8qXm55-VsS^+?s@ug{duKyeo~LV0o|tw1zKdwXGw)4`|^k&}xCl_^wW>z9Nt#7LAA zcgbk+|Ci^(exlj7NPLMVw5mPX*tC}esk?IneI>?f*+xV^#dFPnN9c~_cD9x{HU1hU z5ZBD@l8Zy~%^66N)C@`g&^6_?8{!Bf4*yF{%8NC&s;GU;33aBV2X|)JWk0Dh6qpu< zJZ9w&Mg?0J|H(_c<5-A)uXyPO%Bn8g(hr{$Q$-4H-K{g@h#BWYctm&f=! zs(Qge8h)%n1w>m|s1S7@V^C-@W@(x~HWh5O-FcR_VB<`KD&}cRRy8)YS%7m1+I8S3 zGTbCp=M+zXQ`K&?(&rcpD2%NWgD(xBfsNm%zIJt#RWqKt~ zBm6yMT;qK!%Bq!89c)6i8-?1$z8bEL;Jrh*23@l&;QhdCi0V_wy}f)#L-$d4MYIC& zOM)n*F6sh0!^|76K$>|v12R>%!QohjU_S;ou~PPr5L)W9%&97Z%1+M2u@i<{gIkB= zKf*YJm7A2HIOxPFOoTld4pBYkV1XqE*&t#+2V%%b4LK)A6M6NHfg_Z-hW=Y&>bro%KXpxjJo_D2^Vev4)C=ha`&}}EO0)($75sTFde86oY`#Is5 z2j0Z(MD^4Z9kns6Tgr;((Z16=AxyC1hng>=j>F<;A83vfaBc55P;?610R4GQ(Gyd` zY7lkt7fgqm3 zM@zwpGY)ybSo2Aw-A_7>RW)D>0d%PsJ2F~oj@yj*D1AFJqO*T`FyL9vHk!WtZ{qmi_l+(+^)`q75AelbC;p-@LgUObQEds~AX}YbO~+1{EH2 zjLm^&Ry=>l7AEG5-bVN(z*D|Djl?*T2Kf#UvLZcDW&JJB*{SSi3zUQZX|JH5fcWFb zczGqYC?lcw#jlY zPtS>IRiAoVh|)xfu>V%e9NF?Kv_qw&zL1<){xoQ~Hq#RLc}md9gX!w5zZm@LU5DM1fM6o+%<%Gb>9Vs& zTwLOJU2BZTa*!dQ_PqIy?Nd8Bg-HGsCG-dD`L9+&ZikmjuHip^AIG?jRrf3o)6%j2 z_khBVEu|OUm2u9gdC9`p;FwgM>DVv^9Tc5nZF&qf0VLF!fs3~ct%+LT;qHJ&w<^l- z630=J6c@vrtxsv09$o&$pRCy>Xs9Q~d{Rb3x*h&*!LYMDzq2aqI`HJjIa_NPc$?j0 zxolUMdZ162M6W;7qM^DLgLxnCJnu%9vX}cZv=A54FNHR`G_I_Tfg4K z@H}mJ+g3y1f%v#O=ei^$1t1Ihp=r=G6E<|vI{)KGc3LRR07@e~g)B)~7#t(RL7O&; z9ydCsnXaMX2r4721Xtr=NujZ(v$RF$E^bLkR3>SUEu44iSLd6r_yUQFmh9z>1Xrxx z7XO#VhCQA!JCb4j!Is4y^>?DMo3S*>8J@Tlk^y2n&^3O`S*MM>n|rcS=q$fx3y5wt zmVSlDP>}=HjA%)ltIIaiv}0AbWU=Yg)DCz)I)MJsWu$u^wpGBXyFZUpf4 zvtk5Uo<6pm|BBDKvz^caw0i?0=&-7eJD1$o#!R6cC^M^PJ#qcsUNq{*!v$oipaLUuhZSWYN8d&&%?xkv7s2TAaM=-($3xl1c6Wv z7<45e!$B}r$}?7`O##ir95cw^h2Ca1JH%%rKTeiWtB4kELQ_@KAPDvm@Go-xFKct! z*tktVZQ=(6M#Cu@JT+x+5m3XHVr3vVK2biL4UCR_pYcUqc|fr@Zic|43@GF0CedUE z_agHSL=i3D4G{z}{@IDYz7zdk_Vi!nY&9I4!zv9iuBeI?%>9>sQl~(sN`W6;O#n1` zZ}oEV_2Lj1w7GTsB(Hi}SdV=tgaUYobC?+$VO*yaZL4z6wZ{8~1mJeY=kxO)8A@vL zpqzO4k+Wyma3*mWyc6&a=>d|&KLveC7fkuwIs^hCZ3gimHS}In@Lbk%?U$QBIB;65 zh0rR!SB;o)R`j+J0RYrX=WRR7Jj~}Epo{ISjW0?4#TA!dGxNT<^cjUJMu~^gR(GcF z$xZtOqUybLW{uFs$T;9sh>2pV25%t0Zef=(9FKa;sSRYlMVU|Qz+=(Y;`o1VUwz{j zvy^t*1sbu7aax#ZFDp4S6R}PYtz|X-aItcGL6LIwsG&bGG!!0OSXh`uXc_=Rp2iP6 zp7mqpCUn!8C~7K2K|-uzm7ktn0&%+7W)-sLnz@1U67KOk?Zt0=l1NzP zz9w)>61t8pTwWTjpA+8)rY7jWFolk)xy5FVvW9!6{Be%ZH^UAP;~QsZ!!~BeKnx?= zl1H{pARtM4qZV}iemd~XxV45b)+H+#P1aMpN6>>rA)YLPc(H=&`p(%=G#lnOLhXC< zW*508RoUMY78AXmJ!mm1<(gz=db9cR8R&3(9 z;&spjRqlt&#U7>HJmCpDr!P>Vhy}x7^*@V3T8JsLyNLd zZkXtR30!NT)?vs8^0ndYLZm2_sO)(vt#-ZBrr7QC%HpN!#-QtV;O861=})ah!U&_| zQ)*%I@g^+HoK|-K3Xgy1dcEknyLFpkL{UrH0Mnn}p#IguPjHXVPW6Zm+hJn?!e0G)(%K3wT!)x+!wN@ z_6C=GXrfkP#c3eL$VP?ri{qJCq%By4n$(145th#_NeVfur&bN`Jxa57*E+`*}1eV4!cUf#PAEMLt<` z^~+=)Y|o}riHRhfs)Yzm)3M^B!uD7X%~9u=g2E4AKbhK*FSP9IN_6`DuP22@2Eehr zm^J*RxudLHG$Fko+r=gOM8&hSg)>t8DOqXzEf)IV$%)~B0;oMZj#-@Gb5EC^QnYfllv3g^q%3@HYNp;EGp4|ARjXnf=fG_gXGC9% z4M@<~xXq~Djcn+-K=r%+OK%uHOF*+sAOT3~@zuv*?~}A;Xxcw!A>JyMs!alX(ypC% z@KsYs(n51mnr@@+9p_NMyQZS0$tO#riuOsW=qJ4Z*9J3tBEd z@Ef$b(@T65qU8LL6PmV4ur3LNZIH8Qn&`Qc(MPqfI+gUS$|B3COSal?U_ZVZCHNMy zjW%sFHae^oX7hd)fXrD@34J5^tA{#tYZ@3!ss9@JzW*hjxuGXsmRT6e2ZXsVk~Rfu zLBCCZQR~L@OzscmNJP$o!GChkcb84g?=N-@x~@LuO)vv8-eM^YA2u#v(hn^~bR(>& zSg-ZSBH=Mm4t0C%nXlD93;Eef5^z4fk$i26@N6Tdob`J3HOkhBjoOvMaemS5H};YoyV`FfB~ADZc4QcFV78DyliZu# zKWlPOPPCOZHexfX7sxp}9)DCIOi~KsSrhj3lMlR~42|k1MWy;|xfVGY#aN@KI}gvy zjD^L5)u%H~&bWP5YrfBlt;dqAq0axL#N@gPy&i;onW`w+;I)|y&;`ia@8WwV^q?E4 zLE9tW=L&e;8;Nvwb}A_;Nm*HyX^UmRspF@(6!%Qe4&{mVLNx*ly46Ffb5YM9C>7QT zO2Zn#kl%lgPN+@0a zi)wepdHPcaQI4-dS05P{@V+Rhl%b(JsL*F%yiSHwMxW6cu&zL9E zFzHqbb*~x!c~z0hw+6?%>!|X(hbU9C)|HW-FEDAuP?7XA4*pKm^iSOESFoax>oJ|v zVzFDz4bj5@70`kG?&fmz?tEF&Tn`9Y%`%ahQ3}o;?Fmx>{{2$T+4p569-dm|BlCqp z<+ceBr$6Y40?*v*d}mwM^`OBN3a*rd#8eh4I7V~sj>b|-HX*e24=$XR!Bzm_6JAhP;v}+OlWJ0ONA_(%*rvGDgz)Cqg*uu<%yM*-kUS1|T)oHo5IRf?W`iEbn z#;NcG6OK$5%``*$NNAQVN7j;9g`D*`r=RrTz(>Oh%TYCOG4WPipARB6nOSXoxxAx7 zN8nJx9E{#A8)EaU_g*GQ#zJQ|6`n4NC^4-KxUFSzFzG+K`p}9i2>hb``3VIJ!;FtL zS=SL%LmCI2!og)h_;zm2>0r*->0)a6b{EP2q2~}}bXaZ7( zTU$qGf4ffe8$wNwTjk0^Qs>caW$;Xeg#nu$wZOFqc`ZQ;_SdHwisefM%O($iZ@wP7 z(eT2#nS@PC3`AqWYlc%I?{L4LEfilRqY7zVsE~R+aD{j*gK0l?sJoCmj}fUn4ZsrQ zz-K7rsK*V&I4}^?l!zfrQBN%B&U7bxnkmX3VrGtSL1O`_`VF5t()VV-CZRCl71KXh zO*%dDJ=}E3=biSCN*s-{#&8JOgb1XMmcJj09CXRqtb(mcPl$T>(EQpKi>j4eeWC3l zrE9?LSrWhOS}TKGGNP{Zf?F$`OaW9@R0L1)c&D?3ZhIhdN?xAJFmH{!+?1Alpk1=D zZDp4zGMvRmpMy`Kk%RR3vgoouL-*U;!2GDRPJt9kf{@^X??X~v1N*mkLkes6^eAz( zlyB#qr7=UrGHmh72jeB<eGyS|U*+4p3#T^T#8U>qc2AD1wXIkd;FTMqK&2uSbJfA50--Gw}L z?2C~p?!`1$hP2x3Si}5JC)pl8*a~eIfH3*8RH6E-zjjjEzu$b5(je|xnxouayNkY1 zqTQBsX32=++A}g|LCxETXSKmZ9qY(6DcNO|SdJY#t0?J+#t&sRk`JAtTMngd7^4|e zWHupbTyER)YsGf=s-gvFeQw@%21ImgJKgzra6V1+CHRZtdieW;!H5))(P-(nPr#9Q z@aC@NX4RV=Jkj~BUbWVQMGH4cmv;wMVh0+p*G@xTPpj~!lq!E;J-!#ty&AfTCwaAK zQiMV?YclWL!43*SmCFcVwAzu*CufB`UyjFQ3r(Nvj za*c=IogZDZl3?;l=(fRJ7|j8mMl!ed0$J-0)9Y6)3xjMX!w1#++CXRPeKxF!1Dd~) zq?mHPIwKAvUgYV05*-;o!%PWu=KU4YQFfp8?AUGI53%=~c%HZr{f&CGcUZ78!5nMg zklME=Q8Utx4kXs&UYRb?VO6MS8z5h51s;O}_<(|FxNE)^hvE{@%4V?hzMxE#k&^BC z_vZh*I9(ht%Oh;azV{^W%O|I^8k`-oYnX9|{Cy2%E}M$vEx#!I<+tq8k-oK-7;gv8;!P|qK`WI;O2cvXNR1*(38-DbRvS)S`9 zW}N*R8MaQ>Fo6Arqx%PuDVKn6cQs(pW0P5@nh+*EUNqEujPj^dCc9u({C2SDw_X_- zfW3@j%9#Zys4vaT=sgEDGPPc7YjzpG2r|*A3ltd^A(~U@2tcf397_My<*;5-s64d~ zMlZWUn5ok7Nx)A^Q}mm`4m}E6z_&r%nJWXHaiq10;A=C<)}*Cq=TfU!6*@t5!Cop) z(k{aUz~U(?=*2Cg(Nc%(yCiW`={Bq7>VWH=8yas^Y3_I5RwO^&1yRXMD!8KMYFB?R zbw1>Uu6VyJgD1idZ-1YxCpTPD`o=mzHv5@ZP5eHWv<4rt4<)^Jw#S^z2AAWUryLx9 z+@$Q6&_pRcBCYavkG%hEy6bgUFnoUH1Mo3BY zbEalQVLq05kB8{ivQ<%?rd|twb&6xXo<&^^Q)O}`G$#W z=(n}|p&)yH@=N!(Lu?BTny7Hg6bHn2%g%raj6wDVnep}|)jvaA)xDA*=<&g)@|E+K zrHgmM3*p_l2wU4GAsWpqtc~CW%;M|?ab%Vte%Zt}m@)6(Ol~cZTbP%)X&)87K<$^@ zWEy!pPZx%9VpO}ETu*|F=f>Xp5^>_EsVV^Wuu{_&_}OSp<$xGRW+wrD~sI7Ly_JaT`w^d&f>DGk%&Vx?vK#%W<0f5RzefH3JD581R@*@w;&Fl z3elY?UZQFlY+@O5Ic;b5dMUjYfNuZJzozN#j7{uXR%GLe7b$}lJzJa!)#dwTc~8g1 z*(_xi3g@9vNv+bnUL14;6h@;t7578hnszdx;jIJ+LUt}emUP*u*97^ZoF^-x-ulNY zXf%`Oc9%U*-Y_%I-#wkfr^ljSYhST!p%)8*T{*bg;ZR2C*2Bz}SMs4;s!ix^*T`Z1 zqsMfBV{FTa+gIVUo`mH?4YDYqbWx|Luw~VH%~_wd zLSDSYnx3twZ5(GKMxU>F$xF4#DaC#dpxFSJH&3UAW5X1;^dF~c`@P_>%fJ=UbhrU>x*f%hqpZ?izi7)O?*Ud?^hbp03U}*>gL0*UMrEF z1f&>^Ph9Irg-hbUi2RCvtE+f%ehu43D4;7CYrjzpGvohYlYRd80p02ob1y2A*pJ0&H}1QOHY(RN zzYU+9-@_|vR>ITdO7uCI*CjI3pW=uw=N2FioZ)Er^y4lcKkH@gb^=#|%fX zJy*gt1=ONYfQ)tEi9H>u7r+7-3L|}hd}ku>`H9K4 zDZ9UAmf}%_0MI=-`0blrPfU+;##x=GPp{RZQ-Io4Phoc&N8v^xlw%T)u;$*x88(8S z-z`N+Uf^;TrPK?*28iYIaFB4^nZqBtHw&p=_$Gg3<}SnWP3K~?$x_(1n@YZ{h5C=A zlVzL5PeBTX+|SUDoq|{so}FU9eh_}vdVj}I@B_B7DWVjmXzy>avB$Ra?I$pH(qXke z0NoiJWfnoM{+t>C-}Pen>tYBuA)u(p!E$C0o^8YU4Uma5dsbCCk;gAkc$2Br?_24& z85NwgMOGCIIxS6XdFi=`&cL@^C7mAW3$5^?kPS6JSdGz%S5OP}WZ3DA z5_%~)w~L1F{I%u+EJTZEz+D-Pt-$Y*$n!P%vwTgb-FV-?<&_@j|JXQvZi>Y0OiTkX zrcu#?t&s%GupP2sD+g=7fp>RzFFM7X?WLJgKVa=5{YJEO+r&l>cqQr+Gxz?0J)Fr; z{6~gKj3{J66`)zEjQz08lN{oxh)G=M54d!FZFW5Ntw(*D$Yw7=G8DN7A=;lG{qkJz zqf3bQ!LPWE87LEwSivzx<^=dGg|z~mR+*p9+Qrz2h@;Hu4m#SxNk@Y2cwzCo8P8b_ zwJx?1#kd6Z5gNUtf$yGZzef(-UO|lKt0uG*M`V$=XXqo~G8#N)9b3HOVTh>~bPFq| z%5(;+Td`^3=t~4e{o$3XGApdFoeZDwdBpNF0m{1s62-zA)cz-$t4D6aeo2xTMu~XP zqdzKlt16R37i;#rvK&$c`2vNWV7$$pD(G#Mft(%LN~)twi%LP`EY* z#Z{gkq^qXbrP``1!?|oVL4>)KF+)2^A0TaMbjX#-RCGAP_aL$!zhJ%sm;I=GlnCUT zWw4sNgkexRJgK<&BoCVk+7JaL8R*+~0LDC`AN0l!KicIX%KSJK*C2aG>WP<5ja@Hq z{CkmK84|i@>R&bS*vK~c#xm37Y;DHM0eCbNC|n^uc!DG4KwCcSSw~Mof<*4o9(t7( zlikOSB)%549rZHU_+wb2eI6dk*hjj+e%d4_ys3W&5~m2^D#}E{%;ShGcw$Aeg^meU za@jd?M0$G_P)gil=93f=F7y8E3x3MPE48FJvO0&3T;VF@|4# zl{-acGq`XqG|UF_aPOXqa2V6#=I;`=JGRq!nJQe%^plef<3%OKVmh)Cw<_g$oojaE zR4C{MAY$D0ZCHJe;`r}jnQf9Cf-}yc@L6f-jYiQIih$|I{tD{Qn?O$t;$Aky`#ZXH z7KB5MKc)#Wak%q0ZQIEH*FwJ-vT<;o2d*e!i3wqHepAuNu~yDXC#7yFog2O4>>F!d zQT|?{giVo15tsadgFvR~(k&g78~I#@jtz*pdnzQ9R2nta9s?pS9;imJSHjh7R`onjBYdgo^$ z4=6DajaBgE_&cLaXDJ^c(c~A*BlNRrav~igN4eK!Q_cF;;djIRH$LvIgpC}5{mZzQCc<2waQz4_YxBdM-Pi#W} zUZXh%LleNbt_w@MuM&_>*WxqB7p%AEwlspnTWY|!k7#l}lKf7!#1;D{Ng8Pfc`s6Q zHdmO^LcRw9Z9@n9$Z8)Idiprf4kHAtpzT&Wirvva-cRt;E*%UU-Ga96Dd6URJ1I>y zme@B^6p$&EjvU=8nEAbOy=zRS8`MvbD`@PVV85d$e@tZ`=U!_%m!gQB=r(<8`g>2R z|G{cUJAl%8E-D0K&X&F~uJN`6LnWY{pVV&Dd&O>%F}Jx2n*&P3qGRPRlBFaG57COWv$L)Cea5(F13Cz z%5Z6`P_~VC!S5;ji0~Q%rlwYxVgys|kAF-Skak3=v4$h4 zDz!Upd?*3}T8V-cxfr~lvJ&`FzwZ#C4ZsC8LSLF)dH z(3Bs>l_Hlq<5TYCdgum_d7MuTx zL}7?fTgH4DOAhQX#7{=^ofB z>4U($z>a5UQGxP@fDUqthh}n$8&itQQfl3*Km<{X&O(GO6xm`|gbg{4-L5seWy|GY z^Wf3XeWW!!5DM`uO7bQw34;B)y5tW_*CTzUzH|(ZB^hbpOriXmBY%NlRi($6Q)V)1 za&2{p;`<;knbs=eD)}@e_FurfR(u@GM$fWm2qtWC$WxCmaj zh7_S8E&E_@v@zEeCfe%zBcE>^3$q5etc}d&SJ`*0NAk#{ZY#tnXs!ZRrlP3jaA!f8=N=j%FOJbo%Y%KCO@n-1q zcm1+Qusi&!Z5eCIPu%iwuA0Bu4MHR?P z^uGfi#l1ZG3UQ8bxpbKxbk!4e*CatyLxRkjD7_0sl@d&B8Y$-I`-`ApOgB@hQ zw)O<(!mG5mql~e%&8(Bpo(}y{ z625%Th%^V;qk%koRUe##?bgpV0bLwI5ihFRno^+oj(6`@H$US zV@D2-;zB-!z$bDW!B_hVu!lb`?vubv`fbcH8BIt>f>M1P;XW|y3R-w`l({U$V*PQAXxwyB8{oLVQev+Gql5<}A{PWVa{b~?#a9z&N$&0}Wps;*=i9~Yib5jdK zVOx@c{rAa2EIVvO@3tZM>A^_!s_B8g4Scco(OBF=2JeTf)37(240g(`odjBVqz4{o z=z}}!dO|~W9T!6BAydSVz+~j=$e7+p0x84fz@nisVlvTc{ucMa!Xmk`x9E;-Kb-D=0$v~byC>jIndm_$9jr&{pV^OaV9)weT zGsoS5OEuJx0k6Q86NT8FX~UqlfiUJ4V=<37O6e4>faF+kgr-wn2 z(Tb?E$D=FK#P@c?T96vH)a7M?zh}X?7_wA&|rhIno<{JQatbjd(sl${Od3j%OlK8y8_f#tYT(MpF_& zVv2)*;9yMeTHY8$$DG(T2*)Q?L+2PB-unIwGWa7)S7BD7a~K<>!)LC6NnE>%ymFX9 z&AiaHo;N}~6=D{Vm_RKeJyaM+oPo!`NHwH$dM|BTW^CQvO#1D)6gdmX5 zzeFOrDB}4+HIMy$(qDh4vX=WfhEe2xR}42r`4+W124u;X-r4F&Yz6mgL&G+B|J z=NvsR{R|1sPm*xBNP*wRRK>mtRWQGIsPkb)U zN91|w_8goprgT#}i%d48W}8`~Q4MX!pe3iVH`RjoheYGs2O<&Ru;9nj7JT|gCZ_(8 zg9kc>VCRIY`1=VG%{4v6_mZi3Hq@!)gY{2V!J%j3keyb9$v?XqJ`Fn6k6%Z z-)6_1HNB9XRg5nV6#=}RyV#Nilbv0RCA$j{6{1I^mkP;QW@K>7C7p+Ji^@WIw2*if zleLn|v#T(Fa5R!9S4Wp96{h}_jzbwXg!pRUCViidy48GfU{X~~>)?yuj~kIq!d8q*YE$5)Ug1cZQXONPdg9|>)37el zg4HAl$E?agt!h5lJCV-Ms5p#o=_|6Cnr{_dJNG?P72n(+jsx-ecxgkv5ZEz6S{TR( zJPXQJD-!wqv8(OFiBl+5K)6YZ5h%9iWMs-t!zZx~k*{Fg#oMpio5;>w(iUlEuqlzukPlFmH77wRe zkW^$xW%|v6BApF=3S!Wz5Mxl_7zqra$EYIK>r`kSp%vZolJhJ$oNE*2t*?g)p(Heq zktk$?w{+K0XSy8l@ld02s78!n$=|Z&aTiW*EunY~w0}J^=6&cnpFrt7LGkc0xs|fA zj~W?8Hsp};E?NLNPO60Y8^G!$j!xy9#ULkah;vI?B@$jKEN(?b&1qJmS-2KfG9V6S z^1w9Y&(TnYS1nkFXcC_$dMNDAG&{FZdVT2J)DF_He!|uiGjjPmL=qAOQ>$aw&J46& zlIm>Xqx5lFLP`AjQ2vjSu;RFdKPQy(?WvOw zAkl5L({Dci?9UG~3fC%H2Q{s3Pd34iGFnXMBa6zI6RxMc@wqB8+ORv*D)PwFCWLw@ za3IwTZfFjrGD;u;rKPZube+Ug**1wWz)R)8fiyGH`CB=0e(k6cLgh^77KHpDxI6;JS*D^sOhW1DOxY2EQ@o`=acUjI`@r3weX;{ zq|kkIi1J|PW0P|w;fj#cPF!E)v=e0`ih}O%Yc=hJ^F9J*_K!w0jTys!$bgcbOUYE{ z`*>=gCxI8@uY=uW#paVnRE;uV(_=CCVSO6L{hf!~Dj8tPHA3O%jivpA@Z_>&>^x)P z2fla)aUEWt?oWP+m`Zhw`}uOEi1MI&Q%*ba4_7kn1lMmZs(WMez#zoUK8;nQ;&A9l z4*GqSj+)^*)PcFuxY&wrr?}qqm(Ktef{6Y)Yv_c`G0ydVE&f01Sqm%e)BJYN|j7sX@VUT*Cw-O0C99Hrso zQYd$_L=re03mx8@%Y;{5X+NvXcqiV@aJh1tO6A)bDfzk1(OGfQHr`y4Cf=9x<=(yY zzV2k}NBG-`u<k%I90A_ZaDh@!X-g7D3IqS4XUiYHfR zQQhglU&oA?a&I&yxAVfjBr|G<>hRq1Q_zI^;FXU4NJ%folvSx{*EkS4Cvy>6Edcja z*JJ<5LhL33zh@l-;$ArhpD-`%7!iTTKRt!7PMD!Mx7WpGBFaMw!n3$iLD;OSH`etJ z#))Dj(o*tp$Kq6|eKh!s1b;mr72-)0^2^`e^XPf3(P8zF2;}FP5gw?++MU^0o9e)8 ztqiC%BLS1{i@~@!B_iHRz$0D5(9hS7qk0bnX650opYyTq(HI0NY*3R3+rboIinO#e)U8|hn$@Rnv+kCF3yF;rg_gtu zKa|-pKBwSH!j#%Bev5Ti7+x>+5EEh8k6Xsrxwi48!YUTS+`FS1LUsFl}G6FBgs9Nkk&IBC;STg$Q3pI>m8H zZ4>L{%AzDa9EN}MlGEi%9|eHfY(k$*{^h&xWb9; zyp8?b`Axa zjc-#hZ)*WGR9|_hfybAhK|)3`27H->hknk2S*t!nNoer-yfd4(+1!xTA&_mhR0HQy1}k#ocQAqLIH6udYkSjK6ZxqplAg zYwUwMz6x}mcM`SVJq;_RMXgbxTMbW)|LQdQeQ}2Nw&UXy#dy4xFV^mIe&Ud+Rbxye z9^mX|yvxqU{XF9q89o=%N%vnn_qBMgtySLKQi4!YzKDyD{>NvSv6H(rvPS$5ta^SB z8jEfCC9zn{srX-{;YP%HejM1IT#OUkXOjh+Ynw!Jvp~dW179CCiV?rLb4aka8XkHjCUp$LlMOtvExrg|1~rOuEr`#z!kAEmYztjoWQ-=! z@5w#G@!8#>IGI=glS2uA4<(vc^}^y_!6?o$;Yg;_Mv(l!z4HK!s>uKNcl&OV&8BD5 z8=xRPC@2V06cj5eHU!094$pe_a#rl($=Mb7p9rWF6%;{w7o`XY={?!>x-Gl;|9-Q( zVd22jm;fR3fyur%GjHDX`TgFTHzSd>Fa9!bN6Npl`AU8lu6+E4=Xg*1VWm(gJjXE( zCn6$0eC6Y{lSn!XLb&JecX{>!e_QSFJPB<#C@dC$V}jMta$9N7u#W-Uw8Mkc2nkdn zj0AY!LmwSGP`Y^KX0fL(+i8`E=Z#VgDlxG0cjY{3z@SqhHB?I;fVY3Fz=**SxI9UX z|Lo*_V3pK|h|5n{gc8V#E>FwnH&9ep#B}%efWi)<{7T|Ij9HQPS|3k%s0*gDzWS4V z6eu* z8|yLQj4+f}*-%>P604`{>H6#{2O1ijm@=mj1K!NVH4DqItk8}qvmOJ?I-Gr0C?cua zgaoS5H$sDaQu_91)Z(%^CA>}Fg;gTvtvi^}k49K65`|FT`EM7gi1Hi4 zV@pSVLny){;r^llCft@DEVfmcx1j(%48 z1reb-te6^)=f5bxf-Hxaa?auTU;bwiS<%to5R!s&`^acKH7poajV=Tmlz3u60dz5; z7#-ro#5v`dG&Tx1C8{yw&ubT8#Ou6n0WB+T$Uc$d3;AhEU`Km1AZV8FK=*Ks*r#^tqB2zYKJVgt z`#;*|b2fZSw)dU;`}3Vs8D2F!8gC8K!TekIyx;6&g0u*pknEzgF!OQDw z(JL(&OD37|$+BX6knhI&o8#!(3f%u~6<)qE2@zEdn7X_cA6*xRaYy>)y#kqbU&YUK9aGV>6VLZFhW8 zj34rxp3fPMLTC7fu(q}qd3kw=i;ENWub`kn{3`kBibQe(Ao@ju6~zAi`$MaxhT0CC zGzy_egf}Ti>&y=(l?N#A;h#WU&q44LZLxR<9aBL5LAClJ%k~&l{Q0Z#Z!bDBjYr~O zx{1T``*_lt56m&j3m@X%ED-U^sbUM1<69?=6;dHQwsfdMDBkj@;GW(Cb5~igr4i_F zXu%ynREl=a0%NFNDYj}6uz$K&1dSh+P0iw4Iu9)fCxxNj?hb2?SC8TN>SB zN@=K8fzk#S)O6oop=zOc@QS{?XJHK~AG{KFs!@ggRSwt{D&#)a4R3svkJoqb8=dxf zLnz|gQ3@gTbN7xl$HJh;3X7xoLro8J^;wS^ogA0h%&sG|BnYL=)YDzXcqdQ(G6 zw2SkBvXASUL90Tu+YMe>TdPwdko;9wkzzg4j5pG?hN zUtZO^xXFzcib`0JQv9+$Vx@4JC~w0IbV7E#8nl7hLvK=)Ep$J=2gjW&cnn=vN$w7+ zFj3^ck;q0H1T}2soT7(AW^-fTb=n-we##`fY z;|IC;rBQ*^52WJbMfrGST|J`0wPJyrB2o%@`z|hm-^;hosu0G;#zL)Dd+MV^a)MA? zT#Oz)dO)w&du{$2EJ=2HL>px#jSUG8Ej+d4FhoPXY6yoh^H@ZmIoYpD}%w`nbe2)mgE5}rCk$8Cy#J0Rjg|HDZLQHrQ^Y(#u{DpsuI zvZFlh*gO7jI8OXM2T0(dS^)8EhK|KvOnltYc=M!RQUE@^I1+IjiFPgrkw#8WJBKTg z90DsO^z%!86J{(b#neR=2nsb|^KFS(dsi~D9_@xz*PEfH_LdSCfc+1q;pcl(ux@G` zQUd5ojS35IO2TjVrC`tB5-~23MO_8Hzbyq>52hmL{$vaeQ^7{E>T6fUqJYBNFeL_Y z+-Tdee{OF)g0ez<6KEm9-9o~4DM{zN?`M;P>_}N)MchtfS!oj`j+C2b^2=LF)RH@I z4NyZ0052HBLjErnhfpLEB6V;#*f9H7QU>XojuOb*6^f(Ts6ibmeT#P1;L^8qNEvja zUrHdh-;;`8sqDAhnt&uK`q8~ZaNzz_?7A-%8B`yVbqZYAHxyZ<#1uS|hJW@Bz@Sr2 z$oxkdavx5^+vkS~dB7N9!2YMYBm2P=Jj@^4@g9BN+aF6IgPZb@Dt&*q=t@OLC*p58j580>4(%@V|^@e9+Q*Tc7#f3(yA6#c@Ne-%D zS%}&m-(_#P!&7fKJW>XEL#()Y(wstlP<=@mlC7tx`gmKcDY_om{fI}>n5xatt4kTZ{; zn7w#q9C98^#UuTLgmT5!9&<30{W;W)EJ%Qe4ASDhkr5bfCW(SNSn_f3ItsH-au6~e z>yEkOLr4LoPLlA1;}@2&J;=d(u<=^@!S=j4;lv@4*mHLR(u`DWhY2%$c1SoLYr*fj z`P&P_cK+s|y?}&_t4@zV(PP~(txo_cY5vz8=GP0K9(42h^|<{{s2m3*1z`^j^50!! zhK?J7_hL`DhnZIUL*wCbC%Z$A*^k$Fyd69j#%t*Z+w+szFq017+?j-uC%dEa=^j{g zof$n%GQ{a5p2;YAU z_WNJqdhPfjbFxv@s`UH29W-7`iKH#C?ATz(Gpnod%9JGReLNMtqg6D}0sWH$vH9mp zjQywxr}s2rV$VR#7$1$@d#Z89YdLTS>hQooBknyX5doT*_s7x7UEkCpYt?N?;EDpb3W!@ zR`R^=!uZ_Do|T6@OB4FNosSBB&FBf@#D|J$=2{E%5rHUq_7uE!afDEm9vc&hg8Vx4 zemNVB1|42JHx#?`t+@H~V)P?BQ+yC6BUj2 ze66qEaM(UOI%f&7ndGadmRWH6yZJbi%%xwHp~<(RHQ%%KT2J^VGd%7wB9Ekz<~SPP z6lOkdFFL(KD4;eP7p6tucmvYA2jlfoq>MFrHjWq2z~3{`fQN^MAlYod=`lK1;eW?VSbLo5K-5K_o46O- z|31RY;pM{VWqZ3Dy%UYdc{~jRj8wq|F;I9JsmGup&$&e|riN z9R|mdiYl+jiqsb~@$jk|+&4ZJ(}x8kEnI^^J&agnuuMR7i+W5vzAK;D^jc5*K*U74QAK zIHHblHKF`ypf)7x4k>{`8T5GRQgD9on;EOsDvdP9-S9Vryugfaks1E|D|GsA2u18k zA|BN@1S==UqNvu1I1(H}?%j)8E!~HEEvp;v-IRh`duUKcBjNH5<+$>jN-nn-ArnfH8g`~a3qFm5Upy#&N?^FPctBdj=w%&MHPP+!~Us*jSkgfXq*-% zwE|lTZP;5$Dknd6^A6aeh)5`(c76=r8XbiGujF87xl4S!Eeel>d0v66KPkGLmhyTB zmS@{oCl=`sBq6C6+4rRZw5qWgYw~S$f4Y(e%vh5aF*InFQ*_QDp=*1d6}*O~8O2vY%EaDsO6TtgCj6E+#HhihyOYsW(S(6Lf-!4(32ykd z3NY|KTS^iNcwWD!9&j+l)lO7XUY!xEM|q6{IZYI2atjM0;W0#mttB=w&8$zT3VSM= zF^0;d+18Aud3LzyeuA-JgvAd{>0Y3n?zV zuwRH4MyhTWo{B@^1P7^ccA_3;y%IZ%ZTQJoKZs~b)0r_^@$FAG)vGmm6es>Bm2NVe z>cKuzwo@Z@=uUB7Oae-M6V;J0t;j=O^WaA+qkM|{4d=z;lM784`dkJjP)~xi5^JfP zvc=S)-~Tp<^Om3PrgNQ%)}V;;Z573%$>~N|uoma{55@mo5`kMj$;CPgf1Bcj&Dw$p z%EO^?Is{U5w@|vWYWQA9FT?gmenWU{>F7me0(ELoGr>OULn9M{V7WgYm(D81f}7$n z^q;?Bhh2r8_ov{4)fP+|8i`dKEqH~5`LF;rGOL`Jc~dG*wKw95`4*T0RH&i${?H|{ z7#-Y<0Uwqi%%njW^;KWpkbvjr=VN}36YCyG!`6+ZxPN^EjYU+E+knUlYAbF1hEU`s zwL$TK#Rp%CaWF!*{G_fK?8eR6v?S#gUF`i79NJ%z@V3>u$ZjV^rq zNDnj@)gwB|h+lVB;E}a;Lc!0kX~w6wry#k>hN~A?uQa)X2M_wEXu^=T=k zTZ5JtyJO;vOf1NEB07l1_&yPMbBGS+C$lkncogQGAA;2B*|3RzzwMdSmTw5#lh&14 zTkBN|^`CU^tqx*5Vo{9c1h%8G<*%gokI-4aAvBxK;u}JhN`;b=5;Qb4K&R7ruaHQN z58Ph4uKSK_YHE<4o{nJZ^V-5mqY#cJg>c2>7(Bnig2%>0Bspj#zG8u07A z$yl_e3_}J-V$Ze;O!%@4cbp%K8RNoGPfC)4l!OoxOkP=9iieihASy^rD%2Xm{JIE_(jdpn zhl_5U6L|!MkUM(umdCBYoa^Ipb)FNy0s+=}}BdM;s}yOV*d-jf@tox-kY@NxA4{)*?tpndo-on)mbY z`93SgoF0ZBt}|oGyIJ_UMuozMl8{BpK@us3dQxn(G>|?vHxDmtqx>hOe96=VoS&>m zE)Ce-$PbS`nYs1o|85}~DTPNHgj#GaTsAlyUtST3@o#3~(J5)@*<#0l8M!EE0!}9d zaqZL?Q3rV3Gg7^JYjFV{*lb1dKYL)q#xh*CgcN@gf+mfM!JLajF>ZPW%1lAnIMs|o z5-7Wy^=PQEVc=^yNbehle~phIL67p5%GIcFW7_;;ytj+eK}xkK3koZal&|ggra)C| z#YLYMVsib02Q!1TxcYPdEC3Xuo?sK{iGPg>J+FaVS^Rm>nI+dW!a%%<^LEU6~bdn zhbn~BZ+D~q^5Kiv(H;F9 z{0==Q^p}1P4(T2byhsVB(m`$Kw8TJM)=i7&R#l6Kj{b0*4;2RvhWWn`mvxXr$Zw-Q zxhf7rBUC7EbYpO$9+$k6gZS>Dcy(kje)zQk(NuS4|4<5ZZz_*d^w^MZ$HYM)cw}}C z@{C5@(_M+-b4uZ&IL*B)4dYmQ{yM}xn}acDM`G64P;`4W8&=An zw#J(|o(ds9@Qb3le|_&je7dy;l`Jz-pFOsx5#2~`;mWiwyAiv}o#>w&fK&7>n0LVT zyANpFgOfoaME=_lfpUWwQPJ-V8k@D{jIC9~NNrzwU^7+{m5k?_UH2Ep=Xfu zOqt@bkyo7SPRj1pALZim&q`s_YVhhsQOL@vLpsH?|694(O;LUM!U%-$Iu$~RA}HXZ z8arNJSA__Q%a3=aVCML6L{r%dt(3}i=9Q#0Y1|m~Rt~y}G&kbLF=liRRf>(E|Gl;x zgWk+V?>BR>B-c*)8i53@lCqY{gW{P=O6u$1m0~0*?R!bc3*|=(62~JB3 z!m}en@a7K|-2Qci@c;L@1(e4{*hXz**41%{RJtk8@~BPZVK3*Idj!U`);IdPK5W{^4jaQ;EQ9mLUL9^4ibx90{MNxaV zk%NQMz_UBVOa$`7Z+v(b1b?qiZCkW&QpQ9$R}D%bh;5Bg@oUvUBy$|xj*Cn zio@aS>6+GSec}7UsK0t*sRggn`2F3!dNEEdFLdJ5&6T)zfdzZ39GJbO8q3O@ zczs1Vo?cyrt@(|(nEK1bg$``UsKrxjs>O<<2vFh91%rRO5$zwYYsL z^>;=!b`~_^jzy(#wDBRwiGV0aQqCux5sp_cjUib>=P6gJKhItqgU3lO;Bj^1T}iki zO^@?PNqlZZsK{r@AA|Fll}_JcGtx+Nybae!(f0oWCz(PBG&H&J%7$trCIw>*KYi7Z z;&yf@cK=p`ZADIg=_XLmy%J&31~FY~TXqvn5nA+*)?#zEm99}^SU5$92mIed+(Ln?2b$2*=)i$G zH_quE0K3hJ+&ULVl8|AySaJW{Vtk)tM>k$dniN0Y-5E0~2z?bZ7fVa{HvY>?&%SfsNJMyjKEd@^(<}{OD zij^BI_{TRDSbd-Y${?M1yZSFu5Eo`zky+uu4OE_={Av|SEl1{tPm8c7+lDVlNZwp* zN1BQ14uy48R4QD0S`eCRZP-uB`{+{x(9qb71C+-XkZ{mgVaFB=e<=%Wr#icZ@+2@+ zkAajYv)7bk-kv7>R&K-1pO@f`y*%BOQzw!9IYe7hwBSc-!!NI{#`|0AP(?y3cTGxa zAB(9Cy}GUj8>vs?Cvm)r=8|9Q@zjr%nEgvVO03insoiYOX~J`>s_@R1TI?;SPE)54 zZRZ724Cd`?Ktr=zD2K(YBvDgdL4DM2s`qVuv)Nwo!^znjD{$_cc^LLa4$75U%)Zu) z6ax?vtih*O#bD+|5tuyKgpaO@#qIqJc>IEBymW2^R^6F^zL6R{dR`PZJ&=kot}!Fo zKip|a;PuWo3@tSuDQx@u%lJt(Yn z;Oi{v$EY9XtrnJKHj(n^y?1AXic3jj_4R!Nu(QyH+lGdV2ia^NWKd$>`f`kTJ`)?N zS}?g^5R{Zo8dfC|Nf+m&QwRez3e4SDC5|Wc55};b!5}>gZ;)aKt-3YAEi`zf_cbBw znI72pKpK9$EkR7L;DJ(2y(j4+D6JtugvSK#O?e>~9#H6%!)Rt@jh`@+Q0^dsgZ>{; z1{ZG%X+Tx+D=Sh$d0`-asOuyRg{NpTmkbWWl1Xt`G&v6U4K~7IcZu&0#m)W~uL{*G z000@LNkl117$&S*`%bUCTn#1Dc9k1cwC)Ye}kc@9HFwzx;63jk6 zpPGONFNnmfE6gY^wIjchJh4tn`5=tv5)Z7sks01SSZwJqw08*R-bHbr7)AELuwKE4 z45GkA8kAUYLjozpc090x%3V!)>>bGcpO*N)FB8t>R$NEr%jqK%CzM^_wvkaJNX25& z_3^lAKmckgZLN9L3SUp@T)w9k{a?+(1^-t9RiFmXjg3IxFi$vV^$iu}@FkV&^P@uH zqzY2f=tg{MAhti=9S0st#hN>kaQ~2Ck^jD1u!>h&%kiQkidQ%Zja=VyswjV1X=KCs zOqOiVG_&?gb&D-rblfVxalZPD*NP8(`*f*Idc1vQ48Ei~GxutgChJl+o8=MZzKy+m?2-2W)3p7R4bHeC10s+!P$dJY-_xDAjy z*@=FJ`p{Ot{LANjJ@rq%>&1$P{$cw3u^)f`+#erjY|a*3(BFiAO^(Hk%Oj9eVMkE z5{)WUG`Rlj;i*I->6*|8d~(5ytDwq;>gp75S<_CBXh5f zLyVHA_GKaFxeUCqwNChPHn*Uj+#`cM!o8SU!&5FuYWH=V{_=!pDES@8+2_OeOG6II z_;nZY>cS#~Kb?W(7qXD@VkW|$Jb-m|bSH0oefaL}V2Pg%$BPu(DpDY8Eln8uY9?Zz z&!jN25dUljM$9h7`Gdl6?_d)?TWdkWbD0=6vjFup0`O+Nt5ID9HQ8siC4O!&u(%A?~5 zj~0G=K-~Um9-{x5iDb&dl;<-L`*JpR){_TsxI(evKsa26ESIgRz!p+)S;4pS_lLxS zm8FPy@?e>UKE59d3YszFl2{CkR%84-If$b&dWe)*B?&J6h_c|D5j9k{e5VBd?t}A* zgBNx2U|L%1qPWbjHnIo4hLgrh8QYPgiv)%S3ZuHT2}7o5A&%+{r#Jqe85lXgTs#2q zg&`IupnH|^in0W+^eDpQCm9!a2o8=J5{abCa`JTb-X2W| z$22fbUs(Ztpauiub@*^yCCd4`2o~ynogaCO4pgG3!j6rkEG9+=V00=Cek_nUnz13@ z26KE6ZXIA0Z%3{;BNQK95DKl!BN+R4pd4naP)_h+>(fZji8%Jbel?`HTFR_Ke!l+f z2=oe7iA`XmLbaIE&nSfO!x6zKl^77GNACnZ(i08n9;Ly`-|AtC3c#fPbR7-&PDS1K0E8%~*I@Bx||8%SGCg!gQF>LytMDEI8wpY^0N7 zb~>4}-Yozxc6!4=BhnHA@CjYJKf4|)i=F6|5(Dh~miU z^~Hs6)mmLBWAjM?=4qX`jgG_!s>{5z`i#rWxUQE$ytO?XzE)gMVa9}L@#W+M+}yey zhQ|-Pt;=pgsF~`_z+mxKk-z6UGc5>bMp7gxSfBDXBH4f>QpS1V9o|Xhrcsd?nXE-T zh5hn{QMjeMPAm+v?8Z2ZkJC_`Re1B#7-}nFVhY+MYBy`iErq|2@{58*ax}Ov@qeRp zvZB){uxwWi?wVbQoA^NtwLzU$A*RA}+YjV7@YMP6K6#?IqRxS5^H(25PktQK%u_kIuVc?DCG^xRJrDQD&f5UlpNst2c)mY{uTZp*a>}=8frI)i zF`I?D0x|nQ(s`hw(~=A^E=3PhgaONMPZWzcolE0Bzin5ml~}s95@X)Y!znZJkx8Ap zjvt~+B$6(Vzo^@(@pWlJj-^3#V`c+>=I@|fE_}S3gnjlvhF@$5@tX$1e{HBkT8a?| z9!|v%B;Y6V2ZnaH=rpIVD94_1C*Hd;5xb|QV8K-}Xt27(XMJqMIO)wjv8N#!ey_p< ztBZtho|@r_6#dcw>)k_AC{OGzrYt#|arKuLL`EC%(>NI>0 zALQN612t(;n9m?u=npN*m+kn7E^jINiksMzB(MJ za$?4$1ZhHhk#$@%ZC>6KV21!Dq#Y1vVZ_SZH8( zaV^8{947tl<7EO#8ktB+?ltKq+&?A~EAC9j`>dcj+_+>R=wiMJl|=<9UUx66K(Fq>*nUqEex|V2-;#)boJnyb zzy7eY*)J^^Ywu3NuD>N?)y?t9D`~>hE2~6(oAP-H>Qrj1rh2pU?qqDEus*mb62X+Q zA8e|D%BaVVhtja&mU#3skqGACyrh6BI(#cPKeA=$ysqAfN0ydDOV_NRaCZGI3Ad3l z%+t+0@BBRV&g)&QD(~VCV5m$?foe=3MfB_#ULKqMc+)|d@ZRQXEX}myvCHD9-X&wp z9m!ZYDGsC9FL~o{c7CgA#xqwWU<1|3-XRKHzrX^UUXSmn{I*X^#a)AfQQpvueu+UC z*V}~Qu{!8?3TGu7 zFnw$k*8Wt9ukxDl(dB09Ut7e!x042iV92Qk(RK~o=~7=D9-tDdN$xGNBQ8Ws-J}9P z7g(teGvS4?5%`e${Oi)Rq%5|eZ)y;(9%e!?DWQRSC3Y2Bp$IYHt?^N~_rfTQPf+9Y zT@45cR6*yRnWClp@YX((&Vd-8sgJ$5ACc6Qg8qb(@Oy5&4y*F5DCg;aWZ#>F+c=VMzvqwW zg|rNe>k){GYCC@7#Rtf4Oj;lY$7r#y#EM;&F7ymhVQH>Syfq0oYH)G)0EB53IAC$$ z>+B|Is1rReIRFPrY{+QzC~dste_xXdEAp(clkzh@)qp&U4LhrNn*}}klhD5~+bTX( zXa8gAp5--m{9HkCAjPStS&w0fIE>;g3@dSuXxV!GX3n>Wveda1se*35#==)I__WmOc8#h~U z-sj~22_quCbiW>yZ~a1*SaP6=^3;=7-+-48rV(%CeyQL+C$&N;<^169G%M;I_=TsA zlE0BX0+1f5L4KtZEAs6ao}k0}+$J(xzEelT^;!PSVGd+@nRj|}K@h#;mPwf(sA!RK&Qj6h92E+%cP)y3z&m^?% z;_oJ@&J9U0U?g43O4mw?*U(rkmSs`AND&<0&46O61HbU=I1Y>NN5P(E)`{}vtteJg z8g^KkVWvoqG;8rqRwH-@F2{dJya9ov6|SWEHubz{d~{(1E_@>s-}1s7B0QiQDR+tH zAgnHG!PYAc*ljhSgvxJRyc^?}G$B4jiR-&5@$3!-=3fv97ge}1|5t^l&N0BM(V@6r zfw#|6VcHrS<{z-*=2KcwQm4RA)Q0lw+!#&uU*mM)yBr%*s2qox)mW0zgjyZ ze=M`$te&Bm)+Ye}*;)m2axk{-uB86j2@~}}PmT=7pfEM{2M&~0+3@1VI^1G-6hU1NZdSuo1IW0L|`Qn~ilxbW1LI`6=! zz%x`&c4pP#wC5>bJ!wDKu7$?-zm*!N5c7sGK{vtWK6~CjtWlVKf>&c`lKh z04SA86c!dDIXSt_+fH`;6MC&g*rJQ;EQvQyKlo)j~yBAg}5{1a~ilr zkPojvadZ&>a5!9_GvC`6AOCcSfryUzmy6-B_%T8`V*hOC9k^P=8$3GZJYaW@2gj40 zz3FZB>x-vKiMdmgaCHw2?pshQR<0bJ8ieP^MPkL4a@2(c;u13{kT2w*o;OnWUeEB8 zZ*iZ)ge#GMzWnC<^Dq5x#g{I|SGIgP`{g)s{MpVM|3mKS%R^2o!%m!sCm4sRJB#a-aL2M5ac_oh?eSGT-2-tgFXErdJ$av0t;9a6W5fItm$AL)k8+beMK zd<$=8=JCB?+<1^o&d$>CimbIE zDHyeBf;;G6TK(ph+dA32G|)BSj|tI*hnCuei2d_rvKy&M=cJ-^#@jb8{R5z45y08XJ;tM%cD!ku{uU#*5^)qP5f%y`w*4ATTH*L*=C;Xmf*G7HI;Qv z)mD7-35%pIGVASiFIk;Gs-@psV??CBc=-k1O=}%B`zV;5-2_p zZ;L!GhT zrHWCjs?9Y$ImXAS^d~3f?T5L|fh!&DuI@8LWR7j8ZTiD1^-$m^y`j*r*n-il z6YRp&CREras}SdD?`L|$`+!i{hyDpa+Y%{gE2Oa%y_$sSt;uthUt}-WY<=7uL=tZn zt&H$!#wH5btvd$V#$mEoJ6Q7<_BKshmvBEqc}F*Xl_|&Aw3wdA0R&*HyX<+X&f!<4 zw(19hmg9;B{G%$0$rl$V-#F>>?Swrt{?h!p1p5+| zC^)MG1(rtb`m5~@;;l4|SU((O=;r1|s;xaBoo0n)FS;YNyvv+#E3**|Rx{&}uxj>& zkQQz^D#$+s4i{tUhfxh@kqapYgTWvWNS6MD)cL3NnDo~Y%SWjVtq|zP>h_5z>NCq; zlz#`W2QqKZ4Hrq^T)?Q2`Qb+J7-VSaV%Wy|WLy)Z^C4)?zGKS(e zExN)3rg%R1t~Jp^?_)2$^y(XskRlhYooDHC5z;uQ>L!04a@v275S9;*)17;T#?~+* z;HB^$^ri-X0g^g6F_QOg(uX|4vaC0jh~HRk&s(0rM4piIhuBiF@%;<+gT8%_CtU&w zCy%KLH&(4YLe<7a*|y=1dC);%<9052{Jv;7ygr-LaLu>#m6G5hC#5kn#Bo7==h_Mn zK`};w3N}PcgVF%2iGbklpY`XR(&5~9c*>n0G4GHSCjEg5-8o?!UTxky}M}kt|=r?JK77}AiyHl!197nBV zgB+1S1=o5Y5TM!TEISB-#*A<#)pqy(95&|FH^~L*A2vuORhJ-I1-=$UqWqo+j=;Gs zII5!kdzTE_lqP#BVTLkWe&=bwL|SH!$uan-H&EBV8m;pW$p=skQ0RmY5aa`Jc&a^|6goaqTxmJ!lKa&PiZ0)c?X<4<8Qf4J?@I-*CnK}^5Z%>HG- zZYdzU_{_GpG!u6)O*6kB&(GxQay9PJ{ibG>zAohg06tPDypQRl4tD?k(``LXRj|{c zp6+fd2M5SFa8wn5SaBP7=mVSmtG~a$8VdFK`WTk0a9gP;#;ep#TI&eJ>}oxUv?Dz| z9RvpRMn%Rsm6jBpDsWZZ;WF2#M*H1>mXdWF=bFVcZPn^I<+%nzgr-3FeszAVLnqc{ z9SE>wCpNm{i?Aey{QzCGnheh?ynEMDd~HE9pDAMws;sQE*b)ilRK^YYGxv2;#Gv4q zak - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/15.handling-attachments/src/main/webapp/index.html b/samples/15.handling-attachments/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/15.handling-attachments/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -

-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
-
-
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java b/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java deleted file mode 100644 index da78b30bc..000000000 --- a/samples/15.handling-attachments/src/test/java/com/microsoft/bot/sample/attachments/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.attachments; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/16.proactive-messages/LICENSE b/samples/16.proactive-messages/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/16.proactive-messages/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/16.proactive-messages/README.md b/samples/16.proactive-messages/README.md deleted file mode 100644 index 06a53ef39..000000000 --- a/samples/16.proactive-messages/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Proactive messages - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to send proactive messages to users by capturing a conversation reference, then using it later to initialize outbound messages. - -## Concepts introduced in this sample - -Typically, each message that a bot sends to the user directly relates to the user's prior input. In some cases, a bot may need to send the user a message that is not directly related to the current topic of conversation. These types of messages are called proactive messages. - -Proactive messages can be useful in a variety of scenarios. If a bot sets a timer or reminder, it will need to notify the user when the time arrives. Or, if a bot receives a notification from an external system, it may need to communicate that information to the user immediately. For example, if the user has previously asked the bot to monitor the price of a product, the bot can alert the user if the price of the product has dropped by 20%. Or, if a bot requires some time to compile a response to the user's question, it may inform the user of the delay and allow the conversation to continue in the meantime. When the bot finishes compiling the response to the question, it will share that information with the user. - -This project has a notify endpoint that will trigger the proactive messages to be sent to -all users who have previously messaged the bot. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-proactive-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "proactiveBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="proactiveBotPlan" newWebAppName="proactiveBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="proactiveBot" newAppServicePlanName="proactiveBotPlan" appServicePlanLocation="westus" --name "proactiveBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/16.proactive-messages/bin/LICENSE b/samples/16.proactive-messages/bin/LICENSE deleted file mode 100644 index 09d2ba6d8..000000000 --- a/samples/16.proactive-messages/bin/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2018 Dave Taniguchi - -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. diff --git a/samples/16.proactive-messages/bin/README.md b/samples/16.proactive-messages/bin/README.md deleted file mode 100644 index 131a86b65..000000000 --- a/samples/16.proactive-messages/bin/README.md +++ /dev/null @@ -1,90 +0,0 @@ -# Spring Boot EchoBot - -This demonstrates how to create a Bot using the Bot Framework 4 SDK Preview for Java in Azure. - -This sample is a Spring Boot app and uses the Azure CLI and azure-webapp Maven plugin to deploy to Azure. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\springechobot-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or Powershell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment create --name "echoBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters groupName="" botId="" appId="" appSecret=""` - -#### To an existing Resource Group -`az group deployment create --name "echoBotDeploy" --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters botId="" appId="" appSecret=""` - -### 5. Update the pom.xml -In pom.xml update the following nodes under azure-webapp-maven-plugin -- `resourceGroup` using the `` used above -- `appName` using the `` used above - -### 6. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 7. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #7 if you make changes to the bot. - - -## Further reading - -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) diff --git a/samples/16.proactive-messages/bin/deploymentTemplates/new-rg-parameters.json b/samples/16.proactive-messages/bin/deploymentTemplates/new-rg-parameters.json deleted file mode 100644 index ead339093..000000000 --- a/samples/16.proactive-messages/bin/deploymentTemplates/new-rg-parameters.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "value": "" - }, - "groupName": { - "value": "" - }, - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "newAppServicePlanLocation": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/16.proactive-messages/bin/deploymentTemplates/preexisting-rg-parameters.json b/samples/16.proactive-messages/bin/deploymentTemplates/preexisting-rg-parameters.json deleted file mode 100644 index b6f5114fc..000000000 --- a/samples/16.proactive-messages/bin/deploymentTemplates/preexisting-rg-parameters.json +++ /dev/null @@ -1,39 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "value": "" - }, - "appSecret": { - "value": "" - }, - "botId": { - "value": "" - }, - "botSku": { - "value": "" - }, - "newAppServicePlanName": { - "value": "" - }, - "newAppServicePlanSku": { - "value": { - "name": "S1", - "tier": "Standard", - "size": "S1", - "family": "S", - "capacity": 1 - } - }, - "appServicePlanLocation": { - "value": "" - }, - "existingAppServicePlan": { - "value": "" - }, - "newWebAppName": { - "value": "" - } - } -} \ No newline at end of file diff --git a/samples/16.proactive-messages/bin/deploymentTemplates/template-with-new-rg.json b/samples/16.proactive-messages/bin/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index dcd6260a5..000000000 --- a/samples/16.proactive-messages/bin/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,191 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "F0", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "resourcesLocation": "[deployment().location]", - "effectiveGroupLocation": "[if(empty(parameters('groupLocation')), variables('resourcesLocation'), parameters('groupLocation'))]", - "effectivePlanLocation": "[if(empty(parameters('newAppServicePlanLocation')), variables('resourcesLocation'), parameters('newAppServicePlanLocation'))]", - "appServicePlanName": "[if(empty(parameters('newAppServicePlanName')), concat(parameters('botId'), 'ServicePlan'), parameters('newAppServicePlanName'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[variables('effectiveGroupLocation')]", - "properties": { - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new App Service Plan", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('effectivePlanLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('appServicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using the new App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2015-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('appServicePlanName')]", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} \ No newline at end of file diff --git a/samples/16.proactive-messages/bin/deploymentTemplates/template-with-preexisting-rg.json b/samples/16.proactive-messages/bin/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index b790d2bdc..000000000 --- a/samples/16.proactive-messages/bin/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,158 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), if(empty(parameters('newAppServicePlanName')),concat(parameters('botId'), 'ServicePlan'),parameters('newAppServicePlanName')))]", - "resourcesLocation": "[if(empty(parameters('appServicePlanLocation')), resourceGroup().location, parameters('appServicePlanLocation'))]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "name": "[variables('servicePlanName')]", - "reserved":true - } - }, - { - "comments": "Create a Web App using an App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2016-08-01", - "location": "[variables('resourcesLocation')]", - "kind": "app", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "serverFarmId": "[variables('servicePlanName')]", - "siteConfig": { - "linuxFxVersion": "JAVA|8-jre8", - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} \ No newline at end of file diff --git a/samples/16.proactive-messages/bin/pom.xml b/samples/16.proactive-messages/bin/pom.xml deleted file mode 100644 index 6900cba5e..000000000 --- a/samples/16.proactive-messages/bin/pom.xml +++ /dev/null @@ -1,191 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.connector.sample - spring-echobot - sample - jar - - ${project.groupId}:${project.artifactId} - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - UTF-8 - UTF-8 - 1.8 - com.microsoft.bot.sample.echo.Application - - - - - org.springframework.boot - spring-boot-starter-web - - - org.springframework.boot - spring-boot-starter-test - test - - - junit - junit - 4.12 - test - - - com.microsoft.bot.schema - botbuilder-schema - 4.0.0-SNAPSHOT - - - com.microsoft.bot.connector - bot-connector - 4.0.0-SNAPSHOT - - - com.fasterxml.jackson.module - jackson-module-parameter-names - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jdk8 - 2.9.2 - - - com.fasterxml.jackson.datatype - jackson-datatype-jsr310 - 2.9.2 - - - - - - MyGet - ${repo.url} - - - - - - MyGet - ${repo.url} - - - - - - - maven-compiler-plugin - 3.8.1 - - 1.8 - 1.8 - - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.echo.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.7.0 - - V2 - {groupname} - {botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - jre8 - jre8 - - - - - ${project.basedir}/target - - *.jar - - - - - - - - org.eluder.coveralls - coveralls-maven-plugin - 4.3.0 - - yourcoverallsprojectrepositorytoken - - - - org.codehaus.mojo - cobertura-maven-plugin - 2.7 - - ../../cobertura-report/spring-echo-sample - xml - 256m - - true - - - - org.apache.maven.plugins - maven-pmd-plugin - 3.12.0 - - - validate - - check - - - - - - - - - - org.apache.maven.plugins - maven-pmd-plugin - - - - diff --git a/samples/16.proactive-messages/bin/src/main/resources/application.properties b/samples/16.proactive-messages/bin/src/main/resources/application.properties deleted file mode 100644 index a695b3bf0..000000000 --- a/samples/16.proactive-messages/bin/src/main/resources/application.properties +++ /dev/null @@ -1,2 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= diff --git a/samples/16.proactive-messages/bin/src/main/webapp/META-INF/MANIFEST.MF b/samples/16.proactive-messages/bin/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/16.proactive-messages/bin/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/16.proactive-messages/bin/src/main/webapp/WEB-INF/web.xml b/samples/16.proactive-messages/bin/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/16.proactive-messages/bin/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/16.proactive-messages/bin/src/main/webapp/index.html b/samples/16.proactive-messages/bin/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/16.proactive-messages/bin/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json b/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/16.proactive-messages/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json b/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/16.proactive-messages/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/16.proactive-messages/pom.xml b/samples/16.proactive-messages/pom.xml deleted file mode 100644 index 274d081ac..000000000 --- a/samples/16.proactive-messages/pom.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-proactive - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Bot Proactive Messages sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.proactive.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.proactive.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java deleted file mode 100644 index fead67fbd..000000000 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/Application.java +++ /dev/null @@ -1,77 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -// -// See NotifyController in this project for an example on adding a controller. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot(ConversationReferences conversationReferences) { - return new ProactiveBot(conversationReferences); - } - - /** - * The shared ConversationReference Map. This hold a list of conversations for - * the bot. - * - * @return A ConversationReferences object. - */ - @Bean - public ConversationReferences getConversationReferences() { - return new ConversationReferences(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ConversationReferences.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ConversationReferences.java deleted file mode 100644 index faf28c151..000000000 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ConversationReferences.java +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import com.microsoft.bot.schema.ConversationReference; - -import java.util.concurrent.ConcurrentHashMap; - -/** - * A Map of ConversationReference object the bot handling. - * - * @see NotifyController - * @see ProactiveBot - */ -public class ConversationReferences extends ConcurrentHashMap { -} diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/NotifyController.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/NotifyController.java deleted file mode 100644 index 6685356a2..000000000 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/NotifyController.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.schema.ConversationReference; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RestController; - -import java.util.UUID; - -/** - * This controller will receive GET requests at /api/notify and send a message - * to all ConversationReferences. - * - * @see ConversationReferences - * @see ProactiveBot - * @see Application - */ -@RestController -public class NotifyController { - /** - * The BotFrameworkHttpAdapter to use. Note is is provided by dependency - * injection via the constructor. - * - * @see com.microsoft.bot.integration.spring.BotDependencyConfiguration - */ - private final BotFrameworkHttpAdapter adapter; - - private ConversationReferences conversationReferences; - private String appId; - - @Autowired - public NotifyController( - BotFrameworkHttpAdapter withAdapter, - Configuration withConfiguration, - ConversationReferences withReferences - ) { - adapter = withAdapter; - conversationReferences = withReferences; - - // If the channel is the Emulator, and authentication is not in use, - // the AppId will be null. We generate a random AppId for this case only. - // This is not required for production, since the AppId will have a value. - appId = withConfiguration.getProperty("MicrosoftAppId"); - if (StringUtils.isEmpty(appId)) { - appId = UUID.randomUUID().toString(); - } - } - - @GetMapping("/api/notify") - public ResponseEntity proactiveMessage() { - for (ConversationReference reference : conversationReferences.values()) { - adapter.continueConversation( - appId, reference, turnContext -> turnContext.sendActivity("proactive hello").thenApply(resourceResponse -> null) - ); - } - - // Let the caller know proactive messages have been sent - return new ResponseEntity<>( - "

Proactive messages have been sent.

", - HttpStatus.ACCEPTED - ); - } -} diff --git a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java b/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java deleted file mode 100644 index 732bee213..000000000 --- a/samples/16.proactive-messages/src/main/java/com/microsoft/bot/sample/proactive/ProactiveBot.java +++ /dev/null @@ -1,82 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.ConversationReference; -import org.apache.commons.lang3.StringUtils; -import org.springframework.beans.factory.annotation.Value; - -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - * - *

- * This is where application specific logic for interacting with the users would - * be added. For this sample, the {@link #onMessageActivity(TurnContext)} echos - * the text back to the user and updates the shared - * {@link ConversationReferences}. The - * {@link #onMembersAdded(List, TurnContext)} will send a greeting to new - * conversation participants with instructions for sending a proactive message. - *

- */ -public class ProactiveBot extends ActivityHandler { - @Value("${server.port:3978}") - private int port; - - private static final String WELCOMEMESSAGE = - "Welcome to the Proactive Bot sample. Navigate to http://localhost:%d/api/notify to proactively message everyone who has previously messaged this bot."; - - private ConversationReferences conversationReferences; - - public ProactiveBot(ConversationReferences withReferences) { - conversationReferences = withReferences; - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - addConversationReference(turnContext.getActivity()); - - return turnContext - .sendActivity(MessageFactory.text("Echo: " + turnContext.getActivity().getText())) - .thenApply(sendResult -> null); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, - TurnContext turnContext - ) { - return membersAdded.stream() - .filter( - member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId()) - ) - .map( - channel -> turnContext - .sendActivity(MessageFactory.text(String.format(WELCOMEMESSAGE, port))) - ) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponses -> null); - } - - @Override - protected CompletableFuture onConversationUpdateActivity(TurnContext turnContext) { - addConversationReference(turnContext.getActivity()); - return super.onConversationUpdateActivity(turnContext); - } - - // adds a ConversationReference to the shared Map. - private void addConversationReference(Activity activity) { - ConversationReference conversationReference = activity.getConversationReference(); - conversationReferences.put(conversationReference.getUser().getId(), conversationReference); - } -} diff --git a/samples/16.proactive-messages/src/main/resources/application.properties b/samples/16.proactive-messages/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/16.proactive-messages/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/16.proactive-messages/src/main/resources/log4j2.json b/samples/16.proactive-messages/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/16.proactive-messages/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/16.proactive-messages/src/main/webapp/META-INF/MANIFEST.MF b/samples/16.proactive-messages/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/16.proactive-messages/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/16.proactive-messages/src/main/webapp/WEB-INF/web.xml b/samples/16.proactive-messages/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/16.proactive-messages/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/16.proactive-messages/src/main/webapp/index.html b/samples/16.proactive-messages/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/16.proactive-messages/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java b/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java deleted file mode 100644 index 5e8974cb9..000000000 --- a/samples/16.proactive-messages/src/test/java/com/microsoft/bot/sample/proactive/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.proactive; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/17.multilingual-bot/LICENSE b/samples/17.multilingual-bot/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/17.multilingual-bot/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/17.multilingual-bot/README.md b/samples/17.multilingual-bot/README.md deleted file mode 100644 index 7cd05af61..000000000 --- a/samples/17.multilingual-bot/README.md +++ /dev/null @@ -1,126 +0,0 @@ -# Multilingual Bot - -Bot Framework v4 multilingual bot sample - -This sample will present the user with a set of cards to pick their choice of language. The user can either change language by invoking the option cards, or by entering the language code (_en_/_es_). The bot will then acknowledge the selection. - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to translate incoming and outgoing text using a custom middleware and the [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/). - -## Concepts introduced in this sample - -Translation Middleware: We create a translation middleware that can translate text from bot to user and from user to bot, allowing the creation of multi-lingual bots. - -The middleware is driven by user state. This means that users can specify their language preference, and the middleware automatically will intercept messages back and forth and present them to the user in their preferred language. - -Users can change their language preference anytime, and since this gets written to the user state, the middleware will read this state and instantly modify its behavior to honor the newly selected preferred language. - -The [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/), Microsoft Translator Text API is a cloud-based machine translation service. With this API you can translate text in near real-time from any app or service through a simple REST API call. -The API uses the most modern neural machine translation technology, as well as offering statistical machine translation technology. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. -- [Microsoft Translator Text API key](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup) - - To consume the Microsoft Translator Text API, first obtain a key following the instructions in the [Microsoft Translator Text API documentation](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-text-how-to-signup). - - Paste the key in the `TranslatorKey` setting in the `application.properties` file. - -## To try this sample - -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-multilingual-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -### Creating a custom middleware - -Translation Middleware: We create a translation middleware than can translate text from bot to user and from user to bot, allowing the creation of multilingual bots. -Users can specify their language preference, which is stored in the user state. The translation middleware translates to and from the user's preferred language. - -### Microsoft Translator Text API - -The [Microsoft Translator Text API](https://docs.microsoft.com/en-us/azure/cognitive-services/translator/), Microsoft Translator Text API is a cloud-based machine translation service. With this API you can translate text in near real-time from any app or service through a simple REST API call. -The API uses the most modern neural machine translation technology, as well as offering statistical machine translation technology. - -## Deploy this bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "multilingualBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multilingualBotPlan" newWebAppName="multilingualBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multilingualBot" newAppServicePlanName="multilingualBotPlan" appServicePlanLocation="westus" --name "multilingualBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update -- `MicrosoftAppPassword` with the botsecret value -- `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - -### Add `TranslatorKey` to Application Settings - -If you used the `application.properties` file to store your `TranslatorKey` then you'll need to add this key and its value to the Application Settings for your deployed bot. - -- Log into the [Azure portal](https://portal.azure.com) -- In the left nav, click on `Bot Services` -- Click the `` Name to display the bots Web App Settings -- Click the `Application Settings` -- Scroll to the `Application settings` section -- Click `+ Add new setting` -- Add the key `TranslatorKey` with a value of the Translator Text API `Authentication key` created from the steps above - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Bot State](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-storage-concept?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Spring Boot](https://spring.io/projects/spring-boot) diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/17.multilingual-bot/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json b/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/17.multilingual-bot/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/17.multilingual-bot/pom.xml b/samples/17.multilingual-bot/pom.xml deleted file mode 100644 index 183a19d9e..000000000 --- a/samples/17.multilingual-bot/pom.xml +++ /dev/null @@ -1,238 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-multilingual - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Multi-Lingual Bot sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.multilingual.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.multilingual.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java deleted file mode 100644 index dafe6ca93..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/Application.java +++ /dev/null @@ -1,99 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.Storage; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.multilingual.translation.MicrosoftTranslator; -import com.microsoft.bot.sample.multilingual.translation.TranslationMiddleware; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot(UserState userState) { - return new MultiLingualBot(userState); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - Storage storage = this.getStorage(); - ConversationState conversationState = this.getConversationState(storage); - - BotFrameworkHttpAdapter adapter = new AdapterWithErrorHandler(configuration, conversationState); - TranslationMiddleware translationMiddleware = this.getTranslationMiddleware(configuration); - adapter.use(translationMiddleware); - return adapter; - } - - /** - * Create the Microsoft Translator responsible for making calls to the Cognitive Services translation service. - * @param configuration The Configuration object to use. - * @return MicrosoftTranslator - */ - @Bean - public MicrosoftTranslator getMicrosoftTranslator(Configuration configuration) { - return new MicrosoftTranslator(configuration); - } - - /** - * Create the Translation Middleware that will be added to the middleware pipeline in the AdapterWithErrorHandler. - * @param configuration The Configuration object to use. - * @return TranslationMiddleware - */ - @Bean - public TranslationMiddleware getTranslationMiddleware(Configuration configuration) { - Storage storage = this.getStorage(); - UserState userState = this.getUserState(storage); - MicrosoftTranslator microsoftTranslator = this.getMicrosoftTranslator(configuration); - return new TranslationMiddleware(microsoftTranslator, userState); - } -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java deleted file mode 100644 index 90205f125..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/MultiLingualBot.java +++ /dev/null @@ -1,179 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.google.common.base.Strings; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.builder.StatePropertyAccessor; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.SuggestedActions; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.Serialization; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.lang3.StringUtils; -import org.springframework.stereotype.Component; - -/** - * This bot demonstrates how to use Microsoft Translator. - * More information can be found - * here https://docs.microsoft.com/en-us/azure/cognitive-services/translator/translator-info-overview. - */ -public class MultiLingualBot extends ActivityHandler { - private static final String WELCOME_TEXT = - new StringBuilder("This bot will introduce you to translation middleware. ") - .append("Say 'hi' to get started.").toString(); - - private static final String ENGLISH_ENGLISH = "en"; - private static final String ENGLISH_SPANISH = "es"; - private static final String SPANISH_ENGLISH = "in"; - private static final String SPANISH_SPANISH = "it"; - - private UserState userState; - private StatePropertyAccessor languagePreference; - - /** - * Creates a Multilingual bot. - * @param withUserState User state object. - */ - public MultiLingualBot(UserState withUserState) { - if (withUserState == null) { - throw new IllegalArgumentException("userState"); - } - this.userState = withUserState; - - this.languagePreference = userState.createProperty("LanguagePreference"); - } - - /** - * This method is executed when a user is joining to the conversation. - * @param membersAdded A list of all the members added to the conversation, - * as described by the conversation update activity. - * @param turnContext The context object for this turn. - * @return A task that represents the work queued to execute. - */ - @Override - protected CompletableFuture onMembersAdded(List membersAdded, - TurnContext turnContext) { - return MultiLingualBot.sendWelcomeMessage(turnContext); - } - - /** - * This method is executed when the turnContext receives a message activity. - * @param turnContext The context object for this turn. - * @return A task that represents the work queued to execute. - */ - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - if (MultiLingualBot.isLanguageChangeRequested(turnContext.getActivity().getText())) { - String currentLang = turnContext.getActivity().getText().toLowerCase(); - String lang = currentLang.equals(ENGLISH_ENGLISH) || currentLang.equals(SPANISH_ENGLISH) - ? ENGLISH_ENGLISH : ENGLISH_SPANISH; - - // If the user requested a language change through the suggested actions with values "es" or "en", - // simply change the user's language preference in the user state. - // The translation middleware will catch this setting and translate both ways to the user's - // selected language. - // If Spanish was selected by the user, the reply below will actually be shown in spanish to the user. - return languagePreference.set(turnContext, lang) - .thenCompose(task -> { - Activity reply = MessageFactory.text(String.format("Your current language code is: %s", lang)); - return turnContext.sendActivity(reply); - }) - // Save the user profile updates into the user state. - .thenCompose(task -> userState.saveChanges(turnContext, false)); - } else { - // Show the user the possible options for language. If the user chooses a different language - // than the default, then the translation middleware will pick it up from the user state and - // translate messages both ways, i.e. user to bot and bot to user. - Activity reply = MessageFactory.text("Choose your language:"); - CardAction esAction = new CardAction(); - esAction.setTitle("Español"); - esAction.setType(ActionTypes.POST_BACK); - esAction.setValue(ENGLISH_SPANISH); - - CardAction enAction = new CardAction(); - enAction.setTitle("English"); - enAction.setType(ActionTypes.POST_BACK); - enAction.setValue(ENGLISH_ENGLISH); - - List actions = new ArrayList<>(Arrays.asList(esAction, enAction)); - SuggestedActions suggestedActions = new SuggestedActions(); - suggestedActions.setActions(actions); - reply.setSuggestedActions(suggestedActions); - return turnContext.sendActivity(reply).thenApply(resourceResponse -> null); - } - } - - private static CompletableFuture sendWelcomeMessage(TurnContext turnContext) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils.equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - Attachment welcomeCard = MultiLingualBot.createAdaptiveCardAttachment(); - Activity response = MessageFactory.attachment(welcomeCard); - return turnContext.sendActivity(response) - .thenCompose(task -> turnContext.sendActivity(MessageFactory.text(WELCOME_TEXT))); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - /** - * Load attachment from file. - * @return the welcome adaptive card - */ - private static Attachment createAdaptiveCardAttachment() { - // combine path for cross platform support - try ( - InputStream input = Thread.currentThread().getContextClassLoader() - .getResourceAsStream("cards/welcomeCard.json") - ) { - String adaptiveCardJson = IOUtils.toString(input, StandardCharsets.UTF_8.toString()); - - Attachment attachment = new Attachment(); - attachment.setContentType("application/vnd.microsoft.card.adaptive"); - attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); - return attachment; - } catch (IOException e) { - e.printStackTrace(); - return new Attachment(); - } - } - - /** - * Checks whether the utterance from the user is requesting a language change. - * In a production bot, we would use the Microsoft Text Translation API language - * detection feature, along with detecting language names. - * For the purpose of the sample, we just assume that the user requests language - * changes by responding with the language code through the suggested action presented - * above or by typing it. - * @param utterance utterance the current turn utterance. - * @return the utterance. - */ - private static Boolean isLanguageChangeRequested(String utterance) { - if (Strings.isNullOrEmpty(utterance)) { - return false; - } - - utterance = utterance.toLowerCase().trim(); - return utterance.equals(ENGLISH_SPANISH) || utterance.equals(ENGLISH_ENGLISH) - || utterance.equals(SPANISH_SPANISH) || utterance.equals(SPANISH_ENGLISH); - } -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java deleted file mode 100644 index f4a2b347a..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/MicrosoftTranslator.java +++ /dev/null @@ -1,96 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual.translation; - -import java.io.Reader; -import java.io.StringReader; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionException; - -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.sample.multilingual.translation.model.TranslatorResponse; - -import okhttp3.MediaType; -import okhttp3.OkHttpClient; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; - -import com.fasterxml.jackson.databind.ObjectMapper; -import com.google.common.base.Strings; - -import org.slf4j.LoggerFactory; - -/** - * A helper class wrapper for the Microsoft Translator API. - */ -public class MicrosoftTranslator { - private static final String HOST = "https://api.cognitive.microsofttranslator.com"; - private static final String PATH = "/translate?api-version=3.0"; - private static final String URI_PARAMS = "&to="; - - private static String key; - - /** - * @param configuration The configuration class with the translator key stored. - */ - public MicrosoftTranslator(Configuration configuration) { - String translatorKey = configuration.getProperty("TranslatorKey"); - - if (translatorKey == null) { - throw new IllegalArgumentException("key"); - } - - MicrosoftTranslator.key = translatorKey; - } - - /** - * Helper method to translate text to a specified language. - * @param text Text that will be translated. - * @param targetLocale targetLocale Two character language code, e.g. "en", "es". - * @return The first translation result - */ - public CompletableFuture translate(String text, String targetLocale) { - return CompletableFuture.supplyAsync(() -> { - // From Cognitive Services translation documentation: - // https://docs.microsoft.com/en-us/azure/cognitive-services/Translator/quickstart-translator?tabs=java - String body = String.format("[{ \"Text\": \"%s\" }]", text); - - String uri = new StringBuilder(MicrosoftTranslator.HOST) - .append(MicrosoftTranslator.PATH) - .append(MicrosoftTranslator.URI_PARAMS) - .append(targetLocale).toString(); - - RequestBody requestBody = RequestBody.create(MediaType.parse("application/json"), body); - - OkHttpClient client = new OkHttpClient(); - Request request = new Request.Builder() - .url(uri) - .header("Ocp-Apim-Subscription-Key", MicrosoftTranslator.key) - .post(requestBody) - .build(); - - try { - Response response = client.newCall(request).execute(); - - if (!response.isSuccessful()) { - String message = new StringBuilder("The call to the translation service returned HTTP status code ") - .append(response.code()) - .append(".").toString(); - throw new Exception(message); - } - - ObjectMapper objectMapper = new ObjectMapper(); - Reader reader = new StringReader(response.body().string()); - TranslatorResponse[] result = objectMapper.readValue(reader, TranslatorResponse[].class); - - return result[0].getTranslations().get(0).getText(); - - } catch (Exception e) { - LoggerFactory.getLogger(MicrosoftTranslator.class).error("findPackages", e); - throw new CompletionException(e); - } - }); - } -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java deleted file mode 100644 index aa1ff6b80..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationMiddleware.java +++ /dev/null @@ -1,126 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual.translation; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; - -import com.google.common.base.Strings; -import com.microsoft.bot.builder.Middleware; -import com.microsoft.bot.builder.NextDelegate; -import com.microsoft.bot.builder.StatePropertyAccessor; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; - -/** - * Middleware for translating text between the user and bot. - * Uses the Microsoft Translator Text API. - */ -public class TranslationMiddleware implements Middleware { - private MicrosoftTranslator translator; - private StatePropertyAccessor languageStateProperty; - - /** - * Initializes a new instance of the {@link TranslationMiddleware} class. - * @param withTranslator Translator implementation to be used for text translation. - * @param userState State property for current language. - */ - public TranslationMiddleware(MicrosoftTranslator withTranslator, UserState userState) { - if (withTranslator == null) { - throw new IllegalArgumentException("withTranslator"); - } - this.translator = withTranslator; - if (userState == null) { - throw new IllegalArgumentException("userState"); - } - - this.languageStateProperty = userState.createProperty("LanguagePreference"); - } - - /** - * Processes an incoming activity. - * @param turnContext Context object containing information for a single turn of conversation with a user. - * @param next The delegate to call to continue the bot middleware pipeline. - * @return A Task representing the asynchronous operation. - */ - public CompletableFuture onTurn(TurnContext turnContext, NextDelegate next) { - if (turnContext == null) { - throw new IllegalArgumentException("turnContext"); - } - - return this.shouldTranslate(turnContext).thenCompose(translate -> { - if (translate) { - if (turnContext.getActivity().isType(ActivityTypes.MESSAGE)) { - return this.translator.translate( - turnContext.getActivity().getText(), - TranslationSettings.DEFAULT_LANGUAGE) - .thenApply(text -> { - turnContext.getActivity().setText(text); - return CompletableFuture.completedFuture(null); - }); - } - } - return CompletableFuture.completedFuture(null); - }).thenCompose(task -> { - turnContext.onSendActivities((newContext, activities, nextSend) -> { - return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenCompose(userLanguage -> { - Boolean shouldTranslate = !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); - - // Translate messages sent to the user to user language - if (shouldTranslate) { - ArrayList> tasks = new ArrayList>(); - for (Activity activity : activities.stream().filter(a -> a.getType().equals(ActivityTypes.MESSAGE)).collect(Collectors.toList())) { - tasks.add(this.translateMessageActivity(activity, userLanguage)); - } - - if (!Arrays.asList(tasks).isEmpty()) { - CompletableFuture.allOf(tasks.toArray(new CompletableFuture[tasks.size()])).join(); - } - } - - return nextSend.get(); - }); - }); - - turnContext.onUpdateActivity((newContext, activity, nextUpdate) -> { - return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenCompose(userLanguage -> { - Boolean shouldTranslate = !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); - - // Translate messages sent to the user to user language - if (activity.getType().equals(ActivityTypes.MESSAGE)) { - if (shouldTranslate) { - this.translateMessageActivity(activity, userLanguage); - } - } - - return nextUpdate.get(); - }); - }); - - return next.next(); - }); - } - - private CompletableFuture translateMessageActivity(Activity activity, String targetLocale) { - if (activity.getType().equals(ActivityTypes.MESSAGE)) { - return this.translator.translate(activity.getText(), targetLocale).thenAccept(text -> { - activity.setText(text); - }); - } - return CompletableFuture.completedFuture(null); - } - - private CompletableFuture shouldTranslate(TurnContext turnContext) { - return this.languageStateProperty.get(turnContext, () -> TranslationSettings.DEFAULT_LANGUAGE).thenApply(userLanguage -> { - if (Strings.isNullOrEmpty(userLanguage)) { - userLanguage = TranslationSettings.DEFAULT_LANGUAGE; - } - return !userLanguage.equals(TranslationSettings.DEFAULT_LANGUAGE); - }); - } -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java deleted file mode 100644 index 9f216d220..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/TranslationSettings.java +++ /dev/null @@ -1,11 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual.translation; - -/** - * General translation settings and constants. - */ -public class TranslationSettings { - public static final String DEFAULT_LANGUAGE = "en"; -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java deleted file mode 100644 index c4f056eb8..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResponse.java +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual.translation.model; - -import java.util.List; - -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Array of translated results from Translator API v3. - */ -@JsonIgnoreProperties(ignoreUnknown = true) -public class TranslatorResponse { - @JsonProperty("translations") - private List translations; - - /** - * Gets the translation results. - * @return A list of {@link TranslatorResult} - */ - public List getTranslations() { - return this.translations; - } - - /** - * Sets the translation results. - * @param withTranslations A list of {@link TranslatorResult} - */ - public void setTranslations(List withTranslations) { - this.translations = withTranslations; - } -} diff --git a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java b/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java deleted file mode 100644 index 6021ee88c..000000000 --- a/samples/17.multilingual-bot/src/main/java/com/microsoft/bot/sample/multilingual/translation/model/TranslatorResult.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual.translation.model; - -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Translation result from Translator API v3. - */ -public class TranslatorResult { - @JsonProperty("text") - private String text; - - @JsonProperty("to") - private String to; - - /** - * Gets the translation result text. - * @return Translation result. - */ - public String getText() { - return this.text; - } - - /** - * Sets the translation result text. - * @param withText Translation result. - */ - public void setText(String withText) { - this.text = withText; - } - - /** - * Gets the target language locale. - * @return Locale. - */ - public String getTo() { - return this.to; - } - - /** - * Sets the target language locale. - * @param withTo Target locale. - */ - public void setTo(String withTo) { - this.to = withTo; - } -} diff --git a/samples/17.multilingual-bot/src/main/resources/application.properties b/samples/17.multilingual-bot/src/main/resources/application.properties deleted file mode 100644 index bbbe15889..000000000 --- a/samples/17.multilingual-bot/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -TranslatorKey= -server.port=3978 diff --git a/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json b/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json deleted file mode 100644 index 47e5614df..000000000 --- a/samples/17.multilingual-bot/src/main/resources/cards/welcomeCard.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "Image", - "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", - "size": "stretch" - }, - { - "type": "TextBlock", - "spacing": "medium", - "size": "default", - "weight": "bolder", - "text": "Welcome to Bot Framework!", - "wrap": true, - "maxLines": 0 - }, - { - "type": "TextBlock", - "size": "default", - "isSubtle": true, - "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", - "wrap": true, - "maxLines": 0 - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Get an overview", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - }, - { - "type": "Action.OpenUrl", - "title": "Ask a question", - "url": "https://stackoverflow.com/questions/tagged/botframework" - }, - { - "type": "Action.OpenUrl", - "title": "Learn how to deploy", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - } - ] -} \ No newline at end of file diff --git a/samples/17.multilingual-bot/src/main/resources/log4j2.json b/samples/17.multilingual-bot/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/17.multilingual-bot/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF b/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/17.multilingual-bot/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml b/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/17.multilingual-bot/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/17.multilingual-bot/src/main/webapp/index.html b/samples/17.multilingual-bot/src/main/webapp/index.html deleted file mode 100644 index c46330130..000000000 --- a/samples/17.multilingual-bot/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - Multi-lingual Bot Sample - - - - - -
-
-
-
Multi-lingual Bot Sample
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java b/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java deleted file mode 100644 index 18d372838..000000000 --- a/samples/17.multilingual-bot/src/test/java/com/microsoft/bot/sample/multilingual/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.multilingual; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/18.bot-authentication/LICENSE b/samples/18.bot-authentication/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/18.bot-authentication/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/18.bot-authentication/README.md b/samples/18.bot-authentication/README.md deleted file mode 100644 index 456bab485..000000000 --- a/samples/18.bot-authentication/README.md +++ /dev/null @@ -1,94 +0,0 @@ -# Bot Authentication - -Bot Framework v4 bot authentication sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to use authentication in your bot using OAuth. - -The sample uses the bot authentication capabilities in [Azure Bot Service](https://docs.botframework.com), providing features to make it easier to develop a bot that authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, etc. - -NOTE: Microsoft Teams currently differs slightly in the way auth is integrated with the bot. Refer to sample 46.teams-auth. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-authentication-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "authenticationBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="authenticationBotPlan" newWebAppName="authenticationBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="authenticationBot" newAppServicePlanName="authenticationBotPlan" appServicePlanLocation="westus" --name "authenticationBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json b/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/18.bot-authentication/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json b/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/18.bot-authentication/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/18.bot-authentication/pom.xml b/samples/18.bot-authentication/pom.xml deleted file mode 100644 index b811dd8c3..000000000 --- a/samples/18.bot-authentication/pom.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-authentication - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Bot Authentication sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.authentication.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.authentication.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java deleted file mode 100644 index 55f589332..000000000 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/Application.java +++ /dev/null @@ -1,72 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - Configuration configuration, - ConversationState conversationState, - UserState userState, - MainDialog dialog - ) { - return new AuthBot(conversationState, userState, new MainDialog(configuration)); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java deleted file mode 100644 index 83d4524bc..000000000 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.concurrent.CompletableFuture; -import java.util.List; - -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.ChannelAccount; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.schema.Activity; -import org.apache.commons.lang3.StringUtils; - -public class AuthBot extends DialogBot { - - public AuthBot(ConversationState conversationState, UserState userState, MainDialog dialog) { - super(conversationState, userState, dialog); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - Activity reply = MessageFactory.text("Welcome to AuthBot." - + " Type anything to get logged in. Type 'logout' to sign-out."); - - return turnContext.sendActivity(reply); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - @Override - protected CompletableFuture onTokenResponseEvent(TurnContext turnContext) { - // Run the Dialog with the new Token Response Event Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } - -} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java deleted file mode 100644 index 83db0b694..000000000 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allows - * multiple different bots to be run at different endpoints within the same project. This can be - * achieved by defining distinct Controller types each with dependency on distinct IBot types, this - * way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been - * used in a Dialog implementation, and the requirement is that all BotState objects are saved at - * the end of a turn. - */ -public class DialogBot extends ActivityHandler { - - protected Dialog dialog; - protected BotState conversationState; - protected BotState userState; - - public DialogBot( - ConversationState withConversationState, - UserState withUserState, - T withDialog - ) { - dialog = withDialog; - conversationState = withConversationState; - userState = withUserState; - } - - @Override - public CompletableFuture onTurn( - TurnContext turnContext - ) { - return super.onTurn(turnContext) - .thenCompose(result -> conversationState.saveChanges(turnContext)) - .thenCompose(result -> userState.saveChanges(turnContext)); - } - - @Override - protected CompletableFuture onMessageActivity( - TurnContext turnContext - ) { - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java deleted file mode 100644 index dcb42dd31..000000000 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.BotFrameworkAdapter; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.schema.ActivityTypes; - -public class LogoutDialog extends ComponentDialog { - - private final String connectionName; - - public LogoutDialog(String id, String connectionName) { - super(id); - this.connectionName = connectionName; - } - - - @Override - protected CompletableFuture onBeginDialog( - DialogContext innerDc, Object options - ) { - DialogTurnResult result = interrupt(innerDc).join(); - if (result != null) { - return CompletableFuture.completedFuture(result); - } - - return super.onBeginDialog(innerDc, options); - } - - @Override - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - DialogTurnResult result = interrupt(innerDc).join(); - if (result != null) { - return CompletableFuture.completedFuture(result); - } - - return super.onContinueDialog(innerDc); - } - - private CompletableFuture interrupt(DialogContext innerDc) { - if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { - String text = innerDc.getContext().getActivity().getText().toLowerCase(); - - if (text.equals("logout")) { - // The bot adapter encapsulates the authentication processes. - BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext() - .getAdapter(); - botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); - innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")) - .join(); - return innerDc.cancelAllDialogs(); - } - } - - return CompletableFuture.completedFuture(null); - } - - /** - * @return the ConnectionName value as a String. - */ - protected String getConnectionName() { - return this.connectionName; - } - -} - diff --git a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java deleted file mode 100644 index 2a4cb4810..000000000 --- a/samples/18.bot-authentication/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; -import com.microsoft.bot.dialogs.prompts.OAuthPrompt; -import com.microsoft.bot.dialogs.prompts.OAuthPromptSettings; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.schema.TokenResponse; - -class MainDialog extends LogoutDialog { - - public MainDialog(Configuration configuration) { - super("MainDialog", configuration.getProperty("ConnectionName")); - - OAuthPromptSettings settings = new OAuthPromptSettings(); - settings.setText("Please Sign In"); - settings.setTitle("Sign In"); - settings.setConnectionName(configuration.getProperty("ConnectionName")); - settings.setTimeout(300000); // User has 5 minutes to login (1000 * 60 * 5) - - addDialog(new OAuthPrompt("OAuthPrompt", settings)); - - addDialog(new ConfirmPrompt("ConfirmPrompt")); - - WaterfallStep[] waterfallSteps = { - this::promptStep, - this::loginStep, - this::displayTokenPhase1, - this::displayTokenPhase2 - }; - - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture promptStep(WaterfallStepContext stepContext) { - return stepContext.beginDialog("OAuthPrompt", null); - } - - private CompletableFuture loginStep(WaterfallStepContext stepContext) { - // Get the token from the previous step. Note that we could also have gotten the - // token directly from the prompt itself. There instanceof an example of this in the next method. - TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); - if (tokenResponse != null) { - stepContext.getContext().sendActivity(MessageFactory.text("You are now logged in.")); - PromptOptions options = new PromptOptions(); - options.setPrompt(MessageFactory.text("Would you like to view your token?")); - return stepContext.prompt("ConfirmPrompt", options); - } - - stepContext.getContext() - .sendActivity(MessageFactory.text("Login was not successful please try again.")); - return stepContext.endDialog(); - } - - private CompletableFuture displayTokenPhase1( - WaterfallStepContext stepContext - ) { - stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")); - - boolean result = (boolean) stepContext.getResult(); - if (result) { - // Call the prompt again because we need the token. The reasons for this are: - // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry - // about refreshing it. We can always just call the prompt again to get the token. - // 2. We never know how long it will take a user to respond. By the time the - // user responds the token may have expired. The user would then be prompted to login again. - // - // There instanceof no reason to store the token locally in the bot because we can always just call - // the OAuth prompt to get the token or get a new token if needed. - return stepContext.beginDialog("OAuthPrompt"); - } - - return stepContext.endDialog(); - } - - private CompletableFuture displayTokenPhase2( - WaterfallStepContext stepContext - ) { - TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); - if (tokenResponse != null) { - stepContext.getContext().sendActivity(MessageFactory.text( - String.format("Here instanceof your token %s", tokenResponse.getToken() - ))); - } - - return stepContext.endDialog(); - } -} - diff --git a/samples/18.bot-authentication/src/main/resources/application.properties b/samples/18.bot-authentication/src/main/resources/application.properties deleted file mode 100644 index 9af36b70a..000000000 --- a/samples/18.bot-authentication/src/main/resources/application.properties +++ /dev/null @@ -1,4 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 -ConnectionName= diff --git a/samples/18.bot-authentication/src/main/resources/log4j2.json b/samples/18.bot-authentication/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/18.bot-authentication/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF b/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/18.bot-authentication/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml b/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/18.bot-authentication/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/18.bot-authentication/src/main/webapp/index.html b/samples/18.bot-authentication/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/18.bot-authentication/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java b/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java deleted file mode 100644 index fcb0cdd43..000000000 --- a/samples/18.bot-authentication/src/test/java/com/microsoft/bot/sample/authentication/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/19.custom-dialogs/LICENSE b/samples/19.custom-dialogs/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/19.custom-dialogs/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/19.custom-dialogs/README.md b/samples/19.custom-dialogs/README.md deleted file mode 100644 index de0766982..000000000 --- a/samples/19.custom-dialogs/README.md +++ /dev/null @@ -1,102 +0,0 @@ -# Custom Dialogs - -Bot Framework v4 custom dialogs bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to sub-class the `Dialog` class to create different bot control mechanism like simple slot filling. - -BotFramework provides a built-in base class called `Dialog`. By subclassing `Dialog`, developers can create new ways to define and control dialog flows used by the bot. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-customdialogs-sample.jar` - -## Testing the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Interacting with the bot - -BotFramework provides a built-in base class called `Dialog`. By subclassing Dialog, developers -can create new ways to define and control dialog flows used by the bot. By adhering to the -features of this class, developers will create custom dialogs that can be used side-by-side -with other dialog types, as well as built-in or custom prompts. - -This example demonstrates a custom Dialog class called `SlotFillingDialog`, which takes a -series of "slots" which define a value the bot needs to collect from the user, as well -as the prompt it should use. The bot will iterate through all of the slots until they are -all full, at which point the dialog completes. - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "multiTurnPromptBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="multiTurnPromptBotPlan" newWebAppName="multiTurnPromptBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="multiTurnPromptBot" newAppServicePlanName="multiTurnPromptBotPlan" appServicePlanLocation="westus" --name "multiTurnPromptBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Dialog class reference](https://docs.microsoft.com/en-us/javascript/api/botbuilder-dialogs/dialog) -- [Manage complex conversation flows with dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-dialog-manage-complex-conversation-flow?view=azure-bot-service-4.0) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/19.custom-dialogs/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json b/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/19.custom-dialogs/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/19.custom-dialogs/pom.xml b/samples/19.custom-dialogs/pom.xml deleted file mode 100644 index 519a93bfe..000000000 --- a/samples/19.custom-dialogs/pom.xml +++ /dev/null @@ -1,244 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-customdialogs - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Custom Dialogs sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.customdialogs.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.customdialogs.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java deleted file mode 100644 index 4997b04e0..000000000 --- a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/Application.java +++ /dev/null @@ -1,86 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - ConversationState conversationState, - UserState userState, - Dialog dialog - ) { - return new DialogBot(conversationState, userState, dialog); - } - - /** - * Returns the starting Dialog for this application. - * - *

- * The @Component annotation could be used on the Dialog class instead of this method - * with the @Bean annotation. - *

- * - * @return The Dialog implementation for this application. - */ - @Bean - public Dialog getRootDialog(UserState userState) { - return new RootDialog(userState); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java deleted file mode 100644 index ce884a210..000000000 --- a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/DialogBot.java +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import java.util.concurrent.CompletableFuture; - -/** - * This IBot implementation can run any type of Dialog. The use of type parameterization is to - * allows multiple different bots to be run at different endpoints within the same project. This - * can be achieved by defining distinct Controller types each with dependency on distinct IBot - * types, this way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have - * been used in a Dialog implementation, and the requirement is that all BotState objects are - * saved at the end of a turn. - */ -public class DialogBot extends ActivityHandler { - protected Dialog dialog; - protected BotState conversationState; - protected BotState userState; - - public DialogBot( - ConversationState withConversationState, - UserState withUserState, - Dialog withDialog - ) { - dialog = withDialog; - conversationState = withConversationState; - userState = withUserState; - } - - @Override - public CompletableFuture onTurn( - TurnContext turnContext - ) { - return super.onTurn(turnContext) - .thenCompose(result -> conversationState.saveChanges(turnContext)) - .thenCompose(result -> userState.saveChanges(turnContext)); - } - - @Override - protected CompletableFuture onMessageActivity( - TurnContext turnContext - ) { - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java deleted file mode 100644 index bfaf3b445..000000000 --- a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/RootDialog.java +++ /dev/null @@ -1,132 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.StatePropertyAccessor; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.NumberPrompt; -import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -public class RootDialog extends ComponentDialog { - - private StatePropertyAccessor>> userStateAccessor; - - public RootDialog(UserState withUserState) { - super("root"); - - userStateAccessor = withUserState.createProperty("result"); - - // Rather than explicitly coding a Waterfall we have only to declare what properties we - // want collected. - // In this example we will want two text prompts to run, one for the first name and one - // for the last. - List fullname_slots = Arrays.asList( - new SlotDetails("first", "text", "Please enter your first name."), - new SlotDetails("last", "text", "Please enter your last name.") - ); - - // This defines an address dialog that collects street, city and zip properties. - List address_slots = Arrays.asList( - new SlotDetails("street", "text", "Please enter the street."), - new SlotDetails("city", "text", "Please enter the city."), - new SlotDetails("zip", "text", "Please enter the zip.") - ); - - // Dialogs can be nested and the slot filling dialog makes use of that. In this example - // some of the child dialogs are slot filling dialogs themselves. - List slots = Arrays.asList( - new SlotDetails("fullname", "fullname"), - new SlotDetails("age", "number", "Please enter your age."), - new SlotDetails( - "shoesize", "shoesize", "Please enter your shoe size.", - "You must enter a size between 0 and 16. Half sizes are acceptable." - ), - new SlotDetails("address", "address") - ); - - // Add the various dialogs that will be used to the DialogSet. - addDialog(new SlotFillingDialog("address", address_slots)); - addDialog(new SlotFillingDialog("fullname", fullname_slots)); - addDialog(new TextPrompt("text")); - addDialog(new NumberPrompt<>("number", Integer.class)); - addDialog(new NumberPrompt("shoesize", this::shoeSize, Float.class)); - addDialog(new SlotFillingDialog("slot-dialog", slots)); - - // Defines a simple two step Waterfall to test the slot dialog. - WaterfallStep[] waterfallSteps = { - this::startDialog, - this::processResult - }; - addDialog(new WaterfallDialog("waterfall", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("waterfall"); - } - - private CompletableFuture startDialog(WaterfallStepContext stepContext) { - // Start the child dialog. This will run the top slot dialog than will complete when - // all the properties are gathered. - return stepContext.beginDialog("slot-dialog"); - } - - private CompletableFuture processResult(WaterfallStepContext stepContext) { - Map result = stepContext.getResult() instanceof Map - ? (Map) stepContext.getResult() - : null; - - // To demonstrate that the slot dialog collected all the properties we will echo them back to the user. - if (result != null && result.size() > 0) { - // Now the waterfall is complete, save the data we have gathered into UserState. - return userStateAccessor.get(stepContext.getContext(), HashMap::new) - .thenApply(obj -> { - Map fullname = (Map) result.get("fullname"); - Float shoesize = (Float) result.get("shoesize"); - Map address = (Map) result.get("address"); - - Map data = new HashMap<>(); - data.put("fullname", String.format("%s %s", fullname.get("first"), fullname.get("last"))); - data.put("shoesize", Float.toString(shoesize)); - data.put("address", String.format("%s, %s, %s", address.get("street"), address.get("city"), address.get("zip"))); - - obj.put("data", data); - return obj; - }) - .thenCompose(obj -> stepContext.getContext().sendActivities( - MessageFactory.text(obj.get("data").get("fullname")), - MessageFactory.text(obj.get("data").get("shoesize")), - MessageFactory.text(obj.get("data").get("address")) - )) - .thenCompose(resourceResponse -> stepContext.endDialog()); - } - - // Remember to call EndAsync to indicate to the runtime that this is the end of our waterfall. - return stepContext.endDialog(); - } - - private CompletableFuture shoeSize(PromptValidatorContext promptContext) { - Float shoesize = promptContext.getRecognized().getValue(); - - // show sizes can range from 0 to 16 - if (shoesize >= 0 && shoesize < 16) { - // we only accept round numbers or half sizes - if (Math.floor(shoesize) == shoesize || Math.floor(shoesize * 2) == shoesize * 2) { - return CompletableFuture.completedFuture(true); - } - } - - return CompletableFuture.completedFuture(false); - } -} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java deleted file mode 100644 index 6531a9741..000000000 --- a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotDetails.java +++ /dev/null @@ -1,75 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.prompts.PromptOptions; - -/** - * A list of SlotDetails defines the behavior of our SlotFillingDialog. This class represents a - * description of a single 'slot'. It contains the name of the property we want to gather and the id - * of the corresponding dialog that should be used to gather that property. The id is that used when - * the dialog was added to the current DialogSet. Typically this id is that of a prompt but it could - * also be the id of another slot dialog. - */ -public class SlotDetails { - - private String name; - private String dialogId; - private PromptOptions options; - - public SlotDetails( - String withName, String withDialogId - ) { - this(withName, withDialogId, null, null); - } - - public SlotDetails( - String withName, String withDialogId, String withPrompt - ) { - this(withName, withDialogId, withPrompt, null); - } - - public SlotDetails( - String withName, String withDialogId, String withPrompt, String withRetryPrompt - ) { - name = withName; - dialogId = withDialogId; - options = new PromptOptions(); - options.setPrompt(MessageFactory.text(withPrompt)); - options.setRetryPrompt(MessageFactory.text(withRetryPrompt)); - } - - public SlotDetails( - String withName, String withDialogId, PromptOptions withPromptOptions - ) { - name = withName; - dialogId = withDialogId; - options = withPromptOptions; - } - - public String getName() { - return name; - } - - public void setName(String name) { - this.name = name; - } - - public String getDialogId() { - return dialogId; - } - - public void setDialogId(String withDialogId) { - dialogId = withDialogId; - } - - public PromptOptions getOptions() { - return options; - } - - public void setOptions(PromptOptions withOptions) { - options = withOptions; - } -} diff --git a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java b/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java deleted file mode 100644 index a63e48371..000000000 --- a/samples/19.custom-dialogs/src/main/java/com/microsoft/bot/sample/customdialogs/SlotFillingDialog.java +++ /dev/null @@ -1,170 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import com.microsoft.bot.connector.Async; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogInstance; -import com.microsoft.bot.dialogs.DialogReason; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.schema.ActivityTypes; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.CompletableFuture; - -/** - * This is an example of implementing a custom Dialog class. This is similar to the Waterfall dialog - * in the framework; however, it is based on a Dictionary rather than a sequential set of functions. - * The dialog is defined by a list of 'slots', each slot represents a property we want to gather and - * the dialog we will be using to collect it. Often the property is simply an atomic piece of data - * such as a number or a date. But sometimes the property is itself a complex object, in which case - * we can use the slot dialog to collect that compound property. - */ -public class SlotFillingDialog extends Dialog { - - // Custom dialogs might define their own custom state. - // Similarly to the Waterfall dialog we will have a set of values in the ConversationState. - // However, rather than persisting an index we will persist the last property we prompted for. - // This way when we resume this code following a prompt we will have remembered what property - // we were filling. - private static final String SLOT_NAME = "slot"; - private static final String PERSISTED_VALUES = "values"; - - // The list of slots defines the properties to collect and the dialogs to use to collect them. - private List slots; - - public SlotFillingDialog(String withDialogId, List withSlots) { - super(withDialogId); - if (withSlots == null) { - throw new IllegalArgumentException("slot details are required"); - } - slots = withSlots; - } - - /** - * Called when the dialog is started and pushed onto the dialog stack. - * - * @param dc The {@link DialogContext} for the current turn of conversation. - * @param options Initial information to pass to the dialog. - * @return If the task is successful, the result indicates whether the dialog is still active - * after the turn has been processed by the dialog. - */ - @Override - public CompletableFuture beginDialog(DialogContext dc, Object options) { - if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); - } - - // Don't do anything for non-message activities. - if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { - return dc.endDialog(new HashMap()); - } - - // Run prompt - return runPrompt(dc); - } - - /** - * Called when the dialog is _continued_, where it is the active dialog and the user replies - * with a new activity. - * - *

If this method is *not* overridden, the dialog automatically ends when the user - * replies.

- * - * @param dc The {@link DialogContext} for the current turn of conversation. - * @return If the task is successful, the result indicates whether the dialog is still active - * after the turn has been processed by the dialog. The result may also contain a return value. - */ - @Override - public CompletableFuture continueDialog( - DialogContext dc - ) { - if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); - } - - // Don't do anything for non-message activities. - if (!dc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { - return CompletableFuture.completedFuture(END_OF_TURN); - } - - // Run next step with the message text as the result. - return runPrompt(dc); - } - - /** - * Called when a child dialog completed this turn, returning control to this dialog. - * - *

Generally, the child dialog was started with a call to - * {@link #beginDialog(DialogContext, Object)} However, if the {@link - * DialogContext#replaceDialog(String, Object)} method is called, the logical child dialog may - * be different than the original.

- * - *

If this method is *not* overridden, the dialog automatically ends when the user - * replies.

- * - * @param dc The dialog context for the current turn of the conversation. - * @param reason Reason why the dialog resumed. - * @param result Optional, value returned from the dialog that was called. The type of the value - * returned is dependent on the child dialog. - * @return If the task is successful, the result indicates whether this dialog is still active - * after this dialog turn has been processed. - */ - @Override - public CompletableFuture resumeDialog( - DialogContext dc, DialogReason reason, Object result - ) { - if (dc == null) { - return Async.completeExceptionally(new IllegalArgumentException("DialogContext")); - } - - // Update the state with the result from the child prompt. - String slotName = (String) dc.getActiveDialog().getState().get(SLOT_NAME); - Map values = getPersistedValues(dc.getActiveDialog()); - values.put(slotName, result); - - // Run prompt - return runPrompt(dc); - } - - // This helper function contains the core logic of this dialog. The main idea is to compare - // the state we have gathered with the list of slots we have been asked to fill. When we find - // an empty slot we execute the corresponding prompt. - private CompletableFuture runPrompt(DialogContext dc) { - Map state = getPersistedValues(dc.getActiveDialog()); - - // Run through the list of slots until we find one that hasn't been filled yet. - Optional optionalSlot = slots.stream() - .filter(item -> !state.containsKey(item.getName())) - .findFirst(); - - if (!optionalSlot.isPresent()) { - // No more slots to fill so end the dialog. - return dc.endDialog(state); - } else { - // If we have an unfilled slot we will try to fill it - SlotDetails unfilledSlot = optionalSlot.get(); - - // The name of the slot we will be prompting to fill. - dc.getActiveDialog().getState().put(SLOT_NAME, unfilledSlot.getName()); - - // Run the child dialog - return dc.beginDialog(unfilledSlot.getDialogId(), unfilledSlot.getOptions()); - } - } - - // Helper function to deal with the persisted values we collect in this dialog. - private Map getPersistedValues(DialogInstance dialogInstance) { - Map state = (Map) dialogInstance.getState() - .get(PERSISTED_VALUES); - if (state == null) { - state = new HashMap(); - dialogInstance.getState().put(PERSISTED_VALUES, state); - } - return state; - } -} diff --git a/samples/19.custom-dialogs/src/main/resources/application.properties b/samples/19.custom-dialogs/src/main/resources/application.properties deleted file mode 100644 index d7d0ee864..000000000 --- a/samples/19.custom-dialogs/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -server.port=3978 diff --git a/samples/19.custom-dialogs/src/main/resources/log4j2.json b/samples/19.custom-dialogs/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/19.custom-dialogs/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF b/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/19.custom-dialogs/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml b/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/19.custom-dialogs/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/19.custom-dialogs/src/main/webapp/index.html b/samples/19.custom-dialogs/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/19.custom-dialogs/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java b/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java deleted file mode 100644 index 89c3838fd..000000000 --- a/samples/19.custom-dialogs/src/test/java/com/microsoft/bot/sample/customdialogs/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.customdialogs; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/21.corebot-app-insights/LICENSE b/samples/21.corebot-app-insights/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/21.corebot-app-insights/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/21.corebot-app-insights/README-LUIS.md b/samples/21.corebot-app-insights/README-LUIS.md deleted file mode 100644 index 60a0b8383..000000000 --- a/samples/21.corebot-app-insights/README-LUIS.md +++ /dev/null @@ -1,216 +0,0 @@ -# Setting up LUIS via CLI: - -This README contains information on how to create and deploy a LUIS application. When the bot is ready to be deployed to production, we recommend creating a LUIS Endpoint Resource for usage with your LUIS App. - -> _For instructions on how to create a LUIS Application via the LUIS portal, see these Quickstart steps:_ -> 1. _[Quickstart: Create a new app in the LUIS portal][Quickstart-create]_ -> 2. _[Quickstart: Deploy an app in the LUIS portal][Quickstart-deploy]_ - -[Quickstart-create]: https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-build-app -[Quickstart-deploy]:https://docs.microsoft.com/azure/cognitive-services/luis/get-started-portal-deploy-app - -## Table of Contents: - -- [Prerequisites](#Prerequisites) -- [Import a new LUIS Application using a local LUIS application](#Import-a-new-LUIS-Application-using-a-local-LUIS-application) -- [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#How-to-create-a-LUIS-Endpoint-resource-in-Azure-and-pair-it-with-a-LUIS-Application) - -___ - -## [Prerequisites](#Table-of-Contents): - -#### Install Azure CLI >=2.0.61: - -Visit the following page to find the correct installer for your OS: -- https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest - -#### Install LUIS CLI >=2.4.0: - -Open a CLI of your choice and type the following: - -```bash -npm i -g luis-apis@^2.4.0 -``` - -#### LUIS portal account: - -You should already have a LUIS account with either https://luis.ai, https://eu.luis.ai, or https://au.luis.ai. To determine where to create a LUIS account, consider where you will deploy your LUIS applications, and then place them in [the corresponding region][LUIS-Authoring-Regions]. - -After you've created your account, you need your [Authoring Key][LUIS-AKey] and a LUIS application ID. - -[LUIS-Authoring-Regions]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-reference-regions#luis-authoring-regions] -[LUIS-AKey]: https://docs.microsoft.com/azure/cognitive-services/luis/luis-concept-keys#authoring-key - -___ - -## [Import a new LUIS Application using a local LUIS application](#Table-of-Contents) - -### 1. Import the local LUIS application to luis.ai - -```bash -luis import application --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appName "FlightBooking" --in "./cognitiveModels/FlightBooking.json" -``` - -Outputs the following JSON: - -```json -{ - "id": "########-####-####-####-############", - "name": "FlightBooking", - "description": "A LUIS model that uses intent and entities.", - "culture": "en-us", - "usageScenario": "", - "domain": "", - "versionsCount": 1, - "createdDateTime": "2019-03-29T18:32:02Z", - "endpoints": {}, - "endpointHitsCount": 0, - "activeVersion": "0.1", - "ownerEmail": "bot@contoso.com", - "tokenizerVersion": "1.0.0" -} -``` - -For the next step, you'll need the `"id"` value for `--appId` and the `"activeVersion"` value for `--versionId`. - -### 2. Train the LUIS Application - -```bash -luis train version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --wait -``` - -### 3. Publish the LUIS Application - -```bash -luis publish version --region "LuisAppAuthoringRegion" --authoringKey "LuisAuthoringKey" --appId "LuisAppId" --versionId "LuisAppversion" --publishRegion "LuisAppPublishRegion" -``` - -> `--region` corresponds to the region you _author_ your application in. The regions available for this are "westus", "westeurope" and "australiaeast".
-> These regions correspond to the three available portals, https://luis.ai, https://eu.luis.ai, or https://au.luis.ai.
-> `--publishRegion` corresponds to the region of the endpoint you're publishing to, (e.g. "westus", "southeastasia", "westeurope", "brazilsouth").
-> See the [reference docs][Endpoint-API] for a list of available publish/endpoint regions. - -[Endpoint-API]: https://westus.dev.cognitive.microsoft.com/docs/services/5819c76f40a6350ce09de1ac/operations/5819c77140a63516d81aee78 - -Outputs the following: - -```json - { - "versionId": "0.1", - "isStaging": false, - "endpointUrl": "https://westus.api.cognitive.microsoft.com/luis/v2.0/apps/########-####-####-####-############", - "region": "westus", - "assignedEndpointKey": null, - "endpointRegion": "westus", - "failedRegions": "", - "publishedDateTime": "2019-03-29T18:40:32Z", - "directVersionPublish": false -} -``` - -To see how to create an LUIS Cognitive Service Resource in Azure, please see [the next README][README-LUIS]. This Resource should be used when you want to move your bot to production. The instructions will show you how to create and pair the resource with a LUIS Application. - -[README-LUIS]: ./README-LUIS.md - -___ - -## [How to create a LUIS Endpoint resource in Azure and pair it with a LUIS Application](#Table-of-Contents) - -### 1. Create a new LUIS Cognitive Services resource on Azure via Azure CLI - -> _Note:_
-> _If you don't have a Resource Group in your Azure subscription, you can create one through the Azure portal or through using:_ -> ```bash -> az group create --subscription "AzureSubscriptionGuid" --location "westus" --name "ResourceGroupName" -> ``` -> _To see a list of valid locations, use `az account list-locations`_ - - -```bash -# Use Azure CLI to create the LUIS Key resource on Azure -az cognitiveservices account create --kind "luis" --name "NewLuisResourceName" --sku "S0" --location "westus" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -The command will output a response similar to the JSON below: - -```json -{ - "endpoint": "https://westus.api.cognitive.microsoft.com/luis/v2.0", - "etag": "\"########-####-####-####-############\"", - "id": "/subscriptions/########-####-####-####-############/resourceGroups/ResourceGroupName/providers/Microsoft.CognitiveServices/accounts/NewLuisResourceName", - "internalId": "################################", - "kind": "luis", - "location": "westus", - "name": "NewLuisResourceName", - "provisioningState": "Succeeded", - "resourceGroup": "ResourceGroupName", - "sku": { - "name": "S0", - "tier": null - }, - "tags": null, - "type": "Microsoft.CognitiveServices/accounts" -} -``` - - - -Take the output from the previous command and create a JSON file in the following format: - -```json -{ - "azureSubscriptionId": "00000000-0000-0000-0000-000000000000", - "resourceGroup": "ResourceGroupName", - "accountName": "NewLuisResourceName" -} -``` - -### 2. Retrieve ARM access token via Azure CLI - -```bash -az account get-access-token --subscription "AzureSubscriptionGuid" -``` - -This will return an object that looks like this: - -```json -{ - "accessToken": "eyJ0eXAiOiJKVtokentokentokentokentokeng1dCI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyIsItokenI6Ik4tbEMwbi05REFMcXdodUhZbkhRNjNHZUNYYyJ9.eyJhdWQiOiJodHRwczovL21hbmFnZW1lbnQuY29yZS53aW5kb3dzLm5ldC8iLCJpc3MiOiJodHRwczovL3N0cy53aW5kb3dzLm5ldC83MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDcvIiwiaWF0IjoxNTUzODc3MTUwLCJuYmYiOjE1NTM4NzcxNTAsImV4cCI6MTU1Mzg4MTA1MCwiX2NsYWltX25hbWVzIjp7Imdyb3VwcyI6InNyYzEifSwiX2NsYWltX3NvdXJjZXMiOnsic3JjMSI6eyJlbmRwb2ludCI6Imh0dHBzOi8vZ3JhcGgud2luZG93cy5uZXQvNzJmOTg4YmYtODZmMS00MWFmLTkxYWItMmQ3Y2QwMTFkYjQ3L3VzZXJzL2ZmZTQyM2RkLWJhM2YtNDg0Ny04NjgyLWExNTI5MDA4MjM4Ny9nZXRNZW1iZXJPYmplY3RzIn19LCJhY3IiOiIxIiwiYWlvIjoiQVZRQXEvOEtBQUFBeGVUc201NDlhVHg4RE1mMFlRVnhGZmxxOE9RSC9PODR3QktuSmRqV1FqTkkwbmxLYzB0bHJEZzMyMFZ5bWZGaVVBSFBvNUFFUTNHL0FZNDRjdk01T3M0SEt0OVJkcE5JZW9WU0dzd0kvSkk9IiwiYW1yIjpbIndpYSIsIm1mYSJdLCJhcHBpZCI6IjA0YjA3Nzk1LThkZGItNDYxYS1iYmVlLTAyZjllMWJmN2I0NiIsImFwcGlkYWNyIjoiMCIsImRldmljZWlkIjoiNDhmNDVjNjEtMTg3Zi00MjUxLTlmZWItMTllZGFkZmMwMmE3IiwiZmFtaWx5X25hbWUiOiJHdW0iLCJnaXZlbl9uYW1lIjoiU3RldmVuIiwiaXBhZGRyIjoiMTY3LjIyMC4yLjU1IiwibmFtZSI6IlN0ZXZlbiBHdW0iLCJvaWQiOiJmZmU0MjNkZC1iYTNmLTQ4NDctODY4Mi1hMTUyOTAwODIzODciLCJvbnByZW1fc2lkIjoiUy0xLTUtMjEtMjEyNzUyMTE4NC0xNjA0MDEyOTIwLTE4ODc5Mjc1MjctMjYwOTgyODUiLCJwdWlkIjoiMTAwMzdGRkVBMDQ4NjlBNyIsInJoIjoiSSIsInNjcCI6InVzZXJfaW1wZXJzb25hdGlvbiIsInN1YiI6Ik1rMGRNMWszN0U5ckJyMjhieUhZYjZLSU85LXVFQVVkZFVhNWpkSUd1Nk0iLCJ0aWQiOiI3MmY5ODhiZi04NmYxLTQxYWYtOTFhYi0yZDdjZDAxMWRiNDciLCJ1bmlxdWVfbmFtZSI6InN0Z3VtQG1pY3Jvc29mdC5jb20iLCJ1cG4iOiJzdGd1bUBtaWNyb3NvZnQuY29tIiwidXRpIjoiT2w2NGN0TXY4RVNEQzZZQWRqRUFtokenInZlciI6IjEuMCJ9.kFAsEilE0mlS1pcpqxf4rEnRKeYsehyk-gz-zJHUrE__oad3QjgDSBDPrR_ikLdweynxbj86pgG4QFaHURNCeE6SzrbaIrNKw-n9jrEtokenlosOxg_0l2g1LeEUOi5Q4gQREAU_zvSbl-RY6sAadpOgNHtGvz3Rc6FZRITfkckSLmsKAOFoh-aWC6tFKG8P52rtB0qVVRz9tovBeNqkMYL49s9ypduygbXNVwSQhm5JszeWDgrFuVFHBUP_iENCQYGQpEZf_KvjmX1Ur1F9Eh9nb4yI2gFlKncKNsQl-tokenK7-tokentokentokentokentokentokenatoken", - "expiresOn": "2200-12-31 23:59:59.999999", - "subscription": "AzureSubscriptionGuid", - "tenant": "tenant-guid", - "tokenType": "Bearer" -} -``` - -The value needed for the next step is the `"accessToken"`. - -### 3. Use `luis add appazureaccount` to pair your LUIS resource with a LUIS Application - -```bash -luis add appazureaccount --in "path/to/created/requestBody.json" --appId "LuisAppId" --authoringKey "LuisAuthoringKey" --armToken "accessToken" -``` - -If successful, it should yield a response like this: - -```json -{ - "code": "Success", - "message": "Operation Successful" -} -``` - -### 4. See the LUIS Cognitive Services' keys - -```bash -az cognitiveservices account keys list --name "NewLuisResourceName" --subscription "AzureSubscriptionGuid" -g "ResourceGroupName" -``` - -This will return an object that looks like this: - -```json -{ - "key1": "9a69####dc8f####8eb4####399f####", - "key2": "####f99e####4b1a####fb3b####6b9f" -} -``` diff --git a/samples/21.corebot-app-insights/README.md b/samples/21.corebot-app-insights/README.md deleted file mode 100644 index 3f93f6aeb..000000000 --- a/samples/21.corebot-app-insights/README.md +++ /dev/null @@ -1,70 +0,0 @@ -# CoreBot with Application Insights - -Bot Framework v4 core bot sample. - -This bot has been created using [Bot Framework](https://dev.botframework.com), it shows how to: - -- Use [LUIS](https://www.luis.ai) to implement core AI capabilities -- Implement a multi-turn conversation using Dialogs -- Handle user interruptions for such things as `Help` or `Cancel` -- Prompt for and validate requests for information from the user -- Use [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/cloudservices) to monitor your bot - -## Prerequisites - -This sample **requires** prerequisites in order to run. - -### Overview - -This bot uses [LUIS](https://www.luis.ai), an AI based cognitive service, to implement language understanding -and [Application Insights](https://docs.microsoft.com/azure/azure-monitor/app/cloudservices), an extensible Application Performance Management (APM) service for web developers on multiple platforms. - -### Create a LUIS Application to enable language understanding - -LUIS language model setup, training, and application configuration steps can be found [here](https://docs.microsoft.com/azure/bot-service/bot-builder-howto-v4-luis?view=azure-bot-service-4.0&tabs=cs). - -If you wish to create a LUIS application via the CLI, these steps can be found in the [README-LUIS.md](README-LUIS.md). - -### Add Application Insights service to enable the bot monitoring - -Application Insights resource creation steps can be found [here](https://docs.microsoft.com/azure/azure-monitor/app/create-new-resource). - -## To try this sample - -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-corebot-app-insights-sample.jar` - -## Testing the bot using Bot Framework Emulator - -[Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - -- Install the latest Bot Framework Emulator from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - -### Connect to the bot using Bot Framework Emulator - -- Launch Bot Framework Emulator -- File -> Open Bot -- Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -To learn more about deploying a bot to Azure, see [Deploy your bot to Azure](https://aka.ms/azuredeployment) for a complete list of deployment instructions. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Language Understanding using LUIS](https://docs.microsoft.com/en-us/azure/cognitive-services/luis/) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Application insights Overview](https://docs.microsoft.com/azure/azure-monitor/app/app-insights-overview) -- [Getting Started with Application Insights](https://github.com/Microsoft/ApplicationInsights-aspnetcore/wiki/Getting-Started-with-Application-Insights-for-ASP.NET-Core) -- [Filtering and preprocessing telemetry in the Application Insights SDK](https://docs.microsoft.com/azure/azure-monitor/app/api-filtering-sampling) diff --git a/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json b/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json deleted file mode 100644 index 89aad31ae..000000000 --- a/samples/21.corebot-app-insights/cognitiveModels/flightbooking.json +++ /dev/null @@ -1,339 +0,0 @@ -{ - "luis_schema_version": "3.2.0", - "versionId": "0.1", - "name": "FlightBooking", - "desc": "Luis Model for CoreBot", - "culture": "en-us", - "tokenizerVersion": "1.0.0", - "intents": [ - { - "name": "BookFlight" - }, - { - "name": "Cancel" - }, - { - "name": "GetWeather" - }, - { - "name": "None" - } - ], - "entities": [], - "composites": [ - { - "name": "From", - "children": [ - "Airport" - ], - "roles": [] - }, - { - "name": "To", - "children": [ - "Airport" - ], - "roles": [] - } - ], - "closedLists": [ - { - "name": "Airport", - "subLists": [ - { - "canonicalForm": "Paris", - "list": [ - "paris", - "cdg" - ] - }, - { - "canonicalForm": "London", - "list": [ - "london", - "lhr" - ] - }, - { - "canonicalForm": "Berlin", - "list": [ - "berlin", - "txl" - ] - }, - { - "canonicalForm": "New York", - "list": [ - "new york", - "jfk" - ] - }, - { - "canonicalForm": "Seattle", - "list": [ - "seattle", - "sea" - ] - } - ], - "roles": [] - } - ], - "patternAnyEntities": [], - "regex_entities": [], - "prebuiltEntities": [ - { - "name": "datetimeV2", - "roles": [] - } - ], - "model_features": [], - "regex_features": [], - "patterns": [], - "utterances": [ - { - "text": "book a flight", - "intent": "BookFlight", - "entities": [] - }, - { - "text": "book a flight from new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 26 - } - ] - }, - { - "text": "book a flight from seattle", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 19, - "endPos": 25 - } - ] - }, - { - "text": "book a hotel in new york", - "intent": "None", - "entities": [] - }, - { - "text": "book a restaurant", - "intent": "None", - "entities": [] - }, - { - "text": "book flight from london to paris on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 17, - "endPos": 22 - }, - { - "entity": "To", - "startPos": 27, - "endPos": 31 - } - ] - }, - { - "text": "book flight to berlin on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 15, - "endPos": 20 - } - ] - }, - { - "text": "book me a flight from london to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 22, - "endPos": 27 - }, - { - "entity": "To", - "startPos": 32, - "endPos": 36 - } - ] - }, - { - "text": "bye", - "intent": "Cancel", - "entities": [] - }, - { - "text": "cancel booking", - "intent": "Cancel", - "entities": [] - }, - { - "text": "exit", - "intent": "Cancel", - "entities": [] - }, - { - "text": "find an airport near me", - "intent": "None", - "entities": [] - }, - { - "text": "flight to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "flight to paris from london on feb 14th", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - }, - { - "entity": "From", - "startPos": 21, - "endPos": 26 - } - ] - }, - { - "text": "fly from berlin to paris on may 5th", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 9, - "endPos": 14 - }, - { - "entity": "To", - "startPos": 19, - "endPos": 23 - } - ] - }, - { - "text": "go to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 6, - "endPos": 10 - } - ] - }, - { - "text": "going from paris to berlin", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 11, - "endPos": 15 - }, - { - "entity": "To", - "startPos": 20, - "endPos": 25 - } - ] - }, - { - "text": "i'd like to rent a car", - "intent": "None", - "entities": [] - }, - { - "text": "ignore", - "intent": "Cancel", - "entities": [] - }, - { - "text": "travel from new york to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "From", - "startPos": 12, - "endPos": 19 - }, - { - "entity": "To", - "startPos": 24, - "endPos": 28 - } - ] - }, - { - "text": "travel to new york", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 17 - } - ] - }, - { - "text": "travel to paris", - "intent": "BookFlight", - "entities": [ - { - "entity": "To", - "startPos": 10, - "endPos": 14 - } - ] - }, - { - "text": "what's the forecast for this friday?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like for tomorrow", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like in new york", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "what's the weather like?", - "intent": "GetWeather", - "entities": [] - }, - { - "text": "winter is coming", - "intent": "None", - "entities": [] - } - ], - "settings": [] -} diff --git a/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json b/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/21.corebot-app-insights/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json b/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/21.corebot-app-insights/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/21.corebot-app-insights/pom.xml b/samples/21.corebot-app-insights/pom.xml deleted file mode 100644 index 48e11bbec..000000000 --- a/samples/21.corebot-app-insights/pom.xml +++ /dev/null @@ -1,253 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-corebot-app-insights - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Core Bot sample with Application Insights using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.corebot.app.insights.Application - - - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - - - com.microsoft.bot - bot-ai-luis-v3 - 4.13.0-SNAPSHOT - - - com.microsoft.bot - bot-applicationinsights - 4.13.0-SNAPSHOT - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.corebot.app.insights.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java deleted file mode 100644 index fb82e8328..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/AdapterWithErrorHandler.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware; -import com.microsoft.bot.builder.BotTelemetryClient; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.connector.Channels; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.slf4j.LoggerFactory; - -import javax.annotation.Nullable; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.CompletableFuture; - -/** - * An Adapter that provides exception handling. - */ -public class AdapterWithErrorHandler extends BotFrameworkHttpAdapter { - - private static final String ERROR_MSG_ONE = "The bot encountered an error or bug."; - private static final String ERROR_MSG_TWO = - "To continue to run this bot, please fix the bot source code."; - // Create field for telemetry client. Add IBotTelemetryClient parameter to AdapterWithErrorHandler - private BotTelemetryClient adapterBotTelemetryClient; - - /** - * Constructs an error handling BotFrameworkHttpAdapter by providing an - * {@link com.microsoft.bot.builder.OnTurnErrorHandler}. - * - *

- * For this sample, a simple message is displayed. For a production Bot, a more - * informative message or action is likely preferred. - *

- * - * @param withConfiguration The Configuration object to use. - * @param telemetryInitializerMiddleware The TelemetryInitializerMiddleware object to use. - * @param botTelemetryClient The BotTelemetryClient object to use. - * @param withConversationState The ConversationState object to use. - */ - public AdapterWithErrorHandler( - Configuration withConfiguration, - TelemetryInitializerMiddleware telemetryInitializerMiddleware, - BotTelemetryClient botTelemetryClient, - @Nullable ConversationState withConversationState) { - super(withConfiguration); - this.use(telemetryInitializerMiddleware); - - // Use telemetry client so that we can trace exceptions into Application Insights - this.adapterBotTelemetryClient = botTelemetryClient; - setOnTurnError((turnContext, exception) -> { - // Track exceptions into Application Insights - // Set up some properties for our exception tracing to give more information - Map properties = new HashMap(); - properties.put("Bot exception caught in", "AdapterWithErrorHandler - OnTurnError"); - - // Send the exception telemetry: - adapterBotTelemetryClient.trackException((Exception) exception, properties, null); - - // Log any leaked exception from the application. - // NOTE: In production environment, you should consider logging this to - // Azure Application Insights. Visit https://aka.ms/bottelemetry to see how - // to add telemetry capture to your bot. - LoggerFactory.getLogger(AdapterWithErrorHandler.class).error("onTurnError", exception); - - // Send a message to the user - return turnContext.sendActivities( - MessageFactory.text(ERROR_MSG_ONE), MessageFactory.text(ERROR_MSG_TWO) - ).thenCompose(resourceResponse -> sendTraceActivity(turnContext, exception)) - .thenCompose(stageResult -> { - if (withConversationState != null) { - // Delete the conversationState for the current conversation to prevent the - // bot from getting stuck in a error-loop caused by being in a bad state. - // ConversationState should be thought of as similar to "cookie-state" in a - // Web pages. - return withConversationState.delete(turnContext) - .exceptionally(deleteException -> { - LoggerFactory.getLogger(AdapterWithErrorHandler.class) - .error("ConversationState.delete", deleteException); - return null; - }); - } - return CompletableFuture.completedFuture(null); - }); - }); - } - - private CompletableFuture sendTraceActivity( - TurnContext turnContext, - Throwable exception - ) { - if (StringUtils.equals(turnContext.getActivity().getChannelId(), Channels.EMULATOR)) { - Activity traceActivity = new Activity(ActivityTypes.TRACE); - traceActivity.setLabel("TurnError"); - traceActivity.setName("OnTurnError Trace"); - traceActivity.setValue(ExceptionUtils.getStackTrace(exception)); - traceActivity.setValueType("https://www.botframework.com/schemas/error"); - - // Send a trace activity, which will be displayed in the Bot Framework Emulator - return turnContext.sendActivity(traceActivity).thenApply(resourceResponse -> null); - } - - return CompletableFuture.completedFuture(null); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java deleted file mode 100644 index 6cc9570e7..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/Application.java +++ /dev/null @@ -1,118 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.applicationinsights.ApplicationInsightsBotTelemetryClient; -import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware; -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.BotTelemetryClient; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.NullBotTelemetryClient; -import com.microsoft.bot.builder.Storage; -import com.microsoft.bot.builder.TelemetryLoggerMiddleware; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.apache.commons.lang3.StringUtils; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -/** - * This is the starting point of the Sprint Boot Bot application. - */ -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - /** - * The start method. - * - * @param args The args. - */ - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method with the - * @Bean annotation. - *

- * - * @param configuration The Configuration object to use. - * @param userState The UserState object to use. - * @param conversationState The ConversationState object to use. - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - Configuration configuration, - UserState userState, - ConversationState conversationState - ) { - BotTelemetryClient botTelemetryClient = getBotTelemetryClient(configuration); - FlightBookingRecognizer recognizer = new FlightBookingRecognizer(configuration, botTelemetryClient); - MainDialog dialog = new MainDialog(recognizer, new BookingDialog(), botTelemetryClient); - - return new DialogAndWelcomeBot<>(conversationState, userState, dialog); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - Storage storage = getStorage(); - ConversationState conversationState = getConversationState(storage); - BotTelemetryClient botTelemetryClient = getBotTelemetryClient(configuration); - TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(botTelemetryClient, false); - TelemetryInitializerMiddleware telemetryInitializerMiddleware = - new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, false); - - AdapterWithErrorHandler adapter = new AdapterWithErrorHandler( - configuration, - telemetryInitializerMiddleware, - botTelemetryClient, - conversationState); - - return adapter; - } - - /** - * Returns a Bot Telemetry Client. - * - * @param configuration The Configuration object to use. - * @return A Bot Telemetry Client. - */ - @Bean - public BotTelemetryClient getBotTelemetryClient(Configuration configuration) { - String instrumentationKey = configuration.getProperty("ApplicationInsights.InstrumentationKey"); - if (StringUtils.isNotBlank(instrumentationKey)) { - return new ApplicationInsightsBotTelemetryClient(instrumentationKey); - } - - return new NullBotTelemetryClient(); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java deleted file mode 100644 index a4bd9b202..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDetails.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -/** - * The model class to retrieve the information of the booking. - */ -public class BookingDetails { - private String destination; - private String origin; - private String travelDate; - - /** - * Gets the destination of the booking. - * - * @return The destination. - */ - public String getDestination() { - return destination; - } - - /** - * Sets the destination of the booking. - * - * @param withDestination The new destination. - */ - public void setDestination(String withDestination) { - destination = withDestination; - } - - /** - * Gets the origin of the booking. - * - * @return The origin. - */ - public String getOrigin() { - return origin; - } - - /** - * Sets the origin of the booking. - * - * @param withOrigin The new origin. - */ - public void setOrigin(String withOrigin) { - origin = withOrigin; - } - - /** - * Gets the travel date of the booking. - * - * @return The travel date. - */ - public String getTravelDate() { - return travelDate; - } - - /** - * Sets the travel date of the booking. - * - * @param withTravelDate The new travel date. - */ - public void setTravelDate(String withTravelDate) { - travelDate = withTravelDate; - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java deleted file mode 100644 index 92dc7e1db..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/BookingDialog.java +++ /dev/null @@ -1,129 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.ConfirmPrompt; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the booking dialogs. - */ -public class BookingDialog extends CancelAndHelpDialog { - private final String destinationStepMsgText = "Where would you like to travel to?"; - private final String originStepMsgText = "Where are you traveling from?"; - - /** - * The constructor of the Booking Dialog class. - */ - public BookingDialog() { - super("BookingDialog"); - - addDialog(new TextPrompt("TextPrompt")); - addDialog(new ConfirmPrompt("ConfirmPrompt")); - addDialog(new DateResolverDialog(null)); - WaterfallStep[] waterfallSteps = { - this::destinationStep, - this::originStep, - this::travelDateStep, - this::confirmStep, - this::finalStep - }; - - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture destinationStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - if (bookingDetails.getDestination().isEmpty()) { - Activity promptMessage = - MessageFactory.text(destinationStepMsgText, destinationStepMsgText, - InputHints.EXPECTING_INPUT - ); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getDestination()); - } - - private CompletableFuture originStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setDestination((String) stepContext.getResult()); - - if (bookingDetails.getOrigin().isEmpty()) { - Activity promptMessage = - MessageFactory - .text(originStepMsgText, originStepMsgText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - return stepContext.next(bookingDetails.getOrigin()); - } - - private CompletableFuture travelDateStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setOrigin((String) stepContext.getResult()); - - if (bookingDetails.getTravelDate() == null || isAmbiguous(bookingDetails.getTravelDate())) { - return stepContext.beginDialog("DateResolverDialog", bookingDetails.getTravelDate()); - } - - return stepContext.next(bookingDetails.getTravelDate()); - } - - private CompletableFuture confirmStep(WaterfallStepContext stepContext) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - - bookingDetails.setTravelDate((String) stepContext.getResult()); - - String messageText = - String.format( - "Please confirm, I have you traveling to: %s from: %s on: %s. Is this correct?", - bookingDetails.getDestination(), bookingDetails.getOrigin(), - bookingDetails.getTravelDate() - ); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - - return stepContext.prompt("ConfirmPrompt", promptOptions); - } - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - if ((Boolean) stepContext.getResult()) { - BookingDetails bookingDetails = (BookingDetails) stepContext.getOptions(); - return stepContext.endDialog(bookingDetails); - } - - return stepContext.endDialog(null); - } - - private static boolean isAmbiguous(String timex) { - TimexProperty timexProperty = new TimexProperty(timex); - return !timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java deleted file mode 100644 index db4c1675b..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/CancelAndHelpDialog.java +++ /dev/null @@ -1,80 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.DialogTurnStatus; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.ActivityTypes; -import com.microsoft.bot.schema.InputHints; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of the dialog interruptions. - */ -public class CancelAndHelpDialog extends ComponentDialog { - - private final String helpMsgText = "Show help here"; - private final String cancelMsgText = "Cancelling..."; - - /** - * The constructor of the CancelAndHelpDialog class. - * - * @param id The dialog's Id. - */ - public CancelAndHelpDialog(String id) { - super(id); - } - - /** - * Called when the dialog is _continued_, where it is the active dialog and the user replies - * with a new activity. - * - * @param innerDc innerDc The inner {@link DialogContext} for the current turn of conversation. - * @return A {@link CompletableFuture} representing the asynchronous operation. If the task is - * successful, the result indicates whether the dialog is still active after the turn has been - * processed by the dialog. The result may also contain a return value. - */ - @Override - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - return interrupt(innerDc).thenCompose(result -> { - if (result != null) { - return CompletableFuture.completedFuture(result); - } - return super.onContinueDialog(innerDc); - }); - } - - private CompletableFuture interrupt(DialogContext innerDc) { - if (innerDc.getContext().getActivity().isType(ActivityTypes.MESSAGE)) { - String text = innerDc.getContext().getActivity().getText().toLowerCase(); - - switch (text) { - case "help": - case "?": - Activity helpMessage = MessageFactory - .text(helpMsgText, helpMsgText, InputHints.EXPECTING_INPUT); - return innerDc.getContext().sendActivity(helpMessage) - .thenCompose(sendResult -> - CompletableFuture - .completedFuture(new DialogTurnResult(DialogTurnStatus.WAITING))); - case "cancel": - case "quit": - Activity cancelMessage = MessageFactory - .text(cancelMsgText, cancelMsgText, InputHints.IGNORING_INPUT); - return innerDc.getContext() - .sendActivity(cancelMessage) - .thenCompose(sendResult -> innerDc.cancelAllDialogs()); - default: - break; - } - } - - return CompletableFuture.completedFuture(null); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java deleted file mode 100644 index cde5d96a0..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DateResolverDialog.java +++ /dev/null @@ -1,107 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.DateTimePrompt; -import com.microsoft.bot.dialogs.prompts.DateTimeResolution; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.PromptValidatorContext; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.Constants; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import javax.annotation.Nullable; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the date resolver dialogs. - */ -public class DateResolverDialog extends CancelAndHelpDialog { - private final String promptMsgText = "When would you like to travel?"; - private final String repromptMsgText = - "I'm sorry, to make your booking please enter a full travel date including Day Month and Year."; - - /** - * The constructor of the DateResolverDialog class. - * @param id The dialog's id. - */ - public DateResolverDialog(@Nullable String id) { - super(id != null ? id : "DateResolverDialog"); - - addDialog(new DateTimePrompt("DateTimePrompt", - DateResolverDialog::dateTimePromptValidator, null)); - WaterfallStep[] waterfallSteps = { - this::initialStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture initialStep(WaterfallStepContext stepContext) { - String timex = (String) stepContext.getOptions(); - - Activity promptMessage = MessageFactory.text(promptMsgText, promptMsgText, InputHints.EXPECTING_INPUT); - Activity repromptMessage = MessageFactory.text(repromptMsgText, repromptMsgText, InputHints.EXPECTING_INPUT); - - if (timex == null) { - // We were not given any date at all so prompt the user. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - promptOptions.setRetryPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - // We have a Date we just need to check it is unambiguous. - TimexProperty timexProperty = new TimexProperty(timex); - if (!timexProperty.getTypes().contains(Constants.TimexTypes.DEFINITE)) { - // This is essentially a "reprompt" of the data we were given up front. - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(repromptMessage); - return stepContext.prompt("DateTimePrompt", promptOptions); - } - - DateTimeResolution dateTimeResolution = new DateTimeResolution(); - dateTimeResolution.setTimex(timex); - List dateTimeResolutions = new ArrayList(); - dateTimeResolutions.add(dateTimeResolution); - return stepContext.next(dateTimeResolutions); - } - - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - String timex = ((ArrayList) stepContext.getResult()).get(0).getTimex(); - return stepContext.endDialog(timex); - } - - private static CompletableFuture dateTimePromptValidator( - PromptValidatorContext> promptContext - ) { - if (promptContext.getRecognized().getSucceeded()) { - // This value will be a TIMEX. And we are only interested in a Date so grab the first result and drop the - // Time part. TIMEX is a format that represents DateTime expressions that include some ambiguity. - // e.g. missing a Year. - String timex = ((List) promptContext.getRecognized().getValue()) - .get(0).getTimex().split("T")[0]; - - // If this is a definite Date including year, month and day we are good otherwise reprompt. - // A better solution might be to let the user know what part is actually missing. - Boolean isDefinite = new TimexProperty(timex).getTypes().contains(Constants.TimexTypes.DEFINITE); - - return CompletableFuture.completedFuture(isDefinite); - } - - return CompletableFuture.completedFuture(false); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java deleted file mode 100644 index fd3bf4ce4..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogAndWelcomeBot.java +++ /dev/null @@ -1,98 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.applicationinsights.core.dependencies.apachecommons.io.IOUtils; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.Attachment; -import com.microsoft.bot.schema.ChannelAccount; -import com.microsoft.bot.schema.Serialization; -import org.apache.commons.lang3.StringUtils; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * The class containing the welcome dialog. - * - * @param is a Dialog. - */ -public class DialogAndWelcomeBot extends DialogBot { - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogAndWelcomeBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - super(withConversationState, withUserState, withDialog); - } - - /** - * When the {@link #onConversationUpdateActivity(TurnContext)} method receives a conversation - * update activity that indicates one or more users other than the bot are joining the - * conversation, it calls this method. - * - * @param membersAdded A list of all the members added to the conversation, as described by the - * conversation update activity - * @param turnContext The context object for this turn. - * @return A task that represents the work queued to execute. - */ - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - // Greet anyone that was not the target (recipient) of this message. - // To learn more about Adaptive Cards, see https://aka.ms/msbot-adaptivecards for more details. - Attachment welcomeCard = createAdaptiveCardAttachment(); - Activity response = MessageFactory - .attachment(welcomeCard, null, "Welcome to Bot Framework!", null); - - return turnContext.sendActivity(response).thenApply(sendResult -> { - return Dialog.run(getDialog(), turnContext, - getConversationState().createProperty("DialogState") - ); - }); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - // Load attachment from embedded resource. - private Attachment createAdaptiveCardAttachment() { - try ( - InputStream inputStream = Thread.currentThread(). - getContextClassLoader().getResourceAsStream("cards/welcomeCard.json") - ) { - String adaptiveCardJson = IOUtils - .toString(inputStream, StandardCharsets.UTF_8.toString()); - - Attachment attachment = new Attachment(); - attachment.setContentType("application/vnd.microsoft.card.adaptive"); - attachment.setContent(Serialization.jsonToTree(adaptiveCardJson)); - return attachment; - - } catch (IOException e) { - e.printStackTrace(); - return new Attachment(); - } - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java deleted file mode 100644 index 5fe2be5d0..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/DialogBot.java +++ /dev/null @@ -1,127 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import org.slf4j.LoggerFactory; - -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allow - * multiple different bots to be run at different endpoints within the same project. This can be - * achieved by defining distinct Controller types each with dependency on distinct Bot types. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been - * used in a Dialog implementation, and the requirement is that all BotState objects are saved at - * the end of a turn. - * - * @param parameter of a type inheriting from Dialog - */ -public class DialogBot extends ActivityHandler { - - private Dialog dialog; - private BotState conversationState; - private BotState userState; - - /** - * Gets the dialog in use. - * - * @return instance of dialog - */ - protected Dialog getDialog() { - return dialog; - } - - /** - * Gets the conversation state. - * - * @return instance of conversationState - */ - protected BotState getConversationState() { - return conversationState; - } - - /** - * Gets the user state. - * - * @return instance of userState - */ - protected BotState getUserState() { - return userState; - } - - /** - * Sets the dialog in use. - * - * @param withDialog the dialog (of Dialog type) to be set - */ - protected void setDialog(Dialog withDialog) { - dialog = withDialog; - } - - /** - * Sets the conversation state. - * - * @param withConversationState the conversationState (of BotState type) to be set - */ - protected void setConversationState(BotState withConversationState) { - conversationState = withConversationState; - } - - /** - * Sets the user state. - * - * @param withUserState the userState (of BotState type) to be set - */ - protected void setUserState(BotState withUserState) { - userState = withUserState; - } - - /** - * Creates a DialogBot. - * - * @param withConversationState ConversationState to use in the bot - * @param withUserState UserState to use - * @param withDialog Param inheriting from Dialog class - */ - public DialogBot( - ConversationState withConversationState, UserState withUserState, T withDialog - ) { - this.conversationState = withConversationState; - this.userState = withUserState; - this.dialog = withDialog; - } - - /** - * Saves the BotState objects at the end of each turn. - * - * @param turnContext - * @return - */ - @Override - public CompletableFuture onTurn(TurnContext turnContext) { - return super.onTurn(turnContext) - .thenCompose(turnResult -> conversationState.saveChanges(turnContext, false)) - .thenCompose(saveResult -> userState.saveChanges(turnContext, false)); - } - - /** - * This method is executed when the turnContext receives a message activity. - * - * @param turnContext - * @return - */ - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - LoggerFactory.getLogger(DialogBot.class).info("Running dialog with Message Activity."); - - // Run the Dialog with the new message Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java deleted file mode 100644 index 0693ab236..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/FlightBookingRecognizer.java +++ /dev/null @@ -1,156 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import org.apache.commons.lang3.StringUtils; -import com.microsoft.bot.ai.luis.LuisApplication; -import com.microsoft.bot.ai.luis.LuisRecognizer; -import com.microsoft.bot.ai.luis.LuisRecognizerOptionsV3; -import com.microsoft.bot.builder.BotTelemetryClient; -import com.microsoft.bot.builder.Recognizer; -import com.microsoft.bot.builder.RecognizerResult; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.integration.Configuration; - -import java.util.concurrent.CompletableFuture; - -/** - * The class in charge of recognizing the booking information. - */ -public class FlightBookingRecognizer implements Recognizer { - private LuisRecognizer recognizer = null; - - /** - * The constructor of the FlightBookingRecognizer class. - * - * @param configuration The Configuration object to use. - * @param telemetryClient The BotTelemetryClient to use. - */ - public FlightBookingRecognizer(Configuration configuration, BotTelemetryClient telemetryClient) { - Boolean luisIsConfigured = StringUtils.isNotBlank(configuration.getProperty("LuisAppId")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIKey")) - && StringUtils.isNotBlank(configuration.getProperty("LuisAPIHostName")); - - if (luisIsConfigured) { - LuisApplication luisApplication = new LuisApplication( - configuration.getProperty("LuisAppId"), - configuration.getProperty("LuisAPIKey"), - "https://".concat(configuration.getProperty("LuisAPIHostName")) - ); - - // Set the recognizer options depending on which endpoint version you want to use. - // More details can be found in - // https://docs.microsoft.com/en-gb/azure/cognitive-services/luis/luis-migration-api-v3 - LuisRecognizerOptionsV3 recognizerOptions = new LuisRecognizerOptionsV3(luisApplication); - recognizerOptions.setTelemetryClient(telemetryClient); - - recognizer = new LuisRecognizer(recognizerOptions); - } - } - - /** - * Verify if the recognizer is configured. - * - * @return True if it's configured, False if it's not. - */ - public Boolean isConfigured() { - return this.recognizer != null; - } - - /** - * Return an object with preformatted LUIS results for the bot's dialogs to consume. - * - * @param context A {link TurnContext} - * @return A {link RecognizerResult} - */ - public CompletableFuture executeLuisQuery(TurnContext context) { - // Returns true if luis is configured in the application.properties and initialized. - return this.recognizer.recognize(context); - } - - /** - * Gets the From data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the From data. - */ - public ObjectNode getFromEntities(RecognizerResult result) { - String fromValue = "", fromAirportValue = ""; - if (result.getEntities().get("$instance").get("From") != null) { - fromValue = result.getEntities().get("$instance").get("From").get(0).get("text") - .asText(); - } - if (!fromValue.isEmpty() - && result.getEntities().get("From").get(0).get("Airport") != null) { - fromAirportValue = result.getEntities().get("From").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("from", fromValue); - entitiesNode.put("airport", fromAirportValue); - return entitiesNode; - } - - /** - * Gets the To data from the entities which is part of the result. - * - * @param result The recognizer result. - * @return The object node representing the To data. - */ - public ObjectNode getToEntities(RecognizerResult result) { - String toValue = "", toAirportValue = ""; - if (result.getEntities().get("$instance").get("To") != null) { - toValue = result.getEntities().get("$instance").get("To").get(0).get("text").asText(); - } - if (!toValue.isEmpty() && result.getEntities().get("To").get(0).get("Airport") != null) { - toAirportValue = result.getEntities().get("To").get(0).get("Airport").get(0).get(0) - .asText(); - } - - ObjectMapper mapper = new ObjectMapper().findAndRegisterModules(); - ObjectNode entitiesNode = mapper.createObjectNode(); - entitiesNode.put("to", toValue); - entitiesNode.put("airport", toAirportValue); - return entitiesNode; - } - - /** - * This value will be a TIMEX. And we are only interested in a Date so grab the first result and - * drop the Time part. TIMEX is a format that represents DateTime expressions that include some - * ambiguity. e.g. missing a Year. - * - * @param result A {link RecognizerResult} - * @return The Timex value without the Time model - */ - public String getTravelDate(RecognizerResult result) { - JsonNode datetimeEntity = result.getEntities().get("datetime"); - if (datetimeEntity == null || datetimeEntity.get(0) == null) { - return null; - } - - JsonNode timex = datetimeEntity.get(0).get("timex"); - if (timex == null || timex.get(0) == null) { - return null; - } - - String datetime = timex.get(0).asText().split("T")[0]; - return datetime; - } - - /** - * Runs an utterance through a recognizer and returns a generic recognizer result. - * - * @param turnContext Turn context. - * @return Analysis of utterance. - */ - @Override - public CompletableFuture recognize(TurnContext turnContext) { - return this.recognizer.recognize(turnContext); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java deleted file mode 100644 index 841de553c..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/MainDialog.java +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.InputHints; -import com.microsoft.recognizers.datatypes.timex.expression.TimexProperty; - -import java.time.LocalDateTime; -import java.time.format.DateTimeFormatter; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.microsoft.bot.builder.BotTelemetryClient; -import org.apache.commons.lang3.StringUtils; - -/** - * The class containing the main dialog for the sample. - */ -public class MainDialog extends ComponentDialog { - - private final FlightBookingRecognizer luisRecognizer; - private final Integer plusDayValue = 7; - - /** - * The constructor of the Main Dialog class. - * - * @param withLuisRecognizer The FlightBookingRecognizer object. - * @param bookingDialog The BookingDialog object with booking dialogs. - * @param withTelemetryClient The BotTelemetryClient to use. - */ - public MainDialog(FlightBookingRecognizer withLuisRecognizer, - BookingDialog bookingDialog, - BotTelemetryClient withTelemetryClient) { - super("MainDialog"); - - luisRecognizer = withLuisRecognizer; - - // Set the telemetry client for this and all child dialogs - this.setTelemetryClient(withTelemetryClient); - - addDialog(new TextPrompt("TextPrompt")); - addDialog(bookingDialog); - WaterfallStep[] waterfallSteps = { - this::introStep, - this::actStep, - this::finalStep - }; - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - /** - * First step in the waterfall dialog. Prompts the user for a command. Currently, this expects a - * booking request, like "book me a flight from Paris to Berlin on march 22" Note that the - * sample LUIS model will only recognize Paris, Berlin, New York and London as airport cities. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture introStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - Activity text = MessageFactory.text("NOTE: LUIS is not configured. " - + "To enable all capabilities, add 'LuisAppId', 'LuisAPIKey' and 'LuisAPIHostName' " - + "to the application.properties file.", null, InputHints.IGNORING_INPUT); - return stepContext.getContext().sendActivity(text) - .thenCompose(sendResult -> stepContext.next(null)); - } - - // Use the text provided in FinalStepAsync or the default if it is the first time. - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMMM d, yyyy"); - String weekLaterDate = LocalDateTime.now().plusDays(plusDayValue).format(formatter); - String messageText = stepContext.getOptions() != null - ? stepContext.getOptions().toString() - : String.format("What can I help you with today?\n" - + "Say something like \"Book a flight from Paris to Berlin on %s\"", weekLaterDate); - Activity promptMessage = MessageFactory - .text(messageText, messageText, InputHints.EXPECTING_INPUT); - PromptOptions promptOptions = new PromptOptions(); - promptOptions.setPrompt(promptMessage); - return stepContext.prompt("TextPrompt", promptOptions); - } - - /** - * Second step in the waterfall. This will use LUIS to attempt to extract the origin, - * destination and travel dates. Then, it hands off to the bookingDialog child dialog to collect - * any remaining details. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture actStep(WaterfallStepContext stepContext) { - if (!luisRecognizer.isConfigured()) { - // LUIS is not configured, we just run the BookingDialog path with an empty BookingDetailsInstance. - return stepContext.beginDialog("BookingDialog", new BookingDetails()); - } - - // Call LUIS and gather any potential booking details. (Note the TurnContext has the response to the prompt.) - return luisRecognizer.recognize(stepContext.getContext()).thenCompose(luisResult -> { - switch (luisResult.getTopScoringIntent().intent) { - case "BookFlight": - // Extract the values for the composite entities from the LUIS result. - ObjectNode fromEntities = luisRecognizer.getFromEntities(luisResult); - ObjectNode toEntities = luisRecognizer.getToEntities(luisResult); - - // Show a warning for Origin and Destination if we can't resolve them. - return showWarningForUnsupportedCities( - stepContext.getContext(), fromEntities, toEntities) - .thenCompose(showResult -> { - // Initialize BookingDetails with any entities we may have found in the response. - BookingDetails bookingDetails = new BookingDetails(); - bookingDetails.setDestination(toEntities.get("airport").asText()); - bookingDetails.setOrigin(fromEntities.get("airport").asText()); - bookingDetails.setTravelDate(luisRecognizer.getTravelDate(luisResult)); - // Run the BookingDialog giving it whatever details we have from the LUIS call, - // it will fill out the remainder. - return stepContext.beginDialog("BookingDialog", bookingDetails); - } - ); - case "GetWeather": - // We haven't implemented the GetWeatherDialog so we just display a TODO message. - String getWeatherMessageText = "TODO: get weather flow here"; - Activity getWeatherMessage = MessageFactory - .text( - getWeatherMessageText, getWeatherMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(getWeatherMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - - default: - // Catch all for unhandled intents - String didntUnderstandMessageText = String.format( - "Sorry, I didn't get that. Please " - + " try asking in a different way (intent was %s)", - luisResult.getTopScoringIntent().intent - ); - Activity didntUnderstandMessage = MessageFactory - .text( - didntUnderstandMessageText, didntUnderstandMessageText, - InputHints.IGNORING_INPUT - ); - return stepContext.getContext().sendActivity(didntUnderstandMessage) - .thenCompose(resourceResponse -> stepContext.next(null)); - } - }); - } - - /** - * Shows a warning if the requested From or To cities are recognized as entities but they are - * not in the Airport entity list. In some cases LUIS will recognize the From and To composite - * entities as a valid cities but the From and To Airport values will be empty if those entity - * values can't be mapped to a canonical item in the Airport. - * - * @param turnContext A {@link WaterfallStepContext} - * @param fromEntities An ObjectNode with the entities of From object - * @param toEntities An ObjectNode with the entities of To object - * @return A task - */ - private static CompletableFuture showWarningForUnsupportedCities( - TurnContext turnContext, - ObjectNode fromEntities, - ObjectNode toEntities - ) { - List unsupportedCities = new ArrayList(); - - if (StringUtils.isNotBlank(fromEntities.get("from").asText()) - && StringUtils.isBlank(fromEntities.get("airport").asText())) { - unsupportedCities.add(fromEntities.get("from").asText()); - } - - if (StringUtils.isNotBlank(toEntities.get("to").asText()) - && StringUtils.isBlank(toEntities.get("airport").asText())) { - unsupportedCities.add(toEntities.get("to").asText()); - } - - if (!unsupportedCities.isEmpty()) { - String messageText = String.format( - "Sorry but the following airports are not supported: %s", - String.join(", ", unsupportedCities) - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - return turnContext.sendActivity(message) - .thenApply(sendResult -> null); - } - - return CompletableFuture.completedFuture(null); - } - - /** - * This is the final step in the main waterfall dialog. It wraps up the sample "book a flight" - * interaction with a simple confirmation. - * - * @param stepContext A {@link WaterfallStepContext} - * @return A {@link DialogTurnResult} - */ - private CompletableFuture finalStep(WaterfallStepContext stepContext) { - CompletableFuture stepResult = CompletableFuture.completedFuture(null); - - // If the child dialog ("BookingDialog") was cancelled, - // the user failed to confirm or if the intent wasn't BookFlight - // the Result here will be null. - if (stepContext.getResult() instanceof BookingDetails) { - // Now we have all the booking details call the booking service. - // If the call to the booking service was successful tell the user. - BookingDetails result = (BookingDetails) stepContext.getResult(); - TimexProperty timeProperty = new TimexProperty(result.getTravelDate()); - String travelDateMsg = timeProperty.toNaturalLanguage(LocalDateTime.now()); - String messageText = String.format("I have you booked to %s from %s on %s", - result.getDestination(), result.getOrigin(), travelDateMsg - ); - Activity message = MessageFactory - .text(messageText, messageText, InputHints.IGNORING_INPUT); - stepResult = stepContext.getContext().sendActivity(message).thenApply(sendResult -> null); - } - - // Restart the main dialog with a different message the second time around - String promptMessage = "What else can I do for you?"; - return stepResult - .thenCompose(result -> stepContext.replaceDialog(getInitialDialogId(), promptMessage)); - } -} diff --git a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java b/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java deleted file mode 100644 index 037f6f74c..000000000 --- a/samples/21.corebot-app-insights/src/main/java/com/microsoft/bot/sample/corebot/app/insights/package-info.java +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. See License.txt in the project root for -// license information. - -/** - * This package contains the classes for the corebot-app-insights sample. - */ -package com.microsoft.bot.sample.corebot.app.insights; diff --git a/samples/21.corebot-app-insights/src/main/resources/application.properties b/samples/21.corebot-app-insights/src/main/resources/application.properties deleted file mode 100644 index cd458c966..000000000 --- a/samples/21.corebot-app-insights/src/main/resources/application.properties +++ /dev/null @@ -1,8 +0,0 @@ -MicrosoftAppId= -MicrosoftAppPassword= -LuisAppId= -LuisAPIKey= -LuisAPIHostName= -# Specifies my instrumentation key for Application Insights. -ApplicationInsights.InstrumentationKey= -server.port=3978 diff --git a/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json b/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json deleted file mode 100644 index 9b6389e39..000000000 --- a/samples/21.corebot-app-insights/src/main/resources/cards/welcomeCard.json +++ /dev/null @@ -1,46 +0,0 @@ -{ - "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", - "type": "AdaptiveCard", - "version": "1.0", - "body": [ - { - "type": "Image", - "url": "https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQtB3AwMUeNoq4gUBGe6Ocj8kyh3bXa9ZbV7u1fVKQoyKFHdkqU", - "size": "stretch" - }, - { - "type": "TextBlock", - "spacing": "medium", - "size": "default", - "weight": "bolder", - "text": "Welcome to Bot Framework!", - "wrap": true, - "maxLines": 0 - }, - { - "type": "TextBlock", - "size": "default", - "isSubtle": true, - "text": "Now that you have successfully run your bot, follow the links in this Adaptive Card to expand your knowledge of Bot Framework.", - "wrap": true, - "maxLines": 0 - } - ], - "actions": [ - { - "type": "Action.OpenUrl", - "title": "Get an overview", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/?view=azure-bot-service-4.0" - }, - { - "type": "Action.OpenUrl", - "title": "Ask a question", - "url": "https://stackoverflow.com/questions/tagged/botframework" - }, - { - "type": "Action.OpenUrl", - "title": "Learn how to deploy", - "url": "https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-howto-deploy-azure?view=azure-bot-service-4.0" - } - ] -} diff --git a/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF b/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/21.corebot-app-insights/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml b/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/21.corebot-app-insights/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/21.corebot-app-insights/src/main/webapp/index.html b/samples/21.corebot-app-insights/src/main/webapp/index.html deleted file mode 100644 index 730d2c453..000000000 --- a/samples/21.corebot-app-insights/src/main/webapp/index.html +++ /dev/null @@ -1,417 +0,0 @@ - - - - - - - Core Bot Sample - - - - - -
-
-
-
Core Bot Sample
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - diff --git a/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java b/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java deleted file mode 100644 index de794a90c..000000000 --- a/samples/21.corebot-app-insights/src/test/java/com/microsoft/bot/sample/corebot/app/insights/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.corebot.app.insights; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/23.facebook-events/LICENSE b/samples/23.facebook-events/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/23.facebook-events/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/23.facebook-events/README.md b/samples/23.facebook-events/README.md deleted file mode 100644 index f839c4916..000000000 --- a/samples/23.facebook-events/README.md +++ /dev/null @@ -1,91 +0,0 @@ -# Facebook Events - -Bot Framework v4 facebook events bot sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to integrate and consume Facebook specific payloads, such as postbacks, quick replies and opt-in events. Since Bot Framework supports multiple Facebook pages for a single bot, we also show how to know the page to which the message was sent, so developers can have custom behavior per page. - -More information about configuring a bot for Facebook Messenger can be found here: [Connect a bot to Facebook](https://docs.microsoft.com/en-us/azure/bot-service/bot-service-channel-connect-facebook) - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\facebook-events-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "facebookBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="facebookBotPlan" newWebAppName="facebookBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="facebookBot" newAppServicePlanName="facebookBotPlan" appServicePlanLocation="westus" --name "facebookBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Facebook Quick Replies](https://developers.facebook.com/docs/messenger-platform/send-messages/quick-replies/0) -- [Facebook PostBack](https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_postbacks/) -- [Facebook Opt-in](https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_optins/) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [.NET Core CLI tools](https://docs.microsoft.com/en-us/dotnet/core/tools/?tabs=netcore2x) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) diff --git a/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json b/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/23.facebook-events/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json b/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/23.facebook-events/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/23.facebook-events/pom.xml b/samples/23.facebook-events/pom.xml deleted file mode 100644 index f37d7eb75..000000000 --- a/samples/23.facebook-events/pom.xml +++ /dev/null @@ -1,243 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - facebook-events - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains a Java Facebook Events sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.facebookevents.Application - - - - - junit - junit - 4.13.1 - test - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.facebookevents.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java deleted file mode 100644 index a0dc8c667..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/Application.java +++ /dev/null @@ -1,67 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.facebookevents; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import com.microsoft.bot.sample.facebookevents.bot.FacebookBot; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot() { - return new FacebookBot(); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java deleted file mode 100644 index b03e541e4..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/bot/FacebookBot.java +++ /dev/null @@ -1,253 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.facebookevents.bot; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.connector.Channels; -import com.microsoft.bot.dialogs.choices.Choice; -import com.microsoft.bot.dialogs.choices.ChoiceFactory; -import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookMessage; -import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookOptin; -import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookPayload; -import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookPostback; -import com.microsoft.bot.sample.facebookevents.facebookmodel.FacebookQuickReply; -import com.microsoft.bot.schema.ActionTypes; -import com.microsoft.bot.schema.Activity; -import com.microsoft.bot.schema.CardAction; -import com.microsoft.bot.schema.HeroCard; -import com.microsoft.bot.schema.Serialization; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -/** - * This class implements the functionality of the Bot. - */ -public class FacebookBot extends ActivityHandler { - - // These are the options provided to the user when they message the bot - final String FacebookPageIdOption = "Facebook Id"; - final String QuickRepliesOption = "Quick Replies"; - final String PostBackOption = "PostBack"; - - protected final Logger Logger; - - public FacebookBot() { - Logger = LoggerFactory.getLogger(FacebookBot.class); - } - - @Override - protected CompletableFuture onMessageActivity(TurnContext turnContext) { - Logger.info("Processing a Message Activity."); - - // Show choices if the Facebook Payload from ChannelData instanceof not handled - try { - return processFacebookPayload(turnContext, turnContext.getActivity().getChannelData()) - .thenCompose(result -> { - if (result == false) { - return showChoices(turnContext); - } else { - return CompletableFuture.completedFuture(null); - } - }); - } catch (JsonProcessingException e) { - e.printStackTrace(); - return CompletableFuture.completedFuture(null); - } - } - - @Override - protected CompletableFuture onEventActivity(TurnContext turnContext) { - Logger.info("Processing an Event Activity."); - - // Analyze Facebook payload from EventActivity.Value - try { - return processFacebookPayload(turnContext, turnContext.getActivity().getValue()).thenApply(result -> null); - } catch (JsonProcessingException e) { - e.printStackTrace(); - return CompletableFuture.completedFuture(null); - } - } - - private CompletableFuture showChoices(TurnContext turnContext) { - // Create choices for the prompt - List choices = new ArrayList(); - - Choice firstChoice = new Choice(); - firstChoice.setValue(QuickRepliesOption); - CardAction firstChoiceCardAction = new CardAction(); - firstChoiceCardAction.setTitle(QuickRepliesOption); - firstChoiceCardAction.setType(ActionTypes.POST_BACK); - firstChoiceCardAction.setValue(QuickRepliesOption); - firstChoice.setAction(firstChoiceCardAction); - choices.add(firstChoice); - - Choice secondChoice = new Choice(); - firstChoice.setValue(FacebookPageIdOption); - CardAction secondChoiceCardAction = new CardAction(); - secondChoiceCardAction.setTitle(FacebookPageIdOption); - secondChoiceCardAction.setType(ActionTypes.POST_BACK); - secondChoiceCardAction.setValue(FacebookPageIdOption); - secondChoice.setAction(secondChoiceCardAction); - choices.add(secondChoice); - - Choice thirdChoice = new Choice(); - firstChoice.setValue(PostBackOption); - CardAction thirdChoiceCardAction = new CardAction(); - thirdChoiceCardAction.setTitle(PostBackOption); - thirdChoiceCardAction.setType(ActionTypes.POST_BACK); - thirdChoiceCardAction.setValue(PostBackOption); - thirdChoice.setAction(thirdChoiceCardAction); - - choices.add(thirdChoice); - - // Create the prompt message - Activity message = ChoiceFactory.forChannel( - turnContext.getActivity().getChannelId(), - choices, - "What Facebook feature would you like to try? Here are some quick replies to choose from!" - ); - return turnContext.sendActivity(message).thenApply(result -> null); - } - - - private CompletableFuture processFacebookPayload( - TurnContext turnContext, - Object data - ) throws JsonProcessingException { - // At this point we know we are on Facebook channel, and can consume the - // Facebook custom payload - // present in channelData. - try { - FacebookPayload facebookPayload = Serialization.safeGetAs(data, FacebookPayload.class); - - if (facebookPayload != null) { - // PostBack - if (facebookPayload.getPostBack() != null) { - return onFacebookPostBack(turnContext, facebookPayload.getPostBack()).thenApply(result -> true); - } else if (facebookPayload.getOptin() != null) { - // Optin - return onFacebookOptin(turnContext, facebookPayload.getOptin()).thenApply(result -> true); - } else if ( - facebookPayload.getMessage() != null && facebookPayload.getMessage().getQuickReply() != null - ) { - // Quick reply - return onFacebookQuickReply(turnContext, facebookPayload.getMessage().getQuickReply()) - .thenApply(result -> true); - } else if (facebookPayload.getMessage() != null && facebookPayload.getMessage().getIsEcho()) { - // Echo - return onFacebookEcho(turnContext, facebookPayload.getMessage()).thenApply(result -> true); - } else { - return turnContext.sendActivity("This sample is intended to be used with a Facebook bot.") - .thenApply(result -> false); - } - - // TODO: Handle other events that you're interested in... - } - } catch (JsonProcessingException ex) { - if (turnContext.getActivity().getChannelId() != Channels.FACEBOOK) { - turnContext.sendActivity("This sample is intended to be used with a Facebook bot."); - } else { - throw ex; - } - } - - return CompletableFuture.completedFuture(false); - } - - protected CompletableFuture onFacebookOptin(TurnContext turnContext, FacebookOptin optin) { - Logger.info("Optin message received."); - - // TODO: Your optin event handling logic here... - return CompletableFuture.completedFuture(null); - } - - protected CompletableFuture onFacebookEcho(TurnContext turnContext, FacebookMessage facebookMessage) { - Logger.info("Echo message received."); - - // TODO: Your echo event handling logic here... - return CompletableFuture.completedFuture(null); - } - - protected CompletableFuture onFacebookPostBack(TurnContext turnContext, FacebookPostback postBack) { - Logger.info("PostBack message received."); - - // TODO: Your PostBack handling logic here... - - // Answer the postback, and show choices - Activity reply = MessageFactory.text("Are you sure?"); - return turnContext.sendActivity(reply).thenCompose(result -> { - return showChoices(turnContext); - }); - } - - protected CompletableFuture onFacebookQuickReply(TurnContext turnContext, FacebookQuickReply quickReply) { - Logger.info("QuickReply message received."); - - // TODO: Your quick reply event handling logic here... - - // Process the message by checking the Activity.getText(). The - // FacebookQuickReply could also contain a json payload. - - // Initially the bot offers to showcase 3 Facebook features: Quick replies, - // PostBack and getting the Facebook Page Name. - switch (turnContext.getActivity().getText()) { - // Here we showcase how to obtain the Facebook page id. - // This can be useful for the Facebook multi-page support provided by the Bot - // Framework. - // The Facebook page id from which the message comes from instanceof in - // turnContext.getActivity().getRecipient().getId(). - case FacebookPageIdOption: { - Activity reply = MessageFactory.text( - String.format( - "This message comes from the following Facebook Page: %s", - turnContext.getActivity().getRecipient().getId() - ) - ); - return turnContext.sendActivity(reply).thenCompose(result -> { - return showChoices(turnContext); - }); - } - - // Here we send a HeroCard with 2 options that will trigger a Facebook PostBack. - case PostBackOption: { - HeroCard card = new HeroCard(); - card.setText("Is 42 the answer to the ultimate question of Life, the Universe, and Everything?"); - - List buttons = new ArrayList(); - - CardAction yesAction = new CardAction(); - yesAction.setTitle("Yes"); - yesAction.setType(ActionTypes.POST_BACK); - yesAction.setValue("Yes"); - buttons.add(yesAction); - - CardAction noAction = new CardAction(); - noAction.setTitle("No"); - noAction.setType(ActionTypes.POST_BACK); - noAction.setValue("No"); - buttons.add(noAction); - - card.setButtons(buttons); - - Activity reply = MessageFactory.attachment(card.toAttachment()); - return turnContext.sendActivity(reply).thenApply(result -> null); - } - - // By default we offer the users different actions that the bot supports, - // through quick replies. - case QuickRepliesOption: - default: { - return showChoices(turnContext); - } - } - } -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java deleted file mode 100644 index b22d285ab..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookMessage.java +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -public class FacebookMessage { - - @JsonProperty(value = "mid") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String messageId; - - @JsonProperty(value = "text") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String text; - - @JsonProperty(value = "is_echo") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private boolean isEcho; - - @JsonProperty(value = "quick_reply") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookQuickReply quickReply; - - /** - * Gets the message Id from Facebook. - * @return the MessageId value as a String. - */ - public String getMessageId() { - return this.messageId; - } - - /** - * Sets the message Id from Facebook. - * @param withMessageId The MessageId value. - */ - public void setMessageId(String withMessageId) { - this.messageId = withMessageId; - } - - /** - * Gets the message text. - * @return the Text value as a String. - */ - public String getText() { - return this.text; - } - - /** - * Sets the message text. - * @param withText The Text value. - */ - public void setText(String withText) { - this.text = withText; - } - - /** - * Gets whether the message is an echo message. See - * {@link - * https://developers#facebook#com/docs/messenger-platform/reference/webhook-events/message-echoes/} Echo Message - * @return the IsEcho value as a boolean. - */ - public boolean getIsEcho() { - return this.isEcho; - } - - /** - * Sets whether the message is an echo message. See - * {@link - * https://developers#facebook#com/docs/messenger-platform/reference/webhook-events/message-echoes/} Echo Message - * @param withIsEcho The IsEcho value. - */ - public void setIsEcho(boolean withIsEcho) { - this.isEcho = withIsEcho; - } - - /** - * Gets the quick reply. - * @return the QuickReply value as a FacebookQuickReply. - */ - public FacebookQuickReply getQuickReply() { - return this.quickReply; - } - - /** - * Sets the quick reply. - * @param withQuickReply The QuickReply value. - */ - public void setQuickReply(FacebookQuickReply withQuickReply) { - this.quickReply = withQuickReply; - } - -} - diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java deleted file mode 100644 index 3831985cc..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookOptin.java +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * A Facebook optin event payload definition. - * - * See https://developers.facebook.com/docs/messenger-platform/reference/webhook-events/messaging_optins/ - * for more information on messaging_optin. - */ -public class FacebookOptin { - - @JsonProperty(value = "ref") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String ref; - - @JsonProperty(value = "user_ref") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String userRef; - - /** - * Gets the optin data ref. - * @return the Ref value as a String. - */ - public String getRef() { - return this.ref; - } - - /** - * Sets the optin data ref. - * @param withRef The Ref value. - */ - public void setRef(String withRef) { - this.ref = withRef; - } - - /** - * Gets the optin user ref. - * @return the UserRef value as a String. - */ - public String getUserRef() { - return this.userRef; - } - - /** - * Sets the optin user ref. - * @param withUserRef The UserRef value. - */ - public void setUserRef(String withUserRef) { - this.userRef = withUserRef; - } - -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java deleted file mode 100644 index 060511b5a..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPayload.java +++ /dev/null @@ -1,114 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Simple version of the payload received from the Facebook channel. - */ -public class FacebookPayload { - - @JsonProperty(value = "sender") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookSender sender; - - @JsonProperty(value = "recipient") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookRecipient recipient; - - @JsonProperty(value = "message") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookMessage message; - - @JsonProperty(value = "postback") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookPostback postBack; - - @JsonProperty(value = "optin") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private FacebookOptin optin; - - /** - * Gets the sender of the message. - * @return the Sender value as a FacebookSender. - */ - public FacebookSender getSender() { - return this.sender; - } - - /** - * Sets the sender of the message. - * @param withSender The Sender value. - */ - public void setSender(FacebookSender withSender) { - this.sender = withSender; - } - - /** - * Gets the recipient of the message. - * @return the Recipient value as a FacebookRecipient. - */ - public FacebookRecipient getRecipient() { - return this.recipient; - } - - /** - * Sets the recipient of the message. - * @param withRecipient The Recipient value. - */ - public void setRecipient(FacebookRecipient withRecipient) { - this.recipient = withRecipient; - } - - /** - * Gets the message. - * @return the Message value as a FacebookMessage. - */ - public FacebookMessage getMessage() { - return this.message; - } - - /** - * Sets the message. - * @param withMessage The Message value. - */ - public void setMessage(FacebookMessage withMessage) { - this.message = withMessage; - } - - /** - * Gets the postback payload if available. - * @return the PostBack value as a FacebookPostback. - */ - public FacebookPostback getPostBack() { - return this.postBack; - } - - /** - * Sets the postback payload if available. - * @param withPostBack The PostBack value. - */ - public void setPostBack(FacebookPostback withPostBack) { - this.postBack = withPostBack; - } - - /** - * Gets the optin payload if available. - * @return the Optin value as a FacebookOptin. - */ - public FacebookOptin getOptin() { - return this.optin; - } - - /** - * Sets the optin payload if available. - * @param withOptin The Optin value. - */ - public void setOptin(FacebookOptin withOptin) { - this.optin = withOptin; - } - -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java deleted file mode 100644 index 0dacaa5f2..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookPostback.java +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Definition for Facebook PostBack payload. Present on calls - * frommessaging_postback webhook event. - * - * See - * {@link - * https://developers#getfacebook()#com/docs/messenger-platform/reference/webhook-events/messaging_postbacks/} - * Facebook messaging_postback - */ -public class FacebookPostback { - - @JsonProperty(value = "payload") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String payload; - - @JsonProperty(value = "title") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String title; - - /** - * Gets payload of the PostBack. Could be an Object depending on - * the Object sent. - * @return the Payload value as a String. - */ - public String getPayload() { - return this.payload; - } - - /** - * Sets payload of the PostBack. Could be an Object depending on - * the Object sent. - * @param withPayload The Payload value. - */ - public void setPayload(String withPayload) { - this.payload = withPayload; - } - - /** - * Gets the title of the postback. - * @return the Title value as a String. - */ - public String getTitle() { - return this.title; - } - - /** - * Sets the title of the postback. - * @param withTitle The Title value. - */ - public void setTitle(String withTitle) { - this.title = withTitle; - } -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java deleted file mode 100644 index 433524d93..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookQuickReply.java +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * A Facebook quick reply. - * - * See - * {@link - * https://developers#getfacebook()#com/docs/messenger-platform/send-messages/quick-replies/} - * Quick Replies Facebook Documentation - */ -public class FacebookQuickReply { - - @JsonProperty(value = "payload") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String payload; - - /** - * @return the Payload value as a String. - */ - public String getPayload() { - return this.payload; - } - - /** - * @param withPayload The Payload value. - */ - public void setPayload(String withPayload) { - this.payload = withPayload; - } -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java deleted file mode 100644 index 34d0527a6..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookRecipient.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Defines a Facebook recipient. - */ -public class FacebookRecipient { - - @JsonProperty(value = "id") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String id; - - /** - * The Facebook Id of the recipient. - * @return the Id value as a String. - */ - public String getId() { - return this.id; - } - - /** - * The Facebook Id of the recipient. - * @param withId The Id value. - */ - public void setId(String withId) { - this.id = withId; - } -} diff --git a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java b/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java deleted file mode 100644 index da96fdf4f..000000000 --- a/samples/23.facebook-events/src/main/java/com/microsoft/bot/sample/facebookevents/facebookmodel/FacebookSender.java +++ /dev/null @@ -1,33 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MT License. - -package com.microsoft.bot.sample.facebookevents.facebookmodel; - -import com.fasterxml.jackson.annotation.JsonInclude; -import com.fasterxml.jackson.annotation.JsonProperty; - -/** - * Defines a Facebook sender. - */ -public class FacebookSender { - - @JsonProperty(value = "id") - @JsonInclude(JsonInclude.Include.NON_EMPTY) - private String id; - - /** - * The Facebook Id of the sender. - * @return the Id value as a String. - */ - public String getId() { - return this.id; - } - - /** - * The Facebook Id of the sender. - * @param withId The Id value. - */ - public void setId(String withId) { - this.id = withId; - } -} diff --git a/samples/23.facebook-events/src/main/resources/application.properties b/samples/23.facebook-events/src/main/resources/application.properties deleted file mode 100644 index c5c87d7f4..000000000 --- a/samples/23.facebook-events/src/main/resources/application.properties +++ /dev/null @@ -1,3 +0,0 @@ -MicrosoftAppId=7455f666-b11d-4ce8-8c81-36370eaae99a -MicrosoftAppPassword=1G_Rv.yT8eDjg.2nd6S6j0__HGxN5T.epz -server.port=3978 diff --git a/samples/23.facebook-events/src/main/resources/log4j2.json b/samples/23.facebook-events/src/main/resources/log4j2.json deleted file mode 100644 index 67c0ad530..000000000 --- a/samples/23.facebook-events/src/main/resources/log4j2.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "configuration": { - "name": "Default", - "appenders": { - "Console": { - "name": "Console-Appender", - "target": "SYSTEM_OUT", - "PatternLayout": {"pattern": "[%-5level] %d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %c{1} - %msg%n"} - } - }, - "loggers": { - "root": { - "level": "debug", - "appender-ref": {"ref": "Console-Appender","level": "debug"} - } - } - } -} diff --git a/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF b/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF deleted file mode 100644 index 254272e1c..000000000 --- a/samples/23.facebook-events/src/main/webapp/META-INF/MANIFEST.MF +++ /dev/null @@ -1,3 +0,0 @@ -Manifest-Version: 1.0 -Class-Path: - diff --git a/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml b/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml deleted file mode 100644 index 383c19004..000000000 --- a/samples/23.facebook-events/src/main/webapp/WEB-INF/web.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - dispatcher - - org.springframework.web.servlet.DispatcherServlet - - - contextConfigLocation - /WEB-INF/spring/dispatcher-config.xml - - 1 - \ No newline at end of file diff --git a/samples/23.facebook-events/src/main/webapp/index.html b/samples/23.facebook-events/src/main/webapp/index.html deleted file mode 100644 index d5ba5158e..000000000 --- a/samples/23.facebook-events/src/main/webapp/index.html +++ /dev/null @@ -1,418 +0,0 @@ - - - - - - - EchoBot - - - - - -
-
-
-
Spring Boot Bot
-
-
-
-
-
Your bot is ready!
-
You can test your bot in the Bot Framework Emulator
- by connecting to http://localhost:3978/api/messages.
- -
Visit Azure - Bot Service to register your bot and add it to
- various channels. The bot's endpoint URL typically looks - like this:
-
https://your_bots_hostname/api/messages
-
-
-
-
- -
- - - diff --git a/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java b/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java deleted file mode 100644 index 2113bce51..000000000 --- a/samples/23.facebook-events/src/test/java/com/microsoft/bot/sample/facebookevents/ApplicationTest.java +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.facebookevents; - -import org.junit.Test; -import org.junit.runner.RunWith; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.test.context.junit4.SpringRunner; - -@RunWith(SpringRunner.class) -@SpringBootTest -public class ApplicationTest { - - @Test - public void contextLoads() { - } - -} diff --git a/samples/24.bot-authentication-msgraph/LICENSE b/samples/24.bot-authentication-msgraph/LICENSE deleted file mode 100644 index 21071075c..000000000 --- a/samples/24.bot-authentication-msgraph/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ - MIT License - - Copyright (c) Microsoft Corporation. All rights reserved. - - 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 diff --git a/samples/24.bot-authentication-msgraph/README.md b/samples/24.bot-authentication-msgraph/README.md deleted file mode 100644 index cdcfaa5ba..000000000 --- a/samples/24.bot-authentication-msgraph/README.md +++ /dev/null @@ -1,118 +0,0 @@ -# Authentication Bot Utilizing MS Graph - -Bot Framework v4 bot authentication using Microsoft Graph sample - -This bot has been created using [Bot Framework](https://dev.botframework.com), is shows how to use the bot authentication capabilities of Azure Bot Service. In this sample we are assuming the OAuth 2 provider is Azure Active Directory v2 (AADv2) and are utilizing the Microsoft Graph API to retrieve data about the user. [Check here](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-authentication?view=azure-bot-service-4.0&tabs=csharp) for information about getting an AADv2 -application setup for use in Azure Bot Service. The [scopes](https://developer.microsoft.com/en-us/graph/docs/concepts/permissions_reference) used in this sample are the following: - -- `openid` -- `profile` -- `User.Read` - -NOTE: Microsoft Teams currently differs slightly in the way auth is integrated with the bot. Refer to sample 46.teams-auth. - -## Prerequisites - -- Java 1.8+ -- Install [Maven](https://maven.apache.org/) -- An account on [Azure](https://azure.microsoft.com) if you want to deploy to Azure. - -## To try this sample locally -- From the root of this project folder: - - Build the sample using `mvn package` - - Run it by using `java -jar .\target\bot-authentication-msgraph-sample.jar` - -- Test the bot using Bot Framework Emulator - - [Bot Framework Emulator](https://github.com/microsoft/botframework-emulator) is a desktop application that allows bot developers to test and debug their bots on localhost or running remotely through a tunnel. - - - Install the Bot Framework Emulator version 4.3.0 or greater from [here](https://github.com/Microsoft/BotFramework-Emulator/releases) - - - Connect to the bot using Bot Framework Emulator - - - Launch Bot Framework Emulator - - File -> Open Bot - - Enter a Bot URL of `http://localhost:3978/api/messages` - -## Deploy the bot to Azure - -As described on [Deploy your bot](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-deploy-az-cli), you will perform the first 4 steps to setup the Azure app, then deploy the code using the azure-webapp Maven plugin. - -### 1. Login to Azure -From a command (or PowerShell) prompt in the root of the bot folder, execute: -`az login` - -### 2. Set the subscription -`az account set --subscription ""` - -If you aren't sure which subscription to use for deploying the bot, you can view the list of subscriptions for your account by using `az account list` command. - -### 3. Create an App registration -`az ad app create --display-name "" --password "" --available-to-other-tenants` - -Replace `` and `` with your own values. - -`` is the unique name of your bot. -`` is a minimum 16 character password for your bot. - -Record the `appid` from the returned JSON - -### 4. Create the Azure resources -Replace the values for ``, ``, ``, and `` in the following commands: - -#### To a new Resource Group -`az deployment sub create --name "authenticationBotDeploy" --location "westus" --template-file ".\deploymentTemplates\template-with-new-rg.json" --parameters appId="" appSecret="" botId="" botSku=S1 newAppServicePlanName="authenticationGraphBotPlan" newWebAppName="authenticationGraphBot" groupLocation="westus" newAppServicePlanLocation="westus"` - -#### To an existing Resource Group -`az deployment group create --resource-group "" --template-file ".\deploymentTemplates\template-with-preexisting-rg.json" --parameters appId="" appSecret="" botId="" newWebAppName="authenticationGraphBot" newAppServicePlanName="authenticationGraphBotPlan" appServicePlanLocation="westus" --name "authenticationGraphBot"` - -### 5. Update app id and password -In src/main/resources/application.properties update - - `MicrosoftAppPassword` with the botsecret value - - `MicrosoftAppId` with the appid from the first step - -### 6. Deploy the code -- Execute `mvn clean package` -- Execute `mvn azure-webapp:deploy -Dgroupname="" -Dbotname=""` - -If the deployment is successful, you will be able to test it via "Test in Web Chat" from the Azure Portal using the "Bot Channel Registration" for the bot. - -After the bot is deployed, you only need to execute #6 if you make changes to the bot. - -## Interacting with the bot - -This sample uses the bot authentication capabilities of Azure Bot Service, providing features to make it easier to develop a bot that -authenticates users to various identity providers such as Azure AD (Azure Active Directory), GitHub, Uber, and so on. These updates also -take steps towards an improved user experience by eliminating the magic code verification for some clients and channels. -It is important to note that the user's token does not need to be stored in the bot. When the bot needs to use or verify the user has a valid token at any point the OAuth prompt may be sent. If the token is not valid they will be prompted to login. - -## Microsoft Graph API - -This sample demonstrates using Azure Active Directory v2 as the OAuth2 provider and utilizes the Microsoft Graph API. -Microsoft Graph is a Microsoft developer platform that connects multiple services and devices. Initially released in 2015, -the Microsoft Graph builds on Office 365 APIs and allows developers to integrate their services with Microsoft products including Windows, Office 365, and Azure. - -## GraphError 404: ResourceNotFound, Resource could not be discovered - -This error may confusingly present itself if either of the following are true: - -- You're using an email ending in `@microsoft.com`, and/or -- Your OAuth AAD tenant is `microsoft.onmicrosoft.com`. - -## Further reading - -- [Bot Framework Documentation](https://docs.botframework.com) -- [Bot Basics](https://docs.microsoft.com/azure/bot-service/bot-builder-basics?view=azure-bot-service-4.0) -- [Dialogs](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-dialog?view=azure-bot-service-4.0) -- [Gathering Input Using Prompts](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-prompts?view=azure-bot-service-4.0&tabs=csharp) -- [Activity processing](https://docs.microsoft.com/en-us/azure/bot-service/bot-builder-concept-activity-processing?view=azure-bot-service-4.0) -- [Microsoft Graph API](https://developer.microsoft.com/en-us/graph) -- [MS Graph Docs](https://developer.microsoft.com/en-us/graph/docs/concepts/overview) and [SDK](https://github.com/microsoftgraph/msgraph-sdk-dotnet) -- [Azure Bot Service Introduction](https://docs.microsoft.com/azure/bot-service/bot-service-overview-introduction?view=azure-bot-service-4.0) -- [Azure Bot Service Documentation](https://docs.microsoft.com/azure/bot-service/?view=azure-bot-service-4.0) -- [Azure CLI](https://docs.microsoft.com/cli/azure/?view=azure-cli-latest) -- [Azure Portal](https://portal.azure.com) -- [Channels and Bot Connector Service](https://docs.microsoft.com/en-us/azure/bot-service/bot-concepts?view=azure-bot-service-4.0) -- [Maven Plugin for Azure App Service](https://docs.microsoft.com/en-us/java/api/overview/azure/maven/azure-webapp-maven-plugin/readme?view=azure-java-stable) -- [Spring Boot](https://spring.io/projects/spring-boot) -- [Azure for Java cloud developers](https://docs.microsoft.com/en-us/azure/java/?view=azure-java-stable) diff --git a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json deleted file mode 100644 index ec2460d3a..000000000 --- a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-new-rg.json +++ /dev/null @@ -1,291 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "groupLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "Specifies the location of the Resource Group." - } - }, - "groupName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the Resource Group." - } - }, - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "newAppServicePlanLocation": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The location of the App Service Plan. Defaults to \"westus\"." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "appServicePlanName": "[parameters('newAppServicePlanName')]", - "resourcesLocation": "[parameters('newAppServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourceGroupId": "[concat(subscription().id, '/resourceGroups/', parameters('groupName'))]" - }, - "resources": [ - { - "name": "[parameters('groupName')]", - "type": "Microsoft.Resources/resourceGroups", - "apiVersion": "2018-05-01", - "location": "[parameters('groupLocation')]", - "properties": {} - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2018-05-01", - "name": "storageDeployment", - "resourceGroup": "[parameters('groupName')]", - "dependsOn": [ - "[resourceId('Microsoft.Resources/resourceGroups/', parameters('groupName'))]" - ], - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "name": "[variables('appServicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/serverfarms/', variables('appServicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[variables('appServicePlanName')]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[concat(variables('resourceGroupId'), '/providers/Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ], - "outputs": {} - } - } - } - ] -} diff --git a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json b/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json deleted file mode 100644 index 024dcf08d..000000000 --- a/samples/24.bot-authentication-msgraph/deploymentTemplates/template-with-preexisting-rg.json +++ /dev/null @@ -1,259 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "appId": { - "type": "string", - "metadata": { - "description": "Active Directory App ID, set as MicrosoftAppId in the Web App's Application Settings." - } - }, - "appSecret": { - "type": "string", - "metadata": { - "description": "Active Directory App Password, set as MicrosoftAppPassword in the Web App's Application Settings. Defaults to \"\"." - } - }, - "botId": { - "type": "string", - "metadata": { - "description": "The globally unique and immutable bot ID. Also used to configure the displayName of the bot, which is mutable." - } - }, - "botSku": { - "defaultValue": "S1", - "type": "string", - "metadata": { - "description": "The pricing tier of the Bot Service Registration. Acceptable values are F0 and S1." - } - }, - "newAppServicePlanName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of the new App Service Plan." - } - }, - "newAppServicePlanSku": { - "type": "object", - "defaultValue": { - "name": "P1v2", - "tier": "PremiumV2", - "size": "P1v2", - "family": "Pv2", - "capacity": 1 - }, - "metadata": { - "description": "The SKU of the App Service Plan. Defaults to Standard values." - } - }, - "appServicePlanLocation": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The location of the App Service Plan." - } - }, - "existingAppServicePlan": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Name of the existing App Service Plan used to create the Web App for the bot." - } - }, - "newWebAppName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The globally unique name of the Web App. Defaults to the value passed in for \"botId\"." - } - } - }, - "variables": { - "defaultAppServicePlanName": "[if(empty(parameters('existingAppServicePlan')), 'createNewAppServicePlan', parameters('existingAppServicePlan'))]", - "useExistingAppServicePlan": "[not(equals(variables('defaultAppServicePlanName'), 'createNewAppServicePlan'))]", - "servicePlanName": "[if(variables('useExistingAppServicePlan'), parameters('existingAppServicePlan'), parameters('newAppServicePlanName'))]", - "publishingUsername": "[concat('$', parameters('newWebAppName'))]", - "resourcesLocation": "[parameters('appServicePlanLocation')]", - "webAppName": "[if(empty(parameters('newWebAppName')), parameters('botId'), parameters('newWebAppName'))]", - "siteHost": "[concat(variables('webAppName'), '.azurewebsites.net')]", - "botEndpoint": "[concat('https://', variables('siteHost'), '/api/messages')]" - }, - "resources": [ - { - "comments": "Create a new Linux App Service Plan if no existing App Service Plan name was passed in.", - "type": "Microsoft.Web/serverfarms", - "condition": "[not(variables('useExistingAppServicePlan'))]", - "name": "[variables('servicePlanName')]", - "apiVersion": "2018-02-01", - "location": "[variables('resourcesLocation')]", - "sku": "[parameters('newAppServicePlanSku')]", - "kind": "linux", - "properties": { - "perSiteScaling": false, - "maximumElasticWorkerCount": 1, - "isSpot": false, - "reserved": true, - "isXenon": false, - "hyperV": false, - "targetWorkerCount": 0, - "targetWorkerSizeId": 0 - } - }, - { - "comments": "Create a Web App using a Linux App Service Plan", - "type": "Microsoft.Web/sites", - "apiVersion": "2018-11-01", - "location": "[variables('resourcesLocation')]", - "kind": "app,linux", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]" - ], - "name": "[variables('webAppName')]", - "properties": { - "name": "[variables('webAppName')]", - "hostNameSslStates": [ - { - "name": "[concat(parameters('newWebAppName'), '.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Standard" - }, - { - "name": "[concat(parameters('newWebAppName'), '.scm.azurewebsites.net')]", - "sslState": "Disabled", - "hostType": "Repository" - } - ], - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('servicePlanName'))]", - "reserved": true, - "isXenon": false, - "hyperV": false, - "scmSiteAlsoStopped": false, - "clientAffinityEnabled": true, - "clientCertEnabled": false, - "hostNamesDisabled": false, - "containerSize": 0, - "dailyMemoryTimeQuota": 0, - "httpsOnly": false, - "redundancyMode": "None", - "siteConfig": { - "appSettings": [ - { - "name": "JAVA_OPTS", - "value": "-Dserver.port=80" - }, - { - "name": "MicrosoftAppId", - "value": "[parameters('appId')]" - }, - { - "name": "MicrosoftAppPassword", - "value": "[parameters('appSecret')]" - } - ], - "cors": { - "allowedOrigins": [ - "https://botservice.hosting.portal.azure.net", - "https://hosting.onecloud.azure-test.net/" - ] - } - } - } - }, - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('webAppName'), '/web')]", - "location": "[variables('resourcesLocation')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ], - "properties": { - "numberOfWorkers": 1, - "defaultDocuments": [ - "Default.htm", - "Default.html", - "Default.asp", - "index.htm", - "index.html", - "iisstart.htm", - "default.aspx", - "index.php", - "hostingstart.html" - ], - "netFrameworkVersion": "v4.0", - "linuxFxVersion": "JAVA|8-jre8", - "requestTracingEnabled": false, - "remoteDebuggingEnabled": false, - "httpLoggingEnabled": false, - "logsDirectorySizeLimit": 35, - "detailedErrorLoggingEnabled": false, - "publishingUsername": "[variables('publishingUsername')]", - "scmType": "None", - "use32BitWorkerProcess": true, - "webSocketsEnabled": false, - "alwaysOn": true, - "managedPipelineMode": "Integrated", - "virtualApplications": [ - { - "virtualPath": "/", - "physicalPath": "site\\wwwroot", - "preloadEnabled": true - } - ], - "loadBalancing": "LeastRequests", - "experiments": { - "rampUpRules": [] - }, - "autoHealEnabled": false, - "localMySqlEnabled": false, - "ipSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictions": [ - { - "ipAddress": "Any", - "action": "Allow", - "priority": 1, - "name": "Allow all", - "description": "Allow all access" - } - ], - "scmIpSecurityRestrictionsUseMain": false, - "http20Enabled": false, - "minTlsVersion": "1.2", - "ftpsState": "AllAllowed", - "reservedInstanceCount": 0 - } - }, - { - "apiVersion": "2017-12-01", - "type": "Microsoft.BotService/botServices", - "name": "[parameters('botId')]", - "location": "global", - "kind": "bot", - "sku": { - "name": "[parameters('botSku')]" - }, - "properties": { - "name": "[parameters('botId')]", - "displayName": "[parameters('botId')]", - "endpoint": "[variables('botEndpoint')]", - "msaAppId": "[parameters('appId')]", - "developerAppInsightsApplicationId": null, - "developerAppInsightKey": null, - "publishingCredentials": null, - "storageResourceId": null - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/', variables('webAppName'))]" - ] - } - ] -} diff --git a/samples/24.bot-authentication-msgraph/pom.xml b/samples/24.bot-authentication-msgraph/pom.xml deleted file mode 100644 index 2f4218c43..000000000 --- a/samples/24.bot-authentication-msgraph/pom.xml +++ /dev/null @@ -1,249 +0,0 @@ - - - - 4.0.0 - - com.microsoft.bot.sample - bot-authentication-msgraph - sample - jar - - ${project.groupId}:${project.artifactId} - This package contains the Bot Authentication using MSGraph sample using Spring Boot. - http://maven.apache.org - - - org.springframework.boot - spring-boot-starter-parent - 2.4.0 - - - - - - MIT License - http://www.opensource.org/licenses/mit-license.php - - - - - - Bot Framework Development - - Microsoft - https://dev.botframework.com/ - - - - - 1.8 - 1.8 - 1.8 - com.microsoft.bot.sample.authentication.Application - - - - - com.microsoft.graph - microsoft-graph - 2.6.0 - - - org.springframework.boot - spring-boot-starter-test - 2.4.0 - test - - - junit - junit - 4.13.1 - test - - - org.junit.vintage - junit-vintage-engine - test - - - - org.slf4j - slf4j-api - - - org.apache.logging.log4j - log4j-api - 2.11.0 - - - org.apache.logging.log4j - log4j-core - 2.13.2 - - - - com.microsoft.bot - bot-integration-spring - 4.13.0-SNAPSHOT - compile - - - com.microsoft.bot - bot-dialogs - 4.13.0-SNAPSHOT - compile - - - - - - build - - true - - - - - src/main/resources - false - - - - - maven-compiler-plugin - 3.8.1 - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - org.springframework.boot - spring-boot-maven-plugin - - - - repackage - - - com.microsoft.bot.sample.authentication.Application - - - - - - com.microsoft.azure - azure-webapp-maven-plugin - 1.12.0 - - V2 - ${groupname} - ${botname} - - - JAVA_OPTS - -Dserver.port=80 - - - - linux - Java 8 - Java SE - - - - - ${project.basedir}/target - - *.jar - - - - - - - - - - - - publish - - - - - org.apache.maven.plugins - maven-compiler-plugin - - - maven-war-plugin - 3.2.3 - - src/main/webapp - - - - - org.sonatype.plugins - nexus-staging-maven-plugin - 1.6.8 - true - - true - ossrh - https://oss.sonatype.org/ - true - - - - - org.apache.maven.plugins - maven-gpg-plugin - - - sign-artifacts - verify - - sign - - - - - - org.apache.maven.plugins - maven-source-plugin - - - attach-sources - - jar - - - - - - org.apache.maven.plugins - maven-javadoc-plugin - - 8 - false - - - - attach-javadocs - - jar - - - - - - - - - diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java deleted file mode 100644 index c4ebeaacf..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/Application.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import com.microsoft.bot.builder.Bot; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.integration.AdapterWithErrorHandler; -import com.microsoft.bot.integration.BotFrameworkHttpAdapter; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.integration.spring.BotController; -import com.microsoft.bot.integration.spring.BotDependencyConfiguration; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.context.annotation.Import; - -// -// This is the starting point of the Sprint Boot Bot application. -// -@SpringBootApplication - -// Use the default BotController to receive incoming Channel messages. A custom -// controller could be used by eliminating this import and creating a new -// org.springframework.web.bind.annotation.RestController. -// The default controller is created by the Spring Boot container using -// dependency injection. The default route is /api/messages. -@Import({BotController.class}) - -/** - * This class extends the BotDependencyConfiguration which provides the default - * implementations for a Bot application. The Application class should - * override methods in order to provide custom implementations. - */ -public class Application extends BotDependencyConfiguration { - public static void main(String[] args) { - SpringApplication.run(Application.class, args); - } - - /** - * Returns the Bot for this application. - * - *

- * The @Component annotation could be used on the Bot class instead of this method - * with the @Bean annotation. - *

- * - * @return The Bot implementation for this application. - */ - @Bean - public Bot getBot( - Configuration configuration, - ConversationState conversationState, - UserState userState, - MainDialog dialog - ) { - return new AuthBot(conversationState, userState, new MainDialog(configuration)); - } - - /** - * Returns a custom Adapter that provides error handling. - * - * @param configuration The Configuration object to use. - * @return An error handling BotFrameworkHttpAdapter. - */ - @Override - public BotFrameworkHttpAdapter getBotFrameworkHttpAdaptor(Configuration configuration) { - return new AdapterWithErrorHandler(configuration); - } -} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java deleted file mode 100644 index 5f189abc0..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/AuthBot.java +++ /dev/null @@ -1,49 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.concurrent.CompletableFuture; -import java.util.List; - -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.schema.ChannelAccount; - -import com.codepoetics.protonpack.collectors.CompletableFutures; -import com.microsoft.bot.schema.Activity; -import org.apache.commons.lang3.StringUtils; - -public class AuthBot extends DialogBot { - - public AuthBot(ConversationState conversationState, UserState userState, MainDialog dialog) { - super(conversationState, userState, dialog); - } - - @Override - protected CompletableFuture onMembersAdded( - List membersAdded, TurnContext turnContext - ) { - return turnContext.getActivity().getMembersAdded().stream() - .filter(member -> !StringUtils - .equals(member.getId(), turnContext.getActivity().getRecipient().getId())) - .map(channel -> { - Activity reply = MessageFactory.text("Welcome to AuthBot on MSGraph." - + " Type anything to get logged in. Type 'logout' to sign-out."); - - return turnContext.sendActivity(reply); - }) - .collect(CompletableFutures.toFutureList()) - .thenApply(resourceResponse -> null); - } - - @Override - protected CompletableFuture onTokenResponseEvent(TurnContext turnContext) { - // Run the Dialog with the new Token Response Event Activity. - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } - -} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java deleted file mode 100644 index 83db0b694..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/DialogBot.java +++ /dev/null @@ -1,54 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import com.microsoft.bot.builder.ActivityHandler; -import com.microsoft.bot.builder.BotState; -import com.microsoft.bot.builder.ConversationState; -import com.microsoft.bot.dialogs.Dialog; -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.builder.UserState; -import java.util.concurrent.CompletableFuture; - -/** - * This Bot implementation can run any type of Dialog. The use of type parameterization is to allows - * multiple different bots to be run at different endpoints within the same project. This can be - * achieved by defining distinct Controller types each with dependency on distinct IBot types, this - * way ASP Dependency Injection can glue everything together without ambiguity. The - * ConversationState is used by the Dialog system. The UserState isn't, however, it might have been - * used in a Dialog implementation, and the requirement is that all BotState objects are saved at - * the end of a turn. - */ -public class DialogBot extends ActivityHandler { - - protected Dialog dialog; - protected BotState conversationState; - protected BotState userState; - - public DialogBot( - ConversationState withConversationState, - UserState withUserState, - T withDialog - ) { - dialog = withDialog; - conversationState = withConversationState; - userState = withUserState; - } - - @Override - public CompletableFuture onTurn( - TurnContext turnContext - ) { - return super.onTurn(turnContext) - .thenCompose(result -> conversationState.saveChanges(turnContext)) - .thenCompose(result -> userState.saveChanges(turnContext)); - } - - @Override - protected CompletableFuture onMessageActivity( - TurnContext turnContext - ) { - return Dialog.run(dialog, turnContext, conversationState.createProperty("DialogState")); - } -} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java deleted file mode 100644 index dcb42dd31..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/LogoutDialog.java +++ /dev/null @@ -1,73 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.BotFrameworkAdapter; -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.ComponentDialog; -import com.microsoft.bot.dialogs.DialogContext; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.schema.ActivityTypes; - -public class LogoutDialog extends ComponentDialog { - - private final String connectionName; - - public LogoutDialog(String id, String connectionName) { - super(id); - this.connectionName = connectionName; - } - - - @Override - protected CompletableFuture onBeginDialog( - DialogContext innerDc, Object options - ) { - DialogTurnResult result = interrupt(innerDc).join(); - if (result != null) { - return CompletableFuture.completedFuture(result); - } - - return super.onBeginDialog(innerDc, options); - } - - @Override - protected CompletableFuture onContinueDialog(DialogContext innerDc) { - DialogTurnResult result = interrupt(innerDc).join(); - if (result != null) { - return CompletableFuture.completedFuture(result); - } - - return super.onContinueDialog(innerDc); - } - - private CompletableFuture interrupt(DialogContext innerDc) { - if (innerDc.getContext().getActivity().getType().equals(ActivityTypes.MESSAGE)) { - String text = innerDc.getContext().getActivity().getText().toLowerCase(); - - if (text.equals("logout")) { - // The bot adapter encapsulates the authentication processes. - BotFrameworkAdapter botAdapter = (BotFrameworkAdapter) innerDc.getContext() - .getAdapter(); - botAdapter.signOutUser(innerDc.getContext(), getConnectionName(), null).join(); - innerDc.getContext().sendActivity(MessageFactory.text("You have been signed out.")) - .join(); - return innerDc.cancelAllDialogs(); - } - } - - return CompletableFuture.completedFuture(null); - } - - /** - * @return the ConnectionName value as a String. - */ - protected String getConnectionName() { - return this.connectionName; - } - -} - diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java deleted file mode 100644 index 17140fc42..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/MainDialog.java +++ /dev/null @@ -1,130 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import java.util.Arrays; -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.MessageFactory; -import com.microsoft.bot.dialogs.DialogTurnResult; -import com.microsoft.bot.dialogs.WaterfallDialog; -import com.microsoft.bot.dialogs.WaterfallStep; -import com.microsoft.bot.dialogs.WaterfallStepContext; -import com.microsoft.bot.dialogs.prompts.ChoicePrompt; -import com.microsoft.bot.dialogs.prompts.OAuthPrompt; -import com.microsoft.bot.dialogs.prompts.OAuthPromptSettings; -import com.microsoft.bot.dialogs.prompts.PromptOptions; -import com.microsoft.bot.dialogs.prompts.TextPrompt; -import com.microsoft.bot.integration.Configuration; -import com.microsoft.bot.schema.TokenResponse; - -import org.springframework.stereotype.Component; - -@Component -class MainDialog extends LogoutDialog { - - public MainDialog(Configuration configuration) { - super("MainDialog", configuration.getProperty("ConnectionName")); - - OAuthPromptSettings settings = new OAuthPromptSettings(); - settings.setConnectionName(""); - settings.setText("Please login"); - settings.setTitle("Login"); - settings.setConnectionName(configuration.getProperty("ConnectionName")); - settings.setTimeout(300000); // User has 5 minutes to login (1000 * 60 * 5) - - addDialog(new OAuthPrompt("OAuthPrompt", settings)); - - addDialog(new TextPrompt("TextPrompt")); - - WaterfallStep[] waterfallSteps = { - this::promptStep, - this::loginStep, - this::commandStep, - this::processStep - }; - - addDialog(new WaterfallDialog("WaterfallDialog", Arrays.asList(waterfallSteps))); - - // The initial child Dialog to run. - setInitialDialogId("WaterfallDialog"); - } - - private CompletableFuture promptStep(WaterfallStepContext stepContext) { - return stepContext.beginDialog("OAuthPrompt", null); - } - - private CompletableFuture loginStep(WaterfallStepContext stepContext) { - // Get the token from the previous step. Note that we could also have gotten the - // token directly from the prompt itself. There instanceof an example of this in the next method. - TokenResponse tokenResponse = (TokenResponse) stepContext.getResult(); - if (tokenResponse != null) { - stepContext.getContext().sendActivity(MessageFactory.text("You are now logged in.")).join(); - PromptOptions options = new PromptOptions(); - options.setPrompt(MessageFactory.text("Would you like to do? (type 'me', or 'email')")); - return stepContext.prompt("TextPrompt", options); - } - - stepContext.getContext().sendActivity( - MessageFactory.text("Login was not successful please try again.")).join(); - return stepContext.endDialog(); - } - - private CompletableFuture commandStep( - WaterfallStepContext stepContext - ) { - - stepContext.getValues().put("command", stepContext.getResult()); - stepContext.getContext().sendActivity(MessageFactory.text("Thank you.")).join(); - - // Call the prompt again because we need the token. The reasons for this are: - // 1. If the user instanceof already logged in we do not need to store the token locally in the bot and worry - // about refreshing it. We can always just call the prompt again to get the token. - // 2. We never know how long it will take a user to respond. By the time the - // user responds the token may have expired. The user would then be prompted to login again. - // - // There instanceof no reason to store the token locally in the bot because we can always just call - // the OAuth prompt to get the token or get a new token if needed. - return stepContext.beginDialog("OAuthPrompt"); - - } - - private CompletableFuture processStep(WaterfallStepContext stepContext) { - if (stepContext.getResult() != null) { - // We do not need to store the token in the bot. When we need the token we can - // send another prompt. If the token is valid the user will not need to log back in. - // The token will be available in the Result property of the task. - TokenResponse tokenResponse = null; - if (stepContext.getResult() instanceof TokenResponse) { - tokenResponse = (TokenResponse) stepContext.getResult(); - } - - // If we have the token use the user is authenticated so we may use it to make API calls. - if (tokenResponse != null && tokenResponse.getToken() != null) { - - String command = ""; - if (stepContext.getValues() != null - && stepContext.getValues().get("command") != null - && stepContext.getValues().get("command") instanceof String) { - command = ((String) stepContext.getValues().get("command")).toLowerCase(); - }; - - if (command.equals("me")) { - OAuthHelpers.ListMeAsync(stepContext.getContext(), tokenResponse).join(); - } else if (command.startsWith("email")) { - OAuthHelpers.ListEmailAddressAsync(stepContext.getContext(), tokenResponse).join(); - } else { - stepContext.getContext().sendActivity( - MessageFactory.text(String.format("Your token is: %s", tokenResponse.getToken()))).join(); - } - } - } else { - stepContext.getContext().sendActivity( - MessageFactory.text("We couldn't log you in. Please try again later.")).join(); - } - - return stepContext.endDialog(); - } -} - diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java deleted file mode 100644 index f3ecb9c9f..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/OAuthHelpers.java +++ /dev/null @@ -1,37 +0,0 @@ -package com.microsoft.bot.sample.authentication; - -import java.util.concurrent.CompletableFuture; - -import com.microsoft.bot.builder.TurnContext; -import com.microsoft.bot.schema.TokenResponse; -import com.microsoft.graph.models.extensions.User; - -public class OAuthHelpers { - // Send the user their Graph Display Name from the bot. - public static CompletableFuture ListMeAsync(TurnContext turnContext, - TokenResponse tokenResponse) { - User user = getUser(turnContext, tokenResponse); - return turnContext.sendActivity(String.format("You are %s.", user.displayName)).thenApply(result -> null); - } - - // Send the user their Graph Email Address from the bot. - public static CompletableFuture ListEmailAddressAsync(TurnContext turnContext, - TokenResponse tokenResponse) { - User user = getUser(turnContext, tokenResponse); - return turnContext.sendActivity(String.format("Your email: %s.", user.mail)).thenApply(result -> null); - } - - private static User getUser(TurnContext turnContext, TokenResponse tokenResponse) { - if (turnContext == null) { - throw new IllegalArgumentException("turnContext cannot be null"); - } - - if (tokenResponse == null) { - throw new IllegalArgumentException("tokenResponse cannot be null"); - } - - // Pull in the data from the Microsoft Graph. - SimpleGraphClient client = new SimpleGraphClient(tokenResponse.getToken()); - return client.getMe(); - } -} diff --git a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java b/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java deleted file mode 100644 index 54a7c9c55..000000000 --- a/samples/24.bot-authentication-msgraph/src/main/java/com/microsoft/bot/sample/authentication/SimpleGraphClient.java +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -package com.microsoft.bot.sample.authentication; - -import com.microsoft.graph.logger.DefaultLogger; -import com.microsoft.graph.logger.LoggerLevel; -import com.microsoft.graph.models.extensions.User; -import com.microsoft.graph.options.Option; -import com.microsoft.graph.requests.extensions.GraphServiceClient; -import com.microsoft.graph.models.extensions.IGraphServiceClient; - -import java.util.LinkedList; -import java.util.List; - -public class SimpleGraphClient { - private String token; - public SimpleGraphClient(String token) { - this.token = token; - } - - public User getMe() { - IGraphServiceClient client = getAuthenticatedClient(); - final List